fix: counterparty template pick crashes the page (#1291)
Picking a suggestion under "Tidigare motparter" in Bokför transaktion replaced
the page with "Något gick fel". handleOpenTemplateReview built the review state
from `{ id, name_sv } as BookingTemplate`, so `template.debit_account` was
undefined, reached QuickReviewDialog's required `defaultAccount: string`, and
threw on `accountOverride.startsWith('2')` during the first render.
Typed the dialog's template prop as a narrow ReviewTemplate whose optional
fields are actually optional, so the cast disappears and the compiler owns this
class of bug. Also carries the counterparty's learned accounts and VAT (the
preview showed the category fallback, not what the server books) and decides
"is this a counterparty booking" from the template id rather than the presence
of a line_pattern (single-line templates got an account/VAT editor the
categorize route discards).
Five more page-crashes of the same shape, adversarially verified:
- suppliers/[id] and supplier-invoices/[id] passed the error envelope OBJECT as
a toast description. The Toaster is a sibling of {children} in the ROOT
layout, so that throw escapes both segment error boundaries onto global-error.
- components/reports/views wrote the same object into a useState<string | null>
at 13 sites and rendered it bare.
- components/ui/toaster.tsx now coerces non-renderable values as a choke point.
- skattekonto read data.informationstext.length off Skatteverket's raw JSON,
where the field is not required.
- TicWorkspace read profile.statuses.length off a persisted jsonb blob. 17 of 17
prod rows predate the TIC v2 upgrade (#584) and lack the key, so that
workspace was in the error boundary for every company that had opened it.
Plus hardening: formatCurrency coerces a null currency to SEK (prod has 0 NULL
across 28 416 transactions, so defense not a live bug) and cleanSignatory
returns [] for a missing description.
Verified by rendering the real dialog against a throwaway /sandbox route: the
pre-fix prop shape reproduces the exact error boundary, the fixed one renders
D: 6570 Bankavgifter / K: 1930 Företagskonto and the matching verifikat.
No migrations.
This commit is contained in:
@@ -664,3 +664,17 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
|
||||
[2026-07-29] Approval-queue MCP App widget (render_ui on list_pending_operations): high-risk confirmed=true now comes from a human click in-widget instead of agent-asserted; payload ceiling 58K->58.5K per the in-test trim-first convention.
|
||||
[2026-07-29] OAuth error popup stays open instead of auto-closing: the postMessage is dropped on any popup/opener origin mismatch, and closing anyway made every such failure invisible (Fortnox silent-connect incident).
|
||||
[2026-07-29] FORTNOX_REDIRECT_URI on prod deliberately left on app.gnubok.se for now: flipping it to app.accounted.se before that callback URL is registered in the Fortnox Developer Portal would break connect earlier, at the authorize step.
|
||||
|
||||
[2026-07-29] Counterparty quick-review crash: typed the review dialog's `template` prop as a narrow `ReviewTemplate` instead of `BookingTemplate`, rather than filling out a full synthetic BookingTemplate. The crash existed because `{ id, name_sv } as BookingTemplate` made 20 absent fields look present to the compiler; `debit_account` came back undefined, reached the dialog's required `defaultAccount: string`, and threw on `accountOverride.startsWith('2')`. Making the optional fields optional turns that whole class into a compile error. A fabricated full template (picking an arbitrary TemplateGroup, risk_level, fallback_category for something that has no catalog entry) would have kept the type honest-looking and the data dishonest.
|
||||
|
||||
[2026-07-29] Counterparty verifikation preview passes `templateVatRate` but deliberately NOT `templateVatTreatment`: for reverse charge, JournalEntryPreview emits the basbelopp pair (44xx/4598, momsdeklaration rutor 20-24), while buildMappingResultFromCounterpartyTemplate's legacy path emits only the 2645/2614 fiktiv-moms pair. That is not an engine gap: a counterparty template is learned from the user's own past vouchers, and a voucher that HAD basbelopp lines would have been learned as a multi-line line_pattern instead. So the preview mirrors the legacy path exactly and does not invent lines the booking will not create.
|
||||
|
||||
[2026-07-29] Hardened components/ui/toaster.tsx to coerce non-renderable title/description values instead of only fixing the call sites that passed the error envelope object. The Toaster is a sibling of {children} in the ROOT layout, so "Objects are not valid as a React child" there escapes app/error.tsx AND app/(dashboard)/error.tsx and lands on global-error, which blanks the app and (via its one-shot reload) pins the user on the fallback for that path. Call sites still route through getErrorMessage; the coercion is the choke point for the one that forgets.
|
||||
|
||||
[2026-07-29] Not fixed in the counterparty crash PR, logged instead: a successful counterparty-template booking sets exitingIds (the row animates out) but shows no "Bokförd" toast and no "Ångra" undo, unlike every other booking path in app/(dashboard)/transactions/page.tsx. Adversarial verification classified it non-crash UX parity; bundling a booking-flow behaviour change into a crash fix would have widened the diff past the report.
|
||||
|
||||
[2026-07-29] formatCurrency now coerces a null/empty currency to SEK. Checked prod before writing this: 0 of 28 416 `transactions` rows have a NULL currency, so this is hardening, not a fixed live crash, and the adversarial verifiers were right to refute the "transactions list blanks" claim on reachability. It is still worth having: the column is nullable with no NOT NULL, `Transaction.currency` declares it required, the browser Supabase client carries no Database generic so rows arrive as `any`, and a `= 'SEK'` default parameter only fires on undefined (`currency: null` throws RangeError out of Intl). It also covers enable-banking's accounts_data JSONB, where currency genuinely is optional.
|
||||
|
||||
[2026-07-29] TicWorkspace ignores a cached profile blob with no `statuses` key rather than normalising it and rendering. Prod check: 17 of 17 `extension_data` rows for general/tic + company_profile predate the TIC v2 upgrade (#584) and have no statuses/signatory/board/representatives/payrolls, i.e. the workspace is in the error boundary for every company that ever opened it. Normalising alone would fix the crash but leave those sections permanently blank, because the success render path has no refresh button, and the auto-fetch effect is gated on `!profile`. Dropping the stale blob lets that effect refetch the current shape and re-save it, so the 17 rows self-heal on next open.
|
||||
|
||||
[2026-07-29] Center Node AB support deletion (Anders Orback) run manually service-side, bypassing the product's AAL2/consent gate on anonymize_user_account: his written support request is the consent (documented in the audit_log row 8501e9e0), and support had already replied "du behöver inte göra något mer", so the fix-the-button-and-let-him-click alternative would have contradicted a sent mail. Manual run mirrored the delete route + RPC body byte-for-byte; GoTrue admin logout endpoint 404s on our GoTrue version, so global signout was done by deleting auth.sessions rows (equivalent effect, ban blocks refresh regardless).
|
||||
|
||||
Reference in New Issue
Block a user