SMSF Servicing Calculator — WLTH Ocean product suite. Updates are validated against the Highlighter YTML reference calculator used by WLTH.
Removed the “Email Result” section from the bottom of the results panel. The button opened a mailto: link with a brief plain-text summary — brokers preferred just generating the PDF or sharing the saved scenario URL, so this section was rarely used. Generate PDF and Save are unchanged.
The Export to Highlighter button has been removed from the calculator. This workflow is moving to the WLTH admin portal at admin.wlth.com, which will continue to use the existing backend (the export-to-highlighter and highlighter-inbound edge functions, the calc.smsf_export_jobs table, the highlighter-exports storage bucket, and the v1.8.13 timeout sweep). Saved scenarios and the rest of the calculator are unaffected.
Companion to v1.8.12. Post-fix monitoring confirmed Highlighter does not reliably send the second, PDF-attached email per submission — under v1.8.12 those jobs would sit at submitted indefinitely instead of falsely flipping to completed. A new pg_cron sweep runs every 5 minutes and flips any job still at submitted after 30 minutes to the previously-unused email_timeout state. The export panel now renders this as “PDF report email did not arrive within 30 minutes” with the saved-session link surfaced, so the broker can open Highlighter and download the report directly without waiting on the spinner forever.
Highlighter sends two emails per submission to the per-job inbox: a near-immediate confirmation (no attachment) and a later email carrying the actual PDF. The inbound webhook was flipping every job to completed on the first email, so the broker UI showed "done" with no PDF and the later PDF email had nothing left to finalise. The webhook now only marks completed when a PDF attachment is actually captured. Notification-only emails just surface the saved-session link and leave the job at submitted, so the spinner keeps running until the PDF lands.
Easy Refi exports were landing in Highlighter with blank Introducer Name and Application Name (our Broker Name and Borrower/Client Name). Live DOM audit showed the Easy Refi form renders both labels with a trailing colon (Introducer Name:, Application Name:), while the Purchase form uses the same names without a colon. The resolver’s exact normalize-space() xpath was silently skipping both fields on every refinance run. Mapping labels now mirror the live DOM verbatim for each form; regression lock added in tests/highlighter-mapping.test.ts.
v1.8.9 got past the email-fill stage but failed on getByRole('button', {name: 'Save'}) for the modal’s submit button — the button is visible and enabled in the live DOM, so this was a race with Playwright’s accessibility computation. Switched to the more deterministic button[name="actions"][value="generate"] attribute selector with an explicit waitFor before clicking.
Tested v1.8.8’s resolver against the live Highlighter DOM before shipping. All 17 non-compound Purchase labels resolve correctly via the new td-based path. Two remaining issues surfaced: (1) the post-Save modal has zero <label> elements and no table structure — the Email Address input is targeted directly by name="hl_emailto" instead; (2) compound rows (frequency select + $ input pairs like SMSF Running Expenses, Guarantor N - Mandatory Super Contributions, Existing Property / Loan N - Repayments) are dropped for this release because their row’s first input is the frequency select and would receive a money value. Those rows are handed off to the mapping-expansion follow-up.
Purchase exports were failing with “Save modal did not appear”. Root cause: Highlighter’s Purchase form has Borrower Rate * as a mandatory field and silently refuses to open the Save modal when it’s empty. Our mapping didn’t fill it because the rate is computed at runtime in the UI (from product, LVR, loan amount) rather than persisted on the scenario. The mapper now recomputes the rate server-side using the same PRODUCTS table the UI uses. Modal-not-found errors also now include a URL + visible-error snapshot so future validation gates are easier to diagnose.
Earlier v1.8.x hotfixes kept misdiagnosing the same underlying prod failure because the error column in the job-row DB was truncating Playwright’s call-log output. With the full log visible, the real cause is obvious: Highlighter renders a <div class="spinnerinit-backdrop modal-backdrop fade in"> during initial page setup, and that overlay intercepts pointer events when the lambda tries to click Save. The orchestrator now waits for that backdrop to reach state:‘hidden’ before clicking, and falls back to {force: true} if the interception persists.
v1.8.4 found the Save link but the click timed out at 5s. Highlighter’s Save control is rendered as an <a> with class="btn-modal" — clicking it opens a modal via JS rather than following its href. Playwright’s default click() waits for a navigation that never comes, so the action hangs. Using { noWaitAfter: true } on the Save click makes it return as soon as the click dispatches; the modal’s email input waitFor then acts as the modal-ready signal.
v1.8.3 prod runs were failing with “Save link not found on wizard page” because headless Chromium’s default viewport combined with Playwright’s visibility detection was hiding Highlighter’s bottom-of-page Save control from the role-based locator. The orchestrator now sets an explicit 1280×2400 viewport, scrolls to the bottom of the form before the lookup, and falls back to a CSS a:has-text('Save') locator if the role query still misses. Error messages now include the live page URL so we can catch Highlighter redirects (cookie walls, bot challenges) if they ever surface.
Export jobs were showing “submitted” but no PDF was ever emailed back. Two root causes: Highlighter’s Save control is an <a> link, not a button, so our earlier getByRole('button', /save/) never matched it; and we missed Highlighter’s two-step save flow entirely — clicking the Save link opens a modal that requires the broker’s email address and a second Save click, which is what actually triggers the email-on-save. The orchestrator now clicks the Save link, waits for the modal, fills the email, and clicks the modal’s Save button. Failures at any of those steps now return ok:false so the job row reports honestly.
Reconciled the field-label mapping against the live Highlighter Purchase and Easy Refi forms. Key renames: Broker Name → Introducer Name; Easy Refi’s Borrower Name → Application Name, SMSF Trust Name → SMSF Name; loan term converted from years to months; Purchase’s single-page form is no longer walked through a phantom Next step. Fields that don’t have a 1:1 analogue on the live form (Property Sub-Type, Location Classification, Investment Assessment Method) are now omitted rather than filled with phantom labels.
Export runs were silently hitting the 60s Vercel lambda ceiling. Each wizard step only shows a subset of mapped fields, and the old fill code relied on Playwright's default 30s action timeout to fail the absent ones — which compounded to well over the budget. The fill helpers now probe for every label with a fast .count() check before touching the DOM, and cap any individual fill/click/select action at 2s so a genuinely broken control can't stall the walk. The orchestrator also races the whole run against a caller-supplied deadline so it reports failure instead of maxing out.
Export to Highlighter runs were failing on Vercel with libnss3.so: cannot open shared object file. Bumped @sparticuz/chromium and playwright-core to versions that bundle the NSS shared libraries alongside the Chromium binary so it launches inside the serverless lambda.
Export to Highlighter — push a saved scenario to the YTML Highlighter reference calculator in one click. The returned scenario link and PDF land directly in the results panel via a capture inbox; no copy-paste.
New calc.smsf_export_jobs table tracks each export run and a private highlighter-exports storage bucket retains the PDF.
Load Scenario modal matches the original design: Load button uses a hook-arrow icon, Delete is an outlined square icon-only button, the dismiss button now reads Cancel, and the footer count shows a plain “N scenarios” when everything fits on one page.
Save after Load now overwrites the scenario that was loaded instead of creating a duplicate. The Save button relabels to Update while a loaded scenario is active. Reset clears the loaded-scenario link so the next Save starts a new row.
Load Scenario modal gains pagination (5 rows per page), a type filter (All / Purchase / Refinance), scenario-type pills on each row, and a live subtitle showing trust name + loan amount + LVR.
Mini stats under Rate Comparison now use a distinct background from the page so they read as a separate group.
Dropped the secondary helper text under the Scenario Name field.
Suisse Int’l fonts (regular + mono, all weights 100–900) are now served from the calculator itself instead of assets.wlth.com. Removes a cross-origin font-loading failure mode and makes the calculator fully self-contained for typography.
Load, Save, and Generate PDF moved from the top of the page into the top of the results panel. On desktop they’re sticky with the results; on mobile they sit with the result view, so Save & PDF are visible immediately after Calculate.
Easy Refi’s three top test tiles (Lower Rate? / Repayments Lower? / LVR ≤80%) now bottom-align their values, so differently-wrapping labels don’t stagger the numbers.
Rate Comparison and other results-panel sub-panels (Breakdown, Serviceability Summary, Policy Flags, Email) now render with their proper white card background + border. A CSS scoping issue was blocking the shared .calc-card style from reaching them.
New Scenario Name field at the top of Scenario Details. Auto-fills from Borrower / Client Name plus the scenario type (“{Client} — Purchase/Refinance/Easy Refinance”) with an Auto button to regenerate.
Save is now a single click — no more prompt. The button briefly shows “Saved” with a ✓ to confirm, then resets.
Dropped the breadcrumb from the top of the page. The top-right Load / Save / Generate PDF buttons now sit vertically centered with the title.
Load, Save, and Generate PDF have moved from the results panel to the top-right of the page, next to the title. Save and Generate PDF stay disabled until you’ve calculated, so it’s clear what’s required first. The bottom-right email send is unchanged.
New Load Scenario modal shows saved scenarios in a table with Scenario Name, Date Created, Last Modified, and per-row Load + Delete actions. Includes search across scenario, trust, and client names. Individual saved-scenario entries no longer take up space in the results panel.
Deleting a saved scenario now flags the row as deleted rather than removing it from the database. The scenario disappears from the list as before, but can be recovered and still contributes to the historical version trail we use when debugging older saves.
Each saved scenario now records the calculator version it was originally saved at and the version it was most recently updated at. No user-facing change — it helps us reproduce, debug, and migrate scenarios cleanly when the calculator evolves.
Saved scenarios now show an explicit Load button next to each entry, alongside the delete icon. Previously the whole row was click-to-load, which wasn't obvious. The scenario name and date are now read-only labels with clear separate actions.
Saved scenarios now persist to your account. When you're signed in at broker.wlth.com, scenarios you save are kept against your broker account and available the next time you use the calculator — no more losing them on page reload. Click any saved scenario to reload the full form and result.
Single sign-on with the broker portal. The calculator is now hosted at smsf-calc.wlth.com and shares its session with broker.wlth.com — sign in once and you're signed in everywhere under *.wlth.com. The calculator can also be embedded inside the broker portal without a second login.
Unauthenticated users can still use the calculator as before — only the Save feature is hidden when not signed in. Saved scenarios are always private to you; row-level security enforces this at the database layer.
3 live-verified Easy Refi scenarios (ER-LV01, ER-LV02, ER-LV03) confirmed against the Highlighter YTML Easy Refi reference calculator. Scenarios cover: clean LVR pass (75.18%), LVR boundary fail (80.20% > 80%), and rate + repayment fail (new rate higher than current). All 108 automated tests pass.
Highlighter buffer behaviour documented — the Highlighter always adds a $1,500 minimum buffer to the new loan amount regardless of whether Rapid Refi Settled is ticked. Our calculator only adds this buffer when rapidRefi = true. The resulting LVR difference is ≤ 0.003% (well within the 0.1% test tolerance).
Comma formatting on all dollar inputs — numbers are now displayed with thousands separators when not actively editing (e.g. 2,000,000 instead of 2000000). Formatting is applied on all dollar fields across both the Standard and Easy Refi calculators.
Default values updated — Government Charges (est) now defaults to $25,000 and Solicitor Fees & Other Costs to $5,000 to better reflect typical SMSF purchase transaction costs.
Accounting Fees and Audit Fees fields removed from the Fund Running Costs section. These costs should now be included in the single Annual SMSF Running Costs field.
SMSF Running Costs field renamed to Annual SMSF Running Costs to better reflect its purpose. The $2,500 minimum floor (incl. GST) still applies automatically.
Easy Refi extended-term warning — when the new loan term is longer than the remaining term on the existing loan, an amber warning is now displayed reminding the broker to explain the total lifetime cost consequences to the client.
Rapid Refi Settled checkbox now correctly wired into the Easy Refi serviceability checklist as a 6th required criterion. Previously the checkbox was present in the form but had no effect on the pass/fail outcome. The application only passes when all 6 criteria are met.
IO loan servicing assessment now correctly uses P&I repayments over the remaining loan term after the interest-only period ends — e.g. a 5-year IO on a 30-year loan is assessed as P&I over the remaining 25 years (300 months), not as IO-only interest. This was confirmed live against the Highlighter YTML reference calculator and changes the outcome for high-LVR IO scenarios (S05 correctly changes from Pass → Fail at 75% LVR IO).
Live-verified 4 additional reference scenarios (S02, S04, S05, S13) directly against the Highlighter YTML purchase calculator. All computed outputs — income, tax, net income, NDI, LVR — confirmed to match to the dollar. Test suite now includes 5 live-verified scenario blocks with 136 automated tests total.
Updated S05 (IO 75% LVR) and S06 (IO 80% LVR) expected test values to reflect the corrected P&I-over-remaining-term repayment formula.
S01 live-verified against the Highlighter YTML purchase calculator. All six key outputs — LVR (70.00%), assessment rate (8.94%), total income ($66,600), loan interest deduction ($41,640), 15% SMSF tax ($3,744) and net income ($62,856) — confirmed identical to the reference calculator.
Automated test report generated on every release — an HTML/JSON report showing all scenario results with pass/fail status. Available at /test-results/.
116 regression tests covering all 15 standard purchase/refinance scenarios and 5 Easy Refi scenarios. Each test checks NDI (±0.005), pass/borderline outcome, LVR (±0.1%) and net income (±$5).
15% SMSF tax calculation corrected to apply to net fund income (gross income less loan interest deduction), matching the Highlighter YTML methodology. Previously the tax was applied to gross income before the interest deduction.
Comprehensive scenario test suite — 20 designed test scenarios covering all major product and policy combinations: P&I and IO loans across all LVR tiers, commercial and residential property, single and multi-member funds, existing debts, low liquidity edge cases, proposed contributions, investment income via deeming, and fixed-rate products.
Pure calculation functions extracted from the Vue composable into a standalone smsfCalc.ts module, making the calculation logic independently testable without any UI framework dependency.
SMSF Servicing Calculator for the WLTH Ocean product suite. Supports standard purchase and refinance serviceability assessment using the NDI (Net Disposable Income) methodology — income from SMSF contributions, rental and investments less 15% fund tax, assessed against P&I repayments at the stress-tested assessment rate plus fund running costs.
Easy Refi eligibility checker for the WLTH SMSF Easy Refi product — automatically checks whether the refinance results in a lower rate, lower repayments, and LVR within the 80% policy limit.
PDF report generation, save & restore scenarios, and email summary. Designed for broker and lending team use with the WLTH brand system.
Important: This Calculator provides an indication only and does not constitute a credit assessment or approval. WLTH Lend Pty Ltd CRN 525 873 as authorised by WLTH Pty Ltd AFSL & ACL 525 752.
Prod diagnostics revealed the export was filling zero fields per run. Root cause: Highlighter’s form has no
<label for="id">associations anywhere — visible labels are<td>cells and inputs are siblings in the same<tr>. Mandatory fields also carry a trailing*suffix on the visible text. OurfindByLabelonly handled semantic-label layouts so matched nothing. Replaced with a multi-strategy resolver that tries Playwright’s getByLabel, a<label>-plus-form-group search, a<td>-in-same-row search, and retries each with a trailing “ *” for mandatory fields. The modal’s Email Address lookup now routes through the same resolver (was usingpage.getByLabelwhich always returned zero).