* chore: repo-wide bloat sweep, remove dead code and fold duplicate helpers
Remove 33 dead files, ~270 unreferenced exports/types, 13 dead i18n
namespaces and 4 unused dependencies; fold byte-identical helper copies
into one canonical home each (lib/utils chunk/sleep/utcDateStamp,
lib/dates/iso, lib/invariants/uuid, lib/xml/escape, lib/reports/sru/format,
lib/pdf/number-text, lib/browser/panel-request, lib/api/v1/body +
v1ValidationError rolled out to ~55 v1 routes, booking-template schemas).
No behaviour change: v1 bodies and status codes, MCP tool schemas, DB
writes and money math are untouched. Naive ore rounding was deliberately
not swapped for roundOre; see DECISIONS.md 2026-09-02 for the full list
of things left alone on purpose.
tsc, lint, 19588 unit tests and check:guards green; antipattern baseline
ratcheted (naive-ore-round 622 -> 620, hand-rolled-invariant 115 -> 113).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(transactions): import RawTransaction from @/types after the ingest re-export removal
CI's type ratchet (check:types, full tsconfig) caught the one test file
that still imported the type through lib/transactions/ingest.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* fix(rot-rut): surface drop-out reasons in payout request dialog and keep selectors usable (#1884)
Four silent drop paths made a paid RUT invoice invisible in the begaran
dialog (neither eligible nor blocked), and the empty list hid the year
picker so the dialog looked dead:
1. deduction lines without a header deduction_total: a second line-based
candidate query now finds them and they block as DEDUCTION_TOTAL_MISSING
(also at file generation: the 1513 receivable was never booked).
2. partially_paid with the customer share settled: remaining_amount = 0
(total - paid_amount - deduction_total, migration 20260817191708) now
counts as paid in evaluateInvoiceForFile; a genuine partial blocks as
NOT_PAID with the outstanding amount.
3. NO_DEDUCTION_OF_TYPE is no longer filtered out of blocked: the message
points at the other type, and the dialog's empty state adds a
switch-type hint.
4. invoices held by a generated/submitted begaran block as
ALREADY_REQUESTED naming the request; decided requests stay omitted
(finished business, visible in the history list).
The dialog keeps the year picker rendered when the list is empty (current
year as inert fallback) and opens the blocked list by default when nothing
is eligible.
Fixes#1884
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(rot-rut): skeptic hardening: decided requests vanish on both tabs, customer share derived from header fields (#1884)
Two skeptic refutations against the frozen PR head:
1. Regression: the wrong-type branch ran before the active-request lookup,
so invoices of the OTHER type whose begaran was already decided
resurfaced forever as NO_DEDUCTION_OF_TYPE in the opposite tab's blocked
list, and the empty-state hint pointed at a tab where they never appear.
The decided-request skip now runs first, on every tab.
2. Correctness: the paid gate and the NOT_PAID message trusted
remaining_amount, but payment-sync's storno path recomputes it WITHOUT
subtracting deduction_total, so the stored column can carry Skatteverkets
1513 share and the dialog could assert a wrong customer-outstanding
figure. The gate now derives the customer share as
total - paid_amount - deduction_total (the buildInvoiceWriteData /
migration 20260817191708 formula) from fields every settlement path
maintains.
Tests pin both: decided+wrong-type omitted from both lists, corrupted
remaining still classified and reported from the derived share.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(rot-rut): align CANDIDATE_STATUSES comment with the derived-share gate (#1884)
The skeptic-hardening commit moved the paid gate off remaining_amount to
the derived customer share (total - paid_amount - deduction_total); the
comment still named remaining_amount as the signal.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(rot-rut): explicit decided-status set + correction-path wording (#1884)
Swedish accounting review findings on the candidate list:
1. The decided-begaran skip inferred 'decided' by exclusion (anything not
generated/submitted), so a future request status would make an invoice
vanish from both lists, exactly the silent drop the module forbids.
DECIDED_REQUEST_STATUSES now names paid/partially_paid; any other
status held by a request lands in blocked as ALREADY_REQUESTED with a
generic message. Test pins it.
2. The DEDUCTION_TOTAL_MISSING message said only 'ratta fakturan', which
could read as an invitation to edit a booked invoice directly. The
invoice edit route already refuses sent/paid/booked invoices, and the
message now names the sanctioned path: drafts edit directly, sent or
paid invoices are corrected via credit note + new invoice.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Seven radii were in circulation (4/5/6/8/12/16px + pill) with no rule for
which went where; one toolbar row on /transactions mixed four shape
languages. This locks a 4-tier ladder (design.md convention 16):
- pill: interactive toolbar controls (buttons, chips, pickers, segmented
controls, toolbar search, count nubs)
- rounded-xl (12px): overlay tier: page panel, dialogs, slide-overs
- rounded-lg (8px): cards, form fields, popover/menu content, boxes
- rounded-sm (4px): nested leaves (menu items, checkboxes, kbd/code nubs)
Changes:
- New SegmentedControl primitive (pill-in-pill tablist, h-8) replaces the
hand-rolled bg-muted/70 tablist copied across 11 files
- New ToolbarSearch primitive (pill, h-8) adopted on 9 page toolbars;
dialog/picker searches keep the rounded-lg Input
- dialog.tsx 8px -> 12px, matching SettingsModal/slide-over/CommandPalette
- ContextPicker chips at the shared h-8 toolbar height
- ~300 rounded-md / bare rounded call sites remapped by role; auth icon
tiles and the mobile nav sheet come down from 16px to 12px
- rounded-md, bare rounded, rounded-2xl and rounded-[Npx] are dead
vocabulary, enforced by a new off-ladder-radius check in check:guards
Verified: lint 0 errors, 14422 unit tests pass, check:guards green, tsc
clean on all changed files, sandbox screenshots of transactions/
bookkeeping/granskning toolbars and the Ny verifikation dialog.
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(design): stop synthesizing bold on Hedvig display headings
Hedvig Letters Serif ships weight 400 only, but the h1-h3 base rule
forced font-weight 500 and DialogTitle/SheetTitle stacked font-semibold
on top, so every display heading rendered browser-synthesized bold: the
smudged heavy look on dialog titles and page headings. Drop the base
rule to 400, remove the weight utilities from the title primitives, and
sweep the 41 files that hand-set font-medium/semibold/bold on serif
headings (a pattern design.md already forbids). Headings that opt into
font-sans keep their weight.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(design): drop empty className left by the weight sweep
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The founder wants the yellow boxes gone everywhere. The design system
already agreed: status colors are data, not chrome (convention 12) and
attention is a single ochre sentence, never a banner (convention 6).
This enforces it:
- ConfirmationDialog: the amber warning panel is now an AttnLine, and
the hardcoded Swedish default warningText is gone: it injected an
immutability warning into dialogs whose authors never asked for one
(every current caller passes the prop explicitly, so no behavior
change at any call site).
- Badge warning variant: amber fill replaced with a hairline chip and
ochre text.
- DestructiveConfirmDialog warning variant: neutral icon disc, default
primary confirm button (only --destructive survives as chrome).
- BankSyncStatusChip stale state: same neutral shape as the healthy
chip, ochre text carries the signal.
- SandboxBanner: solid amber bar becomes secondary-on-border chrome.
- BankIdAuth, BankIdCompanyPicker, SessionTimeoutModal: the last three
raw-amber (bg-amber-*) holdouts moved onto tokens, the company-picker
banner becoming a plain AttnLine.
- Mechanical sweep of the ~58 hand-rolled bg-warning/border-warning
boxes across 45 files: fills to bg-muted/30 (icon discs bg-muted),
borders to border-border, text-warning-foreground to text-attn. The
account-class dots in account-number.tsx keep bg-warning: they are
data indicators, not chrome.
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(invoices): ROT/RUT payout file dialog and file guards
Rebuild the UI for the existing headless HUS V6 payout-file flow
(demanded via #789): a dialog on the invoices page to pick eligible
paid ROT/RUT invoices, generate the XML, download it and track
request status. Adds file-level guards from the Skatteverket spec:
future payment dates blocked, one file per payment year, max 100
cases per file, with per-invoice blocker messages.
Submission stays manual (upload + sign in the SKV e-service);
no direct submission API exists.
Fixes#789
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(invoices): compute rot-rut gating date in Europe/Stockholm
The candidate and begäran date defaults used the UTC calendar day,
which near midnight Swedish time could wrongly block or admit an
invoice via FUTURE_PAYMENT_DATE and shift the 31 January deadline
warning. Use getSwedishLocalDate() like the bookkeeping engine.
Raised by the Swedish compliance review on PR #1380.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>