Commit Graph

2 Commits

Author SHA1 Message Date
Jakob Wennberg 1c04262e06 fix(byra): stop passing a lucide icon across the server boundary on the KPI empty state (#2110)
app/(dashboard)/byra/kpi/page.tsx is a Server Component that passed the lucide
icon TrendingUp as icon={TrendingUp} into EmptyState, a Client Component.
lucide builds every icon with forwardRef, so the value cannot be serialized
across the RSC boundary: the render throws and the route 500s into the
dashboard error boundary. The branch runs only when the byra team has zero
non-archived clients, which is every brand-new byra on day one, and retrying
never helps.

Adds an EmptyByraClients preset beside the existing ones. A reference to a
client component is serializable where the icon is not, so the icon, the copy
and the design treatment are unchanged.

The preset reads from the byra namespace under its own translator name rather
than t, because i18n/__tests__/message-keys.test.ts maps one variable name to
one namespace per file: reusing t would silently re-point every other preset's
key in this file at byra.


Claude-Session: https://claude.ai/code/session_016ifKg6Ec67A39oxfGPU1yc

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 14:51:03 +02:00
Jakob Wennberg 198d3092c7 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.
2026-07-29 19:20:25 +02:00