c0ecf2fa3bebd46bdfd0169efd73b89653d1dfed
11 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3ee3565d6d |
perf(reference-data): sweep the remaining raw reads onto the session cache, ratchet to 0 (#1941)
Final consumer migration of the responsiveness plan: the 35 files still fetching fiscal periods, settings, accounts, cash accounts, dimensions or templates on their own now read lib/reference-data, and every client write site invalidates the shared cache instead of refetching locally. Settings and registries: FiscalYearsManager, FiscalPeriodEditor (period snapshotted once per company so a revalidation cannot reset dates being edited), BookingTemplatesPanel, ChartOfAccounts, ChartOfAccountsManager, EditAccountDialog, CorrectionEntryDialog, StrikeLinesDialog, InvoicePaymentAccountsSettings; the dimensions registry (DimensionsManager, DimensionCombobox, LineDimensionFields, DimensionFilter, bookkeeping/[id]) reads useDimensions and the ad-hoc fetchDimensions/fetchDimensionsCached helpers are deleted. Pages and pickers: CashAccountSelector (FyPicker-shaped restore, once per company load), use-account-names, FiscalYearGapNotice, OpeningBalancePeriodStep, BankFileConfirmStep, ImportReviewStep, the import page (invalidates accounts + periods after a SIE execute), customers list, invoices list + detail, pending, salary employee, asset dispose, year-end and periodisering pages (invalidate periods after closing), reports DimensionPnlView (its pivot picker read the wrong payload key and was always empty; it now populates), SkatteverketPanel, TemplatePicker, ArticleForm (vat_registered). Invoice dialogs and extensions: SendInvoiceDialog, PaymentBookingDialog (init reduced to the credit-note lookup + catalogue, proposal and voucher preview fire on open when cached; a local getSession replaces the network getUser for the fallback CC), InvoiceInboxWorkspace, TicWorkspace, ArcimMigrationWorkspace (invalidates after each SIE import step), enable-banking AccountPickerDialog. raw-reference-fetch ratchet: 35 -> 0 files. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
f8db38f989 |
fix(analytics): mask session replays by default, chrome-only unmask (#1639)
* fix(analytics): mask session replays by default, chrome-only unmask Invert PostHog session-replay masking from visible-by-default with pattern masking to deny-by-default: every input value is masked wholesale (rrweb maskAllInputs, no maskInputFn) and every text node is masked unless it sits under data-ph-unmask chrome or a table column header (th). Chrome tags live on the shared UI primitives (PageHeader, Label, Button except combobox triggers, TabsTrigger, Badge, Card/Dialog/Sheet titles, tooltips, help popovers, empty states, settings labels), and tagged chrome is still pattern-scrubbed for amounts and person-/organisationsnummer. data-ph-mask beats data-ph-unmask, so call sites that interpolate user data into chrome stay masked; a very-thorough audit swept every unmasked primitive and each found site got a call-site mask. Confirm-dialog wrappers and toasts stay masked centrally: their copy describes user objects by design. Untagged new UI over-masks instead of leaking. Privacy policy, RoPA and decision log updated in the same change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(analytics): tag detail-section chrome merged from main The register-detail primitives landed on main after the replay-masking audit ran: kickers and DefRow labels are static i18n chrome, values stay masked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(analytics): close skeptic and review findings on replay masking Explicit data-ph tags now resolve before the th chrome fallback, so a th nested inside a data-ph-mask container masks correctly (regression test added). Seven missed text-leak sites get call-site masks: delete-invoice and credit-page invoice numbers, IB-correction voucher reference, TIC orgnr (served unnormalized, so the separator-based scrub cannot be relied on), articles search-term empty state, dimension segment labels, and activate-account buttons. The attribute channel is closed with rrweb's blockClass: inputs whose placeholder carries an effective user value (salary overrides, correction description, danger-zone confirms, credit confirm) get ph-no-capture, removing the element from recordings while the prefill UX stays intact; the pivot-th title attribute is dropped. Privacy-policy effective date bumped to 2026-08-17. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
9686b54b41 |
refactor(design): lock the border-radius ladder, one radius per role (#1607)
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> |
||
|
|
f633349c8d |
feat(analytics): show form field labels in session replays (#1416)
* feat(analytics): show form field labels in session replays Replays showed the sidebar after #1412 but form pages were still fully masked, so you could not tell WHICH field a user was interacting with. Tag the shared Label primitive (components/ui/label.tsx, used by every form in the app) with data-ph-unmask: field labels are static i18n chrome, and maskAllInputs keeps every typed value hidden. The one Label whose text is user data, the user-defined dimension name in LineDimensionFields, gets data-ph-mask, which wins even on the same element because maskTextFn checks it first. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(analytics): re-mask three Labels that render user data Completing the audit the PR review asked for: a multiline sweep over every Label child found three call sites whose label text is user data, missed by the first single-line pass. Danger-zone confirm labels interpolate the user's email (AccountDangerZone) and the company name (CompanyDangerZone), and the periodisering auto-detect row label is counterparty name + invoice number. All three now carry data-ph-mask. Currency-code and row-count interpolations were reviewed and left visible: categorical UI state, not books data. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4352862845 |
feat(register): concept scenes 25-31 for the seven register pages (#1131)
Kunder, Leverantorer, Artiklar, Anstallda, Anlaggningstillgangar, Kontoplan and Kostnadsstallen & projekt all move to the house language: serif header with quiet secondary actions + primary pill, dry-table lists (TH/TD from components/ui/dry-table, stagger-enter, clickable rows, responsive column hiding), muted text for normal states with chips only for exceptions (Verifierad, Inaktiv, Arkiverad, Avyttrad), and a muted pgnote count footer. Kontoplan gets the scene 30 treatment: BAS 2026 chip, Mina konton/BAS-katalogen seg with counts, class band rows as folds, and the activation toggles preserved. Dimensions swaps its tabs for the house seg with per-dimension counts and Tagga historik as a quiet header link. Separate mobile card lists and Card wrappers are gone; all dialogs, sort params, export menus, gating and load-more behavior preserved. Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
ec27228a8e |
style: remove em/en dashes repo-wide, add CLAUDE.md rule against them (#890)
Em dashes (—) and en dashes (–) had spread across comments, docs, tests, and a few UI strings, reading as AI-generated boilerplate rather than house style. Replaced each with punctuation matching its context: colon for explanatory clauses, comma for asides, plain hyphen for numeric/legal ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for paired-dash asides. messages/en.json and messages/sv.json were fixed by hand together to keep sv/en in sync. Left untouched where the dash is the functional subject rather than decorative punctuation: date-range-parser.ts's separator regex, charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the agent system-prompt files that already instruct against em dashes, and a golden iXBRL test fixture compared byte-for-byte. Also fixes two bugs surfaced along the way: an off-by-one in ApiKeysPanel's scope-label split (a leftover from an earlier partial pass), and a charset-repair test that had lost the literal en-dash it exists to verify. Regenerated the agent atom seed migration (skills:generate) since 27 SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes, with an explicit carve-out for the functional-dash cases above. Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
764348e99c |
feat(dimensions): PR10 advanced — custom dimensions, hierarchy, account rules, commit enforcement (#886)
* feat(dimensions): PR10 advanced — custom dimensions, hierarchy, account rules, commit enforcement The final rung of the dimensions ladder (dev_docs/dimensions_implementation_plan.md §7 row 10): - custom dimensions: POST /api/dimensions creates registry dims (next free SIE number >= 20 when omitted; explicit numbers allowed — SIE import already mints reserved ones); register gets a 'Ny dimension' dialog with a quiet Avancerat disclosure for the #UNDERDIM parent; GET now carries parent_sie_dim_no (the column + SIE round-trip existed since PR1/PR5 — this exposes it) - account_dimension_rules (migration 20260703120000): one rule per (account, dimension) — required / default / fixed, per-rule is_active, company-scoped RLS, composite FK to the registry, value-presence CHECK - enforcement, opt-in BY CONSTRUCTION (zero rules = engine byte-identical; deliberately NO settings toggle — a rule that exists but is ignored is worse than either extreme): default/fixed apply onto line bags at draft creation (fixed overwrites, default fills); required asserts at commitEntry with a Swedish MANDATORY_DIMENSION_MISSING naming every account + dimension; the bulk-book route runs the same policy before its RPC; storno/correction paths never pass through commitEntry so history always reverses regardless of policy; rule fetches fail open incl. thrown exceptions - chart of accounts: per-account Dimensionsregler section in EditAccountDialog (Krävs/Förval/Låst, value picker, pause switch), gated on the existing dimensions toggle, quiet when empty - pickers: LineDimensionFields is registry-driven (one combobox per active dimension, cached fetch, hardcoded 1/6 fallback) — every existing mount lights up custom dims with zero changes - agent briefing: per-dimension required_on_accounts/default_on_accounts so agents self-correct instead of bouncing off the policy error - rules CRUD API with existence/active/company validation and qualified DTO ids; firm_id FK deferred until the firms table lands (per plan) 39 new tests (pure-fn rules, engine enforcement, both new API surfaces, pg-real RLS/CHECK/cascade suite); full suite 6,791 green; migration replayed on a fresh container. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: renumber migration to 20260703200000 — version collision with prod The concurrent session shipped pending_operations_add_link_document_to_voucher as 20260703120000 today; the Supabase preview branch (cloned from prod) rejected the duplicate version key. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: review round — auto-pick retry on collision, fail-open warnings, query schema - POST /api/dimensions retries once past a concurrent number claim when the number was auto-picked (explicit choices still 409) - every fail-open skip of the dimension-rules policy now logs a structured warning (engine draft/commit paths + bulk-book) — deliberate fail-open, but observable - GET /api/dimensions/rules validates its query through ListDimensionRulesQuerySchema instead of an inline regex Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
28827c2613 |
feat(dimensions): voucher-level retro-tagging workbench — reversal pairs hidden by default (#874)
* feat(dimensions): voucher-level retro-tagging workbench, reversal pairs hidden by default UX rework of the BulkTagWorkbench (follow-up to #867): the verifikat is now the unit of work, matching how users think ('that invoice belongs to project X') and how every Swedish bookkeeping tool presents entries. - /api/dimensions/tagging/lines returns voucher-grouped results via a two-step query: filters select QUALIFYING vouchers (line-level predicates become 'voucher has such a line' through the inner join), then the complete line set for each — tagging a voucher always covers the whole verifikat, never the filtered subset of one - reversal pairs are EXCLUDED by default: an annulled entry and its storno net to zero in every dimension bucket as long as both sides carry the same tag, so retro-tagging them is a no-op with an asymmetry foot-gun attached; 'Visa annullerade' opts them back in, and the blocking motverifikat confirmation survives only in that view (correction rebooks remain taggable — only the original+storno pair is hidden) - voucher rows: label, description, date, line count, distinct tag chips + 'Delvis taggad' state, single total amount (no debit/credit columns); chevron expands to per-line rows (signed amount, per-line checkboxes) for the mixed case (a voucher split across projects) - selection stays line-id based under the hood (the retag RPC is per line); shift-click ranges operate on vouchers; apply groups by resulting map and now chunks to the apply route's 500-line cap; failed vouchers auto-expand with their Swedish RPC errors inline - 'endast otaggade' now means 'vouchers with at least one untagged line'; the cap counts vouchers (default 150, max 300) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: pull counter-vouchers into the annulled view even outside filters (review) Without this, a pair leg whose counter fell outside the date range would show no motverifikat warning at all — one-sided tagging would slip through silently, exactly the Srf U 14 skew the guard exists to prevent. Also scope the line fetch explicitly through the parent company filter (defense in depth). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
816b1769c8 |
feat(dimensions): PR6 retro-tagging — audited retag carve-out, BulkTagWorkbench, staged MCP tool (#867)
* feat(dimensions): PR6 retro-tagging — audited retag carve-out, workbench, staged MCP tool
Tier-2 retro-tagging (founder decision №1, approved 2026-07-02): posted
entries in OPEN periods can have their dimension tags changed through ONE
audited path — everything about the verifikat itself stays immutable.
Carve-out (migration 20260702170000): the line-immutability trigger gains a
single narrow branch — while the transaction-local GUC set by the RPC is
active, an UPDATE of a posted line is admitted iff every non-dimension
column is unchanged, enforced by a whole-row to_jsonb diff (any future
column is protected by construction; mirrors cost_center/project are in the
changeable set because they are derived views of dimensions['1']/['6']).
Precedent: mark_entry_as_opening_balance (20260613120000).
retag_line_dimensions RPC: tenant guard (20260619130100 pattern), writer
gate (viewers rejected), posted-only, open period + company lock date
enforced, every code validated against the ACTIVE registry, immutable
dimension_retag_log row (before/after/actor/reason, INSERT-only via its own
trigger, no FKs so the trail survives hard-deletes) written BEFORE the
carve-out UPDATE. Idempotent no-op without a log row. Untag ({}) supported.
Legal position per the plan: dimensions are internredovisning metadata, not
BFL 5 kap 7§ verifikat content — this is strictly more conservative than
Fortnox/Visma (dimension-only diffs, open periods only, immutable log,
storno past locks — Tier 3 has no exceptions).
Mandatory pg suite (11 tests): GUC-less updates still blocked; amounts/
description can never change even under the GUC (transaction-local);
closed/locked/lock-date, role, registry, draft and cross-tenant rejections;
log immutability; gnubok.allow_delete bulk path unaffected.
UX (all writes through the ONE RPC): pencil on posted-voucher lines in
bookkeeping/[id] ("Påverkar endast internredovisningen, inte verifikatet")
+ retag-history card; BulkTagWorkbench at /dimensions/tagging (filters,
shift-select, merge vs "Ersätt tagg" replace mode, reversal-pair warning
with "Inkludera motverifikat" auto-selection, per-line failure display).
MCP: gnubok_tag_journal_lines (bookkeeping:write) — filter block resolved
via resolve-don't-select, ≤500 lines, staged via pending_operations (new
op type migration 20260702171000, medium risk tier, shared Zod validation
boundary between staging and commit; executor loops the RPC per line with
partial-success aggregation).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dimensions): address #867 review — SQLSTATE classification, blocking storno confirm, documented divergence
- Retag route classifies RPC errors by SQLSTATE instead of message-regex:
P0001 (every rule violation in the RPC) → 409 verbatim, 42501 (tenant
guard) → 403, anything else → logged 500 with a generic message. No more
substring sniffing.
- The workbench's storno-pair warning escalates to a BLOCKING confirmation
naming the unselected counter-vouchers before apply (Srf U 14 gross
reporting — one-legged retags silently skew project P&L; the banner alone
was advisory).
- The empty-bag divergence is now documented on both schemas as intentional:
the direct dialog/workbench path allows {} (human untags phantom codes,
logged with reason), the MCP staged path rejects it (agents never
bulk-clear history).
Triage notes: the log's missing FKs are the point (behandlingshistorik must
survive undo_sie_import hard-deletes — a cascade would erase the trail);
SIE exports are generated fresh on demand, never cached, so post-retag
exports carry the new object lists automatically; date-scoped registry
values are deliberately not enforced at retag because entry creation does
not enforce them either — enforcing in one path only would be incoherent
(both belong to the PR10 rules engine).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
11126d6d56 |
feat(dimensions): PR3 tagging — voucher-form pickers, MCP dimension tools with resolve-don't-select, engine soft validation (#859)
Phase 3 of dev_docs/dimensions_implementation_plan.md. Companies with dimensions_enabled=false see zero change; existing free-text API writers keep working (validation is toggle-governed). Engine (soft validation): - validateEntryDimensions() in dimension-resolver: zero queries for untagged entries; toggle off → passthrough; toggle on → one settings fetch + two registry queries, rejects unknown dims/codes and archived values with Swedish per-code messages (DimensionValidationError, 400, details.issues). Wired into createDraftEntry + updateDraftEntry before any insert; reversal/ storno paths untouched (verbatim copies). Fails open on transient registry errors — soft validation must never block bookkeeping. MCP (agent write path): - New tools: gnubok_list_dimensions, gnubok_list_dimension_values (fuse.js fuzzy), gnubok_create_dimension_value (STAGED via pending_operations — agents never silently mint reporting values; new op type + CHECK migration + executor with duplicate-idempotency). - create_voucher/correct_entry: per-line dimensions bag + default_dimensions, resolve-don't-select server-side (code OR natural-language name; exact → fuzzy ≤0.30 with ≥0.15 runner-up margin; non-exact resolutions echoed with confidence; ambiguous → ranked candidates, no auto-create). - gnubok_get_agent_briefing gains a dimensions block (enabled, dims, top values) — omitted when registry empty. - TOOL_SCOPE_MAP entries; risk tier low for staged value creation. UI: - JournalEntryForm (manual voucher + TransactionBookingDialog embed): header "+ Kostnadsställe/Projekt" progressive disclosure (gäller alla rader with documented inheritance rule) + per-row tag popover + compact KS·PR badges; gated on dimensions_enabled. - Voucher detail: display-only dimension badges with registry-name resolution. - EditDraftEntryDialog carries line dimensions so editing a draft no longer strips tags. categorize/bulk_book dims deferred to PR7 (needs the bulk_book RPC migration). Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8bb49c07a2 |
feat(dimensions): PR2 registry — CRUD API, register UI, settings toggle, SIE export on the new registry (#858)
* feat(dimensions): PR2 registry — CRUD API, register UI, settings toggle, SIE export on the new registry
Phase 2 of dev_docs/dimensions_implementation_plan.md. Companies with
dimensions_enabled=false (default) see zero change.
API:
- Dashboard CRUD: GET /api/dimensions (lazy-seeds system dims 1/6 via the
ensure_company_dimensions RPC), PATCH /api/dimensions/[id] (is_system
rename blocked), POST/PATCH/DELETE values (code immutable after creation;
strict Fortnox code format ^[A-Za-z0-9ÅÄÖåäö_+\-]{1,20}$ at the API layer;
retention-trigger deletes surface the Swedish "arkivera istället" message
as 409 DIMENSION_VALUE_REFERENCED).
- POST /api/dimensions/import-existing — scans journal_entry_lines.dimensions
for unregistered codes and mints inactive placeholder registry rows.
- v1 public API: GET dimensions + POST values (Idempotency-Key, dry-run),
registered in the OpenAPI spec (102→104 endpoints).
- dimensions_enabled boolean on company_settings (new migration,
UI-visibility only, never correctness-bearing) exposed through the
existing settings read/update path.
SIE export (lib/reports/sie-export.ts):
- Reads the new dimensions/dimension_values registry; legacy
cost_centers/projects tables now have zero readers (drop migration next).
- Fixes the latent Visma-rejection bug: #OBJEKT now declared for INACTIVE
values referenced by lines.
- Generic-N: #DIM/#UNDERDIM loop sorted by sie_dim_no; #TRANS object lists
serialize from the line JSONB map (sorted, '01'→'1' collapse); orphan
codes/dims synthesize declarations from the SIE reserved-number seed —
every referenced (dim, code) pair is guaranteed declared.
UI:
- /dimensions register (Register-recipe): tabs per dimension, search,
sortable table, value dialog (code immutable on edit, projekt dates on
dim 6), archive-not-delete affordances.
- DimensionCombobox shipped (mounts in the tagging PR).
- Settings toggle "Aktivera kostnadsställen & projekt" — toggle-on runs the
import-existing scan and links to the register.
- Nav row in redovisning, rendered only when dimensions_enabled (same
mechanism as pays_salaries).
- dimensions.* i18n namespace (51 keys, sv/en parity).
- Sandbox seed: demo dims + values, revenue line tagged {"1":"BUTIK","6":"P001"}.
Verified: 6328/6328 unit tests, guard + coverage gate green, tsc parity with
main (210=210), production build passes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dimensions): PR2 review round — atomic archived-create, UNDERDIM ordering, import robustness, date semantics
- POST values accepts is_active so "create as archived" is atomic; the UI's
fragile create-then-PATCH fallback is deleted (PR Agent finding 1).
- DimensionCombobox blur revert reads the committed value/values through refs
so a selection landing inside the 150ms window always wins (finding 2).
- import-existing sanitizes candidate codes like the PR1 backfill and upserts
with ignoreDuplicates — one bad/duplicate code can no longer abort the
batch; created counted from returned rows (finding 3).
- SIE export emits all root #DIM before any #UNDERDIM so a parent always
precedes a lower-numbered child (SIE4 declaration order — Swedish review);
synthesized placeholder declarations now log one structured warning
(BFNAR 2013:2 behandlingshistorik) + defence-in-depth comment.
- Value dates rejected (400 DIMENSION_VALUE_DATES_NOT_ALLOWED) when the
parent dimension is flow-period (resets_annually=true); explicit null
still clears (Swedish review).
- Sandbox seed logs seeded dimension codes; GET /api/dimensions documents
the deliberate absence of dimensions_enabled gating (UI-visibility flag,
not a security boundary — compliance-swarm V8.2.1 rejected by design).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|