c0ecf2fa3bebd46bdfd0169efd73b89653d1dfed
14
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
43341aa55c |
feat(ui): shift-click range selection on list row checkboxes (#2117)
* feat(ui): shift-click range selection on list row checkboxes Click one checkbox, shift-click another, and every row between them takes the clicked row's new state, the way mail clients work. Turns a 20-row bulk selection into two clicks. New useRangeSelect hook (lib/hooks/use-range-select.ts) keeps the anchor and applies the range over the rows as currently rendered, so it follows filtering, sorting and paging rather than the underlying data order. A shift-click with no valid anchor (first click, or the anchor filtered away) degrades to a plain toggle. Select-all and clear reset the anchor. Wired into the 8 selection surfaces: transaction inbox and skattekonto inbox (separate ranges, since the two row types book through different endpoints), journal entry list, invoices, supplier invoices, orders, pending operations, invoice inbox workspace. Radix' onCheckedChange carries no mouse event, so each row records shiftKey from the click that precedes it; the checkbox cells get select-none so shift-clicking does not smear a text selection. The pure range rule is unit tested (10 cases: both directions, range unselect, anchor invalidation, rendered-order independence). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015n8vUx9Nukr8mHC7CVNF7y * fix(ui): void the range anchor on an empty selection, keep placeholders out Review findings from CodeRabbit and the skeptic pass, all in the new range-selection feature: - Clearing a selection left the anchor behind, so the next shift-click extended from a row the user could no longer see selected (click a row, press "Rensa markering", shift-click 30 rows down, get 30 rows). The explicit resetAnchor() calls only covered the clear paths that were wired by hand; several others (period change, filter change, post-bulk success, "Avmarkera") were not. An empty selection now counts as having no anchor, which covers every clear path including ones added later. - The invoice inbox passed optimistic upload placeholders into visibleIds even though they render no checkbox. Safe today only because placeholders are always prepended; filtering them out makes the invariant local instead of depending on insert order elsewhere. - pending: "Godkänn alla" pre-selects a non-empty set, so it resets the anchor explicitly. Two existing tests used a fixture the UI cannot reach (an anchor with an empty selection); they now start from the state a real anchor implies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015n8vUx9Nukr8mHC7CVNF7y --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
5f81c0638c |
fix(ui): make row selection checkboxes always visible at muted opacity (#2093)
* fix(ui): make row selection checkboxes always visible at muted opacity User feedback: bulk-select checkboxes hidden until hover are "sjukt pilligt": an invisible 16px target forces a precise hover-then-aim per row, and bulk selection is a primary workflow on list pages. New CHECKBOX_REVEAL_CLASS in dry-table.tsx: rest at opacity-50, solid on row hover, focus, coarse pointers, and checked state. Applied to the 8 selection-checkbox sites (transaction inbox, skattekonto inbox, journal list, invoices, supplier invoices, orders, pending, invoice inbox workspace). Also fixes a touch bug: TransactionInboxCard and SkattekontoInboxCard lacked pointer-coarse fallback, leaving unselected checkboxes permanently invisible on touch devices. Row action controls (chevrons, quiet links) keep HOVER_REVEAL_CLASS. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015n8vUx9Nukr8mHC7CVNF7y * fix(ui): meet WCAG 3:1 on resting selection checkboxes via border-foreground Skeptic finding: the checkbox primitive's border-input is ~1.4:1 against the page, so the new opacity-50 resting state composited to ~1.2:1, under the 3:1 non-text contrast minimum design.md commits to. Putting border-foreground unconditionally on the 8 reveal checkboxes lands the resting border at >=3.4:1 in every theme (light 3.41:1, dark 4.24:1, white-label palettes similar) and the hover/solid state at ~17:1. The border class lives at call sites, not in CHECKBOX_REVEAL_CLASS, because the constant is sometimes applied to a borderless wrapper (InvoiceInboxWorkspace); documented in dry-table.tsx. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015n8vUx9Nukr8mHC7CVNF7y --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
4cf227001d |
fix(skattekonto): bound the sync to the first räkenskapsår, add an ignore path, EF-aware avdragen skatt (#1729)
* fix(skattekonto): scope the sync and the avdragen-skatt rule for enskild firma
Two EF problems on the skattekonto surface:
1. Stuck pre-company rows. The sync never passed datumFrom, so SKV's
~555-day default lookback imported the owner's PERSONAL skattekonto
history from before the company existed. Those rows can never be
booked (no fiscal period covers them), never deleted (external
mirror), and had no ignore path: visible forever.
- syncSkattekonto now bounds the fetch at the company's earliest
fiscal_periods.period_start (new getEarliestFiscalPeriodStart in
period-service; no bound when no period exists yet). Applied
uniformly to EF and AB.
- New skattekonto_transactions.is_ignored column (migration
20260819080000, copies the transactions.is_ignored precedent:
CHECK that an ignored row has no journal_entry_id, partial index;
the existing company-scoped UPDATE policy already covers it) plus
PATCH /skattekonto/transaktioner/:id/ignore (409 on booked rows,
race-guarded on journal_entry_id IS NULL). Ignored rows leave the
default GET buckets; ignored_count is always reported and
include_ignored=1 returns the rows, surfaced as a count line +
"Ignorerade" band on /skattekonto and an Ignorera affordance with
confirm + Ångra on both /skattekonto and the /transactions inbox.
- PERIOD_LOCKED for a date before the first fiscal period now says
the row predates the company's bookkeeping and can be ignored,
instead of "lås upp perioden" (a dead end for those rows).
2. "Avdragen skatt" auto-mapped to 2710 for every entity type. For an
EF without employees that line is almost always A-skatt an outside
employer withheld from the owner's private salary, not the firm's
payroll liability. New data-driven skattekonto_rules.requires_employer
column (migration 20260819080100, set on the avdragen-skatt seed and
its per-company clones); the matcher gates such rules for an
enskild_firma unless company_settings.employer_registered is true
(the existing AGI gate signal, fetched in the same settings query).
Gated rows take the NO_COUNTER_ACCOUNT path with a distinct hint;
AB and employer-registered EF keep 2710 unconditionally. Regression
guard pins EF preliminärskatt to 2013.
The nightly sync upsert excludes is_ignored so it can never silently
un-ignore a row. New pg tests for the CHECK + RLS need a test:pg run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(skattekonto): clamp datumFrom to the SKV window, gate ignored rows, widen the employer signal
Review fixes on the EF-scoping PR:
- sync: clamp datumFrom to max(earliestPeriodStart, today - 555 days); a
bookkeeping start older than SKV's 555-day default is omitted entirely,
since sending it would widen the window past the default and anything
older than ~915 days fails the whole sync with felkod 2. The misleading
"no-op for AB" comment is corrected and boundary tests added.
- booking/match: an ignored row now throws a typed ROW_IGNORED error
(409) before any draft is created or link is written, in both
bokforSkattekontoTransaction and matchSkattekontoToEntry.
- page: the Nasta dragning / shortfall math re-includes ignored upcoming
charges (SKV draws them regardless of our ignore flag) while the
work-list buckets keep excluding them.
- employer gate: treat employer_registered ?? pays_salaries as the
signal (same fallback as lib/tax/deadline-config.ts), so an EF that
attested pays_salaries keeps 2710 for avdragen skatt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(skattekonto): assert the is_ignored RLS toggle inside the rolled-back transaction
withUserContext always rolls back (tests/pg/setup.ts), so the previous test
wrote inside it and read the pre-write value back on the pool connection:
it failed against a correct policy and would have passed against a missing
one only by accident. The assertions now live inside the same transaction,
pin rowCount=1 (an RLS-filtered UPDATE silently matches zero rows), and a
new test pins the negative: a non-member's UPDATE matches zero rows.
Falsification-verified against a real Postgres: dropping the UPDATE policy
makes both tests fail; with the policy they pass.
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>
|
||
|
|
c897a906df |
fix(ux): actions update lists in place - no more takeovers, jumps and dead air (#1629)
* fix(ux): update lists in place on actions instead of takeover spinners and jumps Founder report: the app feels glitchy when clicking around, especially when deleting a row or booking something. The repo-wide anti-pattern behind it: single-row actions trigger whole-list skeleton/spinner takeovers (layout collapse, scroll jump, full stagger-enter replay), deletes give zero feedback then hard-jump, and the /transactions exit "animation" was filter-only and never animated. Per surface: - Never take over a rendered list for a background refresh. The skeleton/spinner swap is now reserved for an empty (or foreign) list on /transactions (fetchTransactions), /pending (fetchOperations, covering both listed Granskning findings, one file), kundfakturor (fetchInvoices), leverantörsfakturor (fetchInvoices, plus try/catch/finally so a failed fetch can no longer stick the skeleton or masquerade as an empty register) and the verifikat list (JournalEntryList now takes a refreshToken prop and refetches in place; /bookkeeping no longer key-remounts it into a spinner, so expansion/selection/pagination/scroll survive a created verifikat). Quiet inline Loader2 cues near the list headers on /transactions and /pending signal a background reconcile. - /transactions row exit: exiting rows (booked/ignored/deleted) stay rendered through the existing 350ms window with a real exit transition (.row-exit: fast fade, then the space closes by transitioning cell paddings/line metrics and a numeric max-height on the fixed-height cell spans) and pointer-events off. Instant removal under prefers-reduced-motion. Applied to the inbox cards, the skattekonto card and the history rows. - /transactions delete: routes through processingId (row spinner) and the exitingIds path, and decrements totalUncategorizedCount when the deleted row was pending (the realtime echo is not guaranteed for DELETE on a filtered subscription). - FyPicker double-fetch: the initial fetch now waits for FyPicker's onReady (fires after its persisted-scope restore), so mount does one correctly scoped fetch instead of racing an unscoped fetch against the restore refetch (list -> skeleton -> list on every visit). Period changes refetch background-only behind the client-filtered list. - Pagination survives realtime echoes: background refreshes re-fetch range(0, pagedCountRef) instead of resetting to the first 200 rows, so "Visa fler" pages no longer collapse after any action. Gates: full vitest suite green (14764 passed), tsc output byte-identical to the origin/main baseline, eslint 0 errors on touched files, check:guards green, package-lock untouched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): apply review round on action-feedback smoothness - /pending: sequence-guard fetchOperations so a stale previous-tab response can't overwrite the current tab's rows, counts, or loading cues - /pending: check res.ok on the pending fetch and both history fetches before applying payloads; failures keep current rows and surface the existing error toast - /transactions: reset fiscal scope (fyReady/fyPeriodId/fyPeriod) during render on company switch so FyPicker re-runs its persisted restore and stale bounds never scope a fetch for the wrong company - /transactions: drop a deleted row's id from selectedIds so the bulk bar can't act on a deleted row - row exit: add the inert attribute on exiting row wrappers alongside pointer-events so keyboard focus and activation are blocked too - JournalEntryList: preserve selection on refreshToken background refreshes (reconciled against the refreshed page); user-initiated reloads still clear it 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> |
||
|
|
dea5e31756 |
feat(skattekonto): bulk Bokför + inline single-row booking (fewer clicks) (#1535)
* feat(skattekonto): bulk Bokför and inline single-row booking
Booking a year of skattekonto events took 6 clicks per row (list, Bokför,
review page, Bokför, confirm, navigate back), even for a +1 kr
intäktsränta row.
- GET /skattekonto/transaktioner now attaches a deterministic
booking_suggestion per unbooked row (one hoisted skattekonto_rules +
entity_type fetch via the new attachBookingSuggestions), shown as muted
text on the inbox row ('Bokförs mot 8314 ...').
- New POST /skattekonto/transaktioner/bokfor-batch (Zod, max 200 ids):
sequential draft+commit per row server-side (no orphan drafts), per-row
results, never aborts on a row failure. Commit attribution: bulk_accept
for real batches, user_accept for the one-row inline flow. A failed
commit keeps the linked draft (degrades to the old review flow).
- Inbox: hover-reveal checkboxes on eligible SKV rows (suggestion
present, no duplicate hint, unbooked, genomförd), separate skvSelectedIds
set, bulkbar 'Bokför valda (N)' with ONE summary ConfirmationDialog
grouped by suggestion with per-group sums, chunked batchProgress, ONE
aggregate toast, local state patch with exit animation.
- Single-row: new SkattekontoBookDialog (dynamic import) replaces the
draft-then-window.location detour on both /transactions and /skattekonto;
'Öppna som utkast' keeps the old draft path via router.push.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(skattekonto): batch booking guards for unsettled rows, double-post race and locked periods
- reject status != booked rows in the batch flow with NOT_SETTLED before
any draft exists (server no longer trusts client eligibility); the
single-row draft endpoint keeps its behaviour
- make the journal_entry_id backlink a conditional claim (update where
journal_entry_id is null, select affected rows): zero affected rows maps
to ALREADY_BOOKED and commitEntry only runs after a won claim, so two
concurrent submissions can no longer double-post the same row
- detect the period-lock trigger signature in the batch catch and map it
to PERIOD_LOCKED with Swedish text instead of UNKNOWN with raw DB output
- SkattekontoBookDialog: rows with no matched rule no longer get the
guaranteed-422 draft CTA; they route to the existing match flow and to
manual verifikat creation in /bookkeeping
- pass an explicit skv_book_dialog.commit_warning key for the
direct-commit warning instead of ConfirmationDialog's hardcoded default
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>
|
||
|
|
53e343ee92 |
Bug/invalid imports (#1146)
* feat: add Accounted MCP namespace * fix(bookkeeping): stop flagging verifikat whose underlag lives on a referenced supplier invoice The missing-underlag surfaces only accepted a document directly linked to the entry, so payment verifikat for supplier invoices (doc on the registration entry per design) and entries whose doc was pinned to the bank transaction before matching were falsely flagged; opening the entry showed the referenced doc and cleared the warning client-side, and it came back on reload. - verifikat_without_documents + transactions_without_documents now treat an entry as covered when a supplier invoice referencing it (registration or payment FK, or a supplier_invoice_payments row) carries a document anchored to a journal entry (BFL 5 kap 7 paragraf hänvisning till underlag; anchoring required because the WORM deletion guards key on document_attachments.journal_entry_id) - match-supplier-invoice routes (dashboard + v1) propagate the transaction's pinned document onto the payment verifikat, mirroring the categorize route; migration backfills rows already written (open unlocked periods, company-guarded, never steals a linked doc) - /api/documents/counts, the transactions-page badges, the bulk "Inget underlag krävs" count and the push-notification scheduler share the same reference-aware predicate, so every surface agrees with the RPC - counts route validates journal_entry_ids as UUIDs (they are interpolated into a PostgREST or-filter) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(transactions): align table columns flush with page edges Collapse the checkbox gutter column to zero width and hang the hover-revealed checkbox/expand chevron in the page margins, drop the outer padding so DATUM sits flush left and STATUS flush right, and tuck the overflow-menu dots under the middle of the STATUS header. Applied to both the inbox and history tables so they stay identical. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(arsredovisning): tie anlaggningstillgangar note to booked depreciation The ARL 5:8 roll-forward note recomputed depreciation from its own day-based linear formula (365.25/12 month length, non-inclusive day count, linear only), drifting ~20 kr per year per asset from the ledger-driven resultat- and balansrakning and misstating non-linear methods entirely. Note figures now come from posted depreciation_schedules rows (the same source disposeAsset reverses), falling back to the engine's computeAnnualDepreciation when nothing is posted; pre-onboarding opening balances iterate prior years through the engine. Adds a note-vs-trial-balance tie-out warning (accounts 1000-1299, over 1 kr) surfaced before download. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(stripe): move connect and sync surface from settings to import page Stripe's transaction feed is a continuous import source in the same category as the PSD2 bank connection, so its connect/sync surface now lives on the import page as a source card (mode=stripe), gated "kommer snart" on hosted like before; self-hosted keeps the full panel. - Import page: Stripe card after Koppla bank, renders the existing StripeSettingsPanel via the settings-panel registry - OAuth callback and panel cleanup return to /import?mode=stripe - Settings > Betalningar retired: nav item removed, route redirects, PaymentsSettingsContent deleted, legacy ?tab=payments mapped - New import.stripe_* strings in sv+en; dead settings_nav.payments removed Crons and sync logic unchanged; payment-link settings stay in the invoicing section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(underlag): paginate missing-underlag cron and harden doc-surface queries Resolve PR review findings on bug/invalid-imports: - notification-scheduler: fetchAllRows on all 5 global reads; past 1000 rows the capped reads produced false "saknade underlag" notifications - bulk-missing: LOOKUP_CHUNK 300->150 so the twice-embedded .or() id list stays under the PostgREST URL limit - bulk-missing + transactions page: UUID-guard the .or()-interpolated id lists, matching documents/counts - match-supplier-invoice (dashboard + v1): log documentId/journalEntryId on the non-fatal doc-link warning - well-known/oauth-protected-resource: document the tool_namespace allow-list - messages/en: reword stripe_description - DECISIONS.md: record the asset ibAck tie-out and Tailwind !important calls Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(tic): convert registrationDate from Unix seconds to millisecond epoch in lookup and profile tests --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
be9d630347 |
feat(home): concept Hem with Att göra + Fortsätt (UI migration PR 11) (#1132)
* feat(home): concept scene 14: greeting + Att gora/Fortsatt panes Hem becomes the founder-approved two-panel layout: serif time-of-day greeting with date and company, the Att gora worklist restyled to the concept pane (eyebrow header, h-rows with count chips, hover chevrons) and a new Fortsatt pane listing in-progress work derived purely from draft state (lib/worklist/resume: journal drafts, invoice drafts/unsent, mid-lifecycle salary runs; deadline boost, cap 3, tested). A completed flow can never render as a resume row by construction: only draft-state rows are fetched. KPI tiles, revenue/expense cards and the deadline/tax widgets leave the page per dev_docs/last_session_resume.md section 8, which also prunes their fetches (journal-line YTD aggregation, unpaid totals, deadlines): the page got faster. Banners, checklist, build-assistant hero and the Skatteverket nudge survive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(home): serif pane titles for Att gora and Fortsatt Founder feedback: the uppercase eyebrow headers read as a stray font. Both pane titles are now the Hedvig display serif (text-lg) over the hairline, matching the page's heading language; the band headers inside Att gora keep their small uppercase form as grouping devices. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(home): Geist pane titles for Att gora and Fortsatt Founder call: the pane titles use the body sans (14px medium), not the display serif. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): Geist section headers + drop stale-transactions chip Founder feedback: pane/section headers (Att gora, Fortsatt, the reports groups Lopande/Bokslut/Skatt & moms etc) render in Geist sentence case instead of uppercase eyebrows or serif. The global h1-h3 display-font rule moves into @layer base so utility classes like font-sans can actually override it (unlayered element rules beat Tailwind's layered utilities: this was silently eating the override). Also removes the 'N aldre an 14 dagar' chip from the Bokfora transaktioner row and its stale-count plumbing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * feat(ui): continuous nav crossfade + floating slide-over entrance The rail/full nav states now stay mounted and crossfade past each other (the inactive layer absolute, faded, nudged sideways, inert) while the aside width animates: the switch reads as one continuous motion instead of a DOM swap. The detail slide-over floats in from the right edge (slide-in-from-right-full, 300ms decelerating curve) per the concept, with a quicker ease-in exit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): make transitions and enter/exit animations actually run Two silent app-wide animation killers found while chasing 'the nav still is not smooth': 1. The codebase uses the shadcn animate-in/out vocabulary everywhere but no animate plugin was ever installed: Tailwind v4 silently dropped every such class, so popovers, dialogs, menus and the slide-over all appeared instantly. globals.css now defines the exact subset in use (accEnter/accExit keyframes + var-driven utilities), plugin-free, composing with duration/ease via --tw-duration/--tw-ease and collapsing under prefers-reduced-motion. Dialog drops its bracket-variant slide classes (zoom+fade carries the entrance). 2. The scrollbar auto-hide block's universal '* { transition: scrollbar-color ... }' was unlayered, and unlayered rules beat Tailwind's layered transition-* utilities regardless of specificity: every width/margin/color transition in the app was dead. The rule now lives in @layer base. Verified: the aside animates 248->64 over 300ms and the slide-over runs accEnter at 0.3s with the decelerating curve. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ui): finish the rr-mask session-replay masking sweep Main's #1105 switched one amount cell from the no-op sensitive-field class to rr-mask (rrweb's built-in text-masking class). The reskinned tables introduced more sensitive-field cells; all 12 occurrences now use rr-mask so financial amounts are masked in session replays. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
1ccf7ab81f |
feat(transactions): concept shell and row language (UI migration PR 5) (#1124)
* feat(transactions): concept shell for scene 10 (header, toolbar, footer)
- Header: title + Importera SplitButton (import guide / manual entry,
last-used mode persisted via ui_state.create_mode.transactions),
replacing the three-button row
- Toolbar in concept order: [Att bokföra/Alla seg with count chip]
[search] [Välj flera as quiet toggle] ... [source ContextPicker far
right merging the old in-list source dropdown (bank/Skatteverket)]
- Bank sync status + sync-now + Bankavstämning move to the concept's
footer status line together with the honest visible-row counter
("n att hantera")
- skv-reconnect banner and all dialogs/actions untouched
Row cards -> concept dry-table conversion follows separately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): concept row language for the inbox cards
Rows read like the scene 10 table: date as the first tabular column,
description on one line, amount right (+income in sage), auto-detected
invoice matches as beige suggestion chips instead of primary pills,
tighter hairline rows at 13px. Every action unchanged: Bokför pill,
match/assistant/overflow menu, batch checkboxes, exit animations,
extraction status.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(transactions): de-bloat rows and chrome to match the concept
- Rows carry only the essentials: date, description, amount, Bokför and
one overflow menu. The inline match and ask-assistant icon buttons
fold into the overflow menu (all actions still one click away)
- The "n nya banktransaktioner sen ditt senaste besök" banner is removed;
the footer sync line covers it
- The Skatteverket reconnect banner becomes the one-ochre-sentence
AttnLine with an inline action (convention 6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(ui): extract dry-table primitives into components/ui/dry-table
The concept table styles (TH/TD/VTH/VTD classes, quiet links, RowFoldout)
move out of JournalEntryList so the transaction inbox can render the exact
same table language.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): Synka bank nu in the Importera menu
Extracts the per-connection sync/reconnect logic into a shared useBankSync()
hook (BankSyncNowButton keeps using it) and adds the concept's first menu row
to the Importera split button: sync all active connections, with the
last-synced age as the row description. Hidden without a connection or the
bank_sync capability; the footer button stays the gated conversion surface.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): per-account source picker with balances
The context chip becomes the concept's account chooser: always visible in
inbox mode, one row per enabled cash account (PSD2 balance annotation),
an Övriga bucket for bank rows without a registered account, and a
Skattekonto row annotated with the cached saldo. The trigger reads
'Alla källor · <sum>' where the sum covers SEK ledgers + skattekonto.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): concept dry-table for the inbox
The inbox list becomes the exact Bokforing table: borderless dry-table with
hover-revealed checkboxes and chevrons, one-line rows (date, description,
amount, quiet primary pill + overflow + foldout), and a RowFoldout expansion
carrying the row detail and the full action set as quiet links. Skattekonto
rows render as chip-marked table rows with inline actions. The 'Valj flera'
toggle and the floating batch bar are replaced by the concept bulkbar that
pops in above the table once a row is selected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(i18n): singular form for bulkbar selection count
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): suggestion-first Bokfor flow (concept scene 10)
Bokfor on a row with a suggestion now goes straight to the compact
kontering confirm (QuickReview) showing the proposed verifikat; the full
template picker becomes the fallback and the 'Byt mall' path. Rows carry
the concept's 'Forslag: <mall>' chip (with D/K in the tooltip and the
foldout meta). The picker dialog is de-bloated: alternate paths (manuellt,
matcha, ignorera) collapse into quiet links. Modal scroll containers
(DialogContent, SlideOverBody) keep a visible scrollbar thumb: with the
app-wide auto-hide, a long dialog read as cut off at the fold.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(transactions): drop suggestion UI and foldout action links
Founder direction: no booking suggestions in the list for now (logic comes
later), and the foldout duplicated the overflow menu: the three dots are
the clearer surface. The foldout keeps row detail only (FX conversion,
original bank name, 1930/1630 hint, extraction status) and rows without
any detail no longer expand. The Skattekonto picker row drops its saldo
annotation; the Alla kallor sum covers bank ledgers only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(transactions): dry-table for the Alla view + pill chips app-wide
The history list joins the inbox's table language: Datum, Beskrivning,
Belopp, Status columns with Bokford as muted text + Visa verifikat quiet
link (normal state) and Ej bokford as the beige exception chip with an
inline Bokfor pill; the business/private tabs become the house seg and the
KALLA header row becomes the standard chip-picker. Skattekonto rows get
the same treatment. SEK conversion moves to a tooltip (one-line rows).
Badge itself becomes the concept chip: pill radius (99px), 11.5px, quiet
padding, replacing the boxy rounded-md look the founder flagged on the
Underlag saknas badge.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ui): audit round: viewer-disabled split options, touch fallback
Regression-audit fixes: SplitButton options support disabled+disabledTitle
so viewer-gated create paths render inert with the viewer tooltip instead
of silently no-opping (wired on Ny transaktion); the history list gets its
Kopplad till faktura indicator back; dead transactionsWithMatches memo
removed; and coarse-pointer devices now always show hover-revealed
controls (checkboxes, chevrons, quiet row actions) since touch has no
hover.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
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> |
||
|
|
2da9c71eb3 |
UI badge cleanup + /chart-of-accounts route + loop skills (#850)
Bundles three separable concerns: - style(ui): badge audit + cleanup across 45 files — real-status chips use Badge variants (raw Tailwind colors dropped), non-status count/type/label chips demoted to muted text, clustered badges consolidated; 20 unused imports removed. - feat(bookkeeping): Kontoplan moved to a dedicated /chart-of-accounts route (nav + command palette wired); /bookkeeping shows the journal list only. - chore(skills): loop-* automation skills + design-scan workflow under .claude/. fix(reports): restored the destructive count badge on blocking errors in the periodisk sammanställning (EC Sales List) — a genuine status cue the audit had wrongly flattened; flagged by the PR reviewer and Swedish compliance bot, now clean. All CI green; compliance bots report no findings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
32d9978f1b |
Fix/chrome pdf preview csp (#572)
* feat: add option to exclude year-end closing entries in SIE export and related reports * delete docs * fix: allow Chrome's PDF viewer in verifikat document preview The /api/documents/:id/inline route shipped with `object-src 'none'` in its CSP, which blocked Chrome's built-in PDF viewer (it renders inline PDFs via an internal <embed>). Users on Chrome saw "Det här innehållet har blockerats" when expanding a PDF attachment in the bookkeeping view; Firefox (PDF.js) and Edge (own viewer) were unaffected, and JPGs worked because <img> isn't subject to object-src. Drops the CSP for this route to the minimum needed for embeddability: `frame-ancestors 'self'`. X-Content-Type-Options: nosniff plus the fixed Content-Type from the handler already block MIME confusion; X-Frame-Options: SAMEORIGIN + frame-ancestors still block clickjacking. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(auth): add webmail deep link to email confirmation screens Mirrors Stripe's signup UX: after asking the user to verify their email, detect their webmail provider from the domain and show a button that opens the inbox in a new tab. Gmail gets a from:<sender> search pre-populated; Outlook/Yahoo/iCloud/Proton open the inbox directly. Unknown / custom domains fall back to the existing copy. Sender address is configurable via NEXT_PUBLIC_BRANDING_AUTH_EMAIL_FROM (default noreply@gnubok.se) so white-label installs can match their Supabase Auth SMTP config. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(auth): unblock first-time password set for BankID users with MFA Supabase rejects updateUser({password}) and mfa.unenroll with "AAL2 session is required" whenever a TOTP factor is enrolled. BankID magic-link logins produce AAL1, and middleware skips MFA enforcement for bankid_linked users, so they had no path to AAL2 — leaving them unable to set a backup password or disable MFA without going through the email-recovery escape hatch. - /api/account/password: branch on app_metadata.has_password. First-time set writes via service.auth.admin.updateUserById (no existing credential to protect, AAL2 guard does not apply). Change-password keeps the user-session updateUser so AAL2 still fires for credential rotation. - /mfa/verify: accept a safeReturnTo query param and route there after successful verify, so step-up flows can land back where they came from. - SecuritySettings: detect the AAL2 error from both change-password and mfa.unenroll and redirect through /mfa/verify?returnTo=/settings/account instead of toasting a dead-end error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Add tests and rounding utility for öre precision in bokslut calculations - Implemented `roundOre` function for rounding SEK amounts to two decimal places, ensuring consistent monetary calculations. - Introduced `ORE_TOLERANCE` constant for comparing rounded amounts, facilitating invariant checks in financial entries. - Created comprehensive tests for `roundOre`, covering typical cases, edge cases, and idempotency. - Added year-end invariants tests to verify database-level guarantees for closing entries, ensuring they balance to the öre and reject discrepancies. - Developed end-to-end tests for the dispositions chain, validating the correctness of calculations across various scenarios. * fix: update PDF rendering to remove Swish QR code generation and set default to disable Swish visibility * fix: enhance security by rejecting data URIs in safeReturnTo function tests * fix: improve rounding logic in roundOre function and add customer_type migration * fix: add customer_type column to customers and enforce CHECK constraint --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f8f49f8426 |
Inbox page-count gate + DataList/DropdownMenu primitives (#554)
* feat(inbox): skip AI extraction for multi-page PDFs (#553) Bedrock churns for minutes on multi-page PDFs (sales reports, bank statements, contracts) and returns nothing useful. Above 3 pages we now skip extraction entirely and mark the row with extraction_skipped=true; the document still lands in the inbox and can be attached or converted manually. Same gate applies to the /items/:id/attach path. Client can also opt out via skip_extraction=true (skip_reason=client_opt_out). The InvoiceInboxWorkspace renders an "Inte AI-tolkad" badge for skipped rows, distinct from the "Felaktig" failure state (status='error'). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(ui): introduce DataList + DropdownMenu primitives, roll out across list pages DataList replaces the per-row Card pattern across Granskning, Transactions, Invoices, Supplier invoices, and Pending. One bordered container with hairline rows matches the flat-with-hairlines aesthetic in CLAUDE.md — no shadows, no state-tinted borders, secondary token for selected/hover. DropdownMenu fills the gap for row-level action menus on TransactionInboxCard, TransactionHistoryList, and the page-level action menus on /transactions and /pending. Replaces ad-hoc Popover + buttons constructions. Migrates list pages and the transaction inbox/history components onto the new primitives. No behavior change beyond the visual unification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: add agent skills + gnubok domain skills, gitignore compliance reports .agents/skills/ + skills-lock.json + symlinks under .claude/skills/ check in the vercel-labs/agent-skills set pinned by the local skill manager (deploy-to-vercel, vercel-cli-with-tokens, react-best-practices, composition-patterns, react-native-skills, react-view-transitions, web-design-guidelines). Keeps the team on the same versions. .claude/skills/industry/ + .claude/skills/modifier/ are hand-authored vertical and entity-modifier skills for the specialized accountant agent — industries (konsult-it, e-handel, bygg-hantverk, reklambyra, saas-ai) and entity overlays (holding-ab, single-shareholder-ab-fmb, mixed- verksamhet). Project-owned content; lives in the repo by design. Also gitignores .compliance-reports/ — those are large generated SARIF/dossier artifacts from the compliance scanner. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(transactions): unify inbox/history chrome, drop swipe flow The transactions page mixed two-tier filtering, a swipe-view detour, and per-row Card chrome that didn't carry its weight. This pass collapses those into a single editorial-list surface and removes the unused swipe path entirely. User-visible changes: - Mode toggle (Att bokföra / Alla transaktioner) moved from a Tabs row under the header into a dropdown to the right of a unified search bar. Search now persists when switching modes. - Removed the swipe categorization view ("Gå igenom alla") and its trigger button. The 800-line SwipeCategorizationView component is deleted; suggestion-fetching shrinks to what the template picker still consumes. - Inbox rows now show one primary action: invoice/supplier-invoice match shortcut when auto-detected, else "Bokför". A new visible Link2 icon button opens the customer or supplier invoice picker manually (chosen by amount sign). Delete becomes a plain trash button — no overflow menu since it only ever held one item. - Bulk action bar swaps "Markera som privat" for "Ta bort" with a single combined confirmation. - Built SupplierInvoicePicker mirroring InvoicePicker so expense transactions can be matched to supplier invoices from the inbox. Wired through /api/transactions/{id}/match-supplier-invoice. - Template picker dialog renamed to "Bokför transaktion"; "Bokför manuellt…" and "Matcha med faktura…" promoted from muted ghost buttons at the bottom to outline buttons at the top, above the template list. - Breathing room: row padding py-3 → py-4, primary text text-sm → text-base, amount text-base, button heights h-8 → h-9, trailing gap-2 → gap-3 (in the DataList primitive itself, so every list benefits slightly). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: align package-lock.json with merged package.json The merge resolution took origin/main's package-lock.json (which dropped pdf-lib) but kept our package.json (which still requires pdf-lib for the invoice-inbox extension's PDFDocument import). `npm ci` rejected the mismatch. Regenerate the lock from the merged package.json so both files agree. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: regenerate package-lock.json with npm@10 for CI compat Local npm@11 produced a lock that npm@10 (CI) rejected with "Missing: @swc/helpers@0.5.21". Regenerated with npm@10 --package-lock-only so CI can install. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(transactions): supplier-invoice status-leak guard + drop dead prop Two follow-ups from the merge-risk audit: - SupplierInvoicePicker now mirrors InvoicePicker's status-leak guard: if a supplier invoice is still 'approved'/'overdue' but already has a payment voucher attached (journal_entry_id on supplier_invoice_payments), hide it. Closes a UX race window between payment and status flip. Partially-paid invoices still pass through. - Drop the unused onMarkPrivate prop on TransactionInboxCard and the matching handleMarkPrivate wrapper in the parent. Both became dead when the swipe-categorisation flow was removed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e4488a900b |
feat: add user locale preference to user_preferences table (#555)
* feat: add user locale preference to user_preferences table
- Introduced a new column 'locale' in the user_preferences table to store per-user UI language preferences.
- Added a CHECK constraint to ensure only supported locales ('sv', 'en') are allowed.
- Triggered a schema reload notification for the changes.
chore: declare CSS module support in TypeScript
- Added a declaration for CSS modules in globals.d.ts to enable TypeScript support for importing CSS files.
* feat: add Swish as an invoice payment method in company settings
|
||
|
|
980f29dae8 |
Bug/momsdeklaration skv (#449)
* fix(salary): show birthdate in masked personnummer, hide the 4-digit suffix Flip the personnummer display format from XXXXXXXX-NNNN to YYYYMMDD-XXXX so the sensitive 4-digit suffix is hidden while the (public) birthdate stays visible. Affects the employees list/detail, salary run, payslip PDF, payslip email, and the MCP server tools (list_employees, get_salary_run). Each call site now decrypts the stored personnummer before masking. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(transactions): allow deleting unbooked transactions from "Alla transaktioner" The history list only let users delete via the inbox card; once a category or mall was picked but the verifikation hadn't been created, the row showed "Ej bokförd" with no way to remove it. The API already permits delete while journal_entry_id is null, so the gap was purely a missing UI affordance. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(vat): populate ruta 20-24 for reverse charge + dishonest "Validera OK" Three connected issues caused Skatteverket to reject momsdeklarationer with FK004 even after our local "Validera"-knapp returned OK. 1. supplier-invoice-entries booked fiktiv moms (2614/2624/2634 + 2645/2647) on reverse-charge invoices but never the underlying basbelopp on 44xx/45xx. Ruta 30-32 filled up at SKV while ruta 20-24 stayed at 0 — SKV's FK004 ("silent netting prohibited", ML 13 kap kräver båda sidor). Fix: generateReverseChargeBasisLines in vat-entries.ts emits parallel 45xx/44xx debit + 4598 motkonto credit per rate group. Engine calls it from registration, cash, and credit-note paths. Skipped when the user booked the expense directly on a basis account to avoid double-counting. 4598 added to BAS reference (no migration needed; account_number is plain text on journal_entry_lines). 2. rutorToMomsuppgift rounded each ruta independently but computed summaMoms from the unrounded ruta49. SKV recomputes the sum from integer rutor on their side, so fractional öres caused ±1 SEK drift and SKV rejected with FK009. Fix: derive summaMoms from the already-rounded VAT-amount rutor. 3. "Validera"-knappen only confirmed SKV's internal arithmetic — a declaration with ruta 30-32 populated and ruta 20-24 empty validated fine until /utkast hit FK004. Users got a false green light. Fix: vat-declaration-checks.ts runs locally before the SKV call, blocks Validera/Spara when ERROR-level findings exist, and surfaces them in a separate "Lokala kontroller"-section. Success message reworded so SKV's OK is no longer presented as filing-ready. Tests: 4535/4536/4531/4425 lines + 4598 motkonto on EU/non-EU/byggtjänster RC, credit-note reversal, fractional-öres summaMoms, all four pre-flight codes (RC_BASIS_MISSING, RC_OUTPUT_MISSING, RC_INPUT_VAT_MISMATCH, SUMMA_MOMS_DRIFT). Backfill for already-posted entries follows in the next commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: add skattekonto matching functionality - Enhance TransactionInboxCard to display a warning for potential 1930↔1630 transfers. - Implement match suggestions for skattekonto transactions in the backend. - Create SkattekontoMatchDialog component for linking skattekonto rows to existing journal entries. - Develop SkattekontoInboxCard component to handle skattekonto transactions in the inbox. - Introduce skattekonto-match utility functions for candidate matching and linking. - Update types to include match suggestions and enriched transaction responses. * refactor: reorganize skattekonto types and implement bank counterpart matching logic * docs: update CLAUDE.md to streamline integrations and clarify architecture details * refactor: enhance reverse charge logic to handle non-basis accounts and prevent double-counting --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |