Commit Graph

5 Commits

Author SHA1 Message Date
Mattsson 4c76fb10d7 feat(transactions): "Ta bort underlag" detach action on a transaction (#2132) (#2144)
* feat(transactions): "Ta bort underlag" detach action on a transaction (#2132)

Wrong receipt pinned, no way back: the DELETE
/api/transactions/[id]/attach-document route and its tests already existed,
but nothing in the UI called it. This wires it up, frontend only.

- Inbox card and history list: "Ta bort underlag" in the row menu, shown only
  for writers on unbooked rows that carry a pin (canDetachDocument helper).
- Attach dialog: a small "Ta bort underlag" link beside the already-attached
  hint, the one place the app previously admitted a doc was pinned.
- Page: handleDetachDocument confirms (useDestructiveConfirm, warning), then
  DELETEs; 200 clears document_id in local state (list, dialog snapshot, and
  the inbox card's optimistic override via a -unlinked window event) and
  toasts; 409 renders the route's Swedish BFL message verbatim; other errors
  map through get-error-message.
- Strings under tx_detach in sv.json and en.json.
- Tests: gate hidden when booked / read-only / no pin / no handler; 409
  rendered unchanged; wiring and locale assertions.

Out of scope, follow-up: MCP detach tool (new pending-op type + CHECK
migration), detaching from the inbox for non-email docs, and clearing
invoice_inbox_items.matched_transaction_id on detach so the doc is offered
again by inbox-available.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YRXN5CqHrfuuDw5LLcSgTg

* fix(transactions): clear the inbox back-link when detaching underlag (#2132)

Skeptic finding on PR #2144: DELETE attach-document nulled only
transactions.document_id and left invoice_inbox_items.matched_transaction_id
pointing at the transaction. propagateUnderlagForBookedTransaction selects
on exactly that column at categorize / book / bulk-book time, so the
detached receipt would have been re-anchored onto the new verifikation as
immutable underlag (BFL 5 kap 7 §), and the doc never reappeared in
inbox-available for re-matching.

The route now clears the back-link for the detached document, scoped to
items not yet consumed by a verifikat (created_journal_entry_id null),
mirroring the invoice-inbox extension's unmatch. Best-effort like the POST
side: the pin removal is the primary effect. Three DELETE tests cover the
filters, the no-pin case, and a failing unlink. DECISIONS.md and the PR body
record the accepted bulk-booked-row limitation in the history list.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YRXN5CqHrfuuDw5LLcSgTg

* fix(transactions): detach reports a failed inbox unlink instead of success (#2132)

Swedish compliance review on PR #2144: the inbox back-link cleanup was
fire-and-forget, so a failed UPDATE returned 200 while leaving exactly the
stale matched_transaction_id that re-anchors a detached document onto the
next verifikation (BFL 5 kap 6-7 §).

The unlink is now scoped by transaction only (the unique index on
matched_transaction_id means at most one item points here, and a stale item
from the replace path would re-anchor just the same), runs even when nothing
was pinned so a retry is idempotent, and a failure answers 500 with an honest
Swedish partial-failure message, mirroring the POST side's propagation
failure. Tests updated accordingly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YRXN5CqHrfuuDw5LLcSgTg

* fix(transactions): release inbox back-link before a compare-and-set pin clear (#2132)

Review findings on PR #2144, one pass:

- CodeRabbit (major): DELETE cleared the pin and then released the inbox
  back-link scoped by transaction, so a POST landing in between could end up
  as "new doc pinned, its inbox item unlinked". The release now runs FIRST,
  and the pin clear is a compare-and-set on the document that was read
  (.eq document_id, or .is null when nothing was pinned). Zero rows answers
  409 "ändrades samtidigt" and keeps the newer pin. A failed release returns
  500 before anything changed, so a retry is trivially idempotent.
- Compliance swarm (A.8.15): the unlink failure log carried the raw driver
  error; it now logs errorCauseTag() only.
- CodeRabbit docstring check: JSDoc on handleDetachDocument.

Tests: order of the two writes, CAS filters for both pinned and empty
states, 409 on concurrent re-attach, coded-cause logging.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YRXN5CqHrfuuDw5LLcSgTg

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 00:29:22 +02:00
Jakob Wennberg 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>
2026-08-14 08:55:37 +02:00
Jakob Wennberg 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>
2026-07-04 15:58:06 +02:00
Jakob Wennberg b800dcd403 style(ui): system-wide UX/UI polish pass — design-system conformance + copy cleanup (#835)
* style(ui): system-wide UX/UI polish pass — design-system conformance + copy cleanup

Multi-agent scan of all 404 UI files against the locked design system, then
141 verified surgical fixes across 109 files (net -32 lines):

- Remove forbidden elevation/motion: shadow-* and rounded-xl on cards, active:scale
  bounce, hover:shadow on list items, transition-all -> transition-colors.
- Drop font-medium from single-weight Hedvig display headings/numerals.
- Replace raw rainbow Tailwind status colors with Badge variants / brand tokens /
  neutral surfaces (achromatic chrome, semantic colors stay data-only).
- Route raw dates through formatDate(), hand-rolled currency through formatCurrency(),
  add tabular-nums to financial figures; text-gray-* -> text-foreground tokens.
- Swap hand-rolled skeletons for the Skeleton primitive; off-scale spacing -> token scale.
- Fix copy: mislabeled "Leverantörsfakturor" -> "Utgifter" on bank-import outflow total,
  collapse no-op identical-branch ternaries, broken Swedish diacritics (mojibake),
  correct mismatch-password toast, correct supplier currency-field label.
- Remove PII-leaking debug console.log on register, stray console.logs.

Verified: tsc clean on all changed files, eslint clean, production build passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(auth): sanitize residual error logs in register flow

Follow-up to PR review (compliance swarm V16 / GDPR Art.5(1)(f)): the
remaining console.error calls in the register flow passed raw error
objects, which Supabase may populate with PII (email) in nested fields.
Log only sanitized message strings instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 13:14:13 +02:00
Jakob Wennberg 0521c385d2 feat(transactions): underlag status badges + attach dialog; auto-expire stale pending ops (#712)
* feat(transactions): per-row underlag status + attach-document dialog

- New "Matcha mot underlag" dialog on /transactions (inbox pick or fresh
  upload), the tx→doc mirror of the Documents view's matcher
- Per-row Underlag/Underlag saknas badges on booked history rows, driven
  by computeJeUnderlagStatus — same posted-only, exemption-aware scope as
  the worklist count so badge and count never disagree
- attach-document route + commit dispatcher now propagate the doc onto
  the verifikation when the tx is already booked (BFL 5 kap 6 §), with a
  409 guard for docs consumed by a different verifikation, idempotent
  re-attach (no same-value rewrite under period lock), and an honest 409
  when the period-lock trigger blocks the propagation
- Booking-dialog doc links also pin the doc to the transaction row
  (first linked doc wins) via the link route's new transaction_id param

messages/{sv,en}.json also carries the strings for the pending-ops
expiry UI that lands in the next commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(pending-operations): auto-expire stale staged operations after 30 days

- New daily cron (02:30 UTC, vercel.json + both docker crontabs) flips
  >30-day-old pending ops to rejected with the dispatcher's
  { auto_rejected: true, reason: 'expired' } result_data shape — rows are
  never deleted, the table is the audit trail
- /pending renders an "Utgick automatiskt" badge + detail line for these,
  orders terminal tabs by resolved_at so a fresh expiry sweep isn't
  buried, and adds a first-time-reviewer explainer
- Origin labels spell out where a proposal came from (AI chat, MCP key,
  API, cron) instead of the raw actor_label
- agent_chat actor type added to PendingOperationActorType/AuditLogEntry
  (DB CHECK already widened in 20260519090000) and to the agent filter
- ApprovalCard notes that ignoring a proposal is safe

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(mcp): surface the client telemetry marker in connect instructions

Tag the connector URLs shown in ApiKeysPanel, the connect-claude doc and
the gnubok-mcp README with ?client=<surface> (claude-connector /
claude-code) and GNUBOK_CLIENT=claude-desktop for the npm bridge.
Telemetry-only — the server already reads the param/header; this just
lets us measure which Claude surface connected.

The claude mcp add copy blocks quote the URL: an unquoted ? in the query
string trips zsh globbing ("no matches found").

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* review: fix stale-closure badge flip + zod-validate link route body (PR #712)

- handleDocumentAttached read journal_entry_id off the render-time
  transactions snapshot; if the list changed while the attach dialog was
  open the optimistic badge flip was silently skipped. Read it off the
  dialog's own subject (attachDocTx) instead.
- POST /api/documents/[id]/link now validates the body against the new
  LinkDocumentSchema (uuid-strict, all four fields) instead of a bare
  presence check on journal_entry_id — same canonical VALIDATION_ERROR
  envelope. Test fixtures switched to real UUIDs accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 11:13:51 +02:00