Files
accounted/.agents/skills/vercel-react-best-practices/rules/server-no-shared-module-state.md
T
Jakob WennbergandClaude Opus 4.7 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>
2026-05-22 10:12:21 +02:00

1.7 KiB

title, impact, impactDescription, tags
title impact impactDescription tags
Avoid Shared Module State for Request Data HIGH prevents concurrency bugs and request data leaks server, rsc, ssr, concurrency, security, state

Avoid Shared Module State for Request Data

For React Server Components and client components rendered during SSR, avoid using mutable module-level variables to share request-scoped data. Server renders can run concurrently in the same process. If one render writes to shared module state and another render reads it, you can get race conditions, cross-request contamination, and security bugs where one user's data appears in another user's response.

Treat module scope on the server as process-wide shared memory, not request-local state.

Incorrect (request data leaks across concurrent renders):

let currentUser: User | null = null

export default async function Page() {
  currentUser = await auth()
  return <Dashboard />
}

async function Dashboard() {
  return <div>{currentUser?.name}</div>
}

If two requests overlap, request A can set currentUser, then request B overwrites it before request A finishes rendering Dashboard.

Correct (keep request data local to the render tree):

export default async function Page() {
  const user = await auth()
  return <Dashboard user={user} />
}

function Dashboard({ user }: { user: User | null }) {
  return <div>{user?.name}</div>
}

Safe exceptions:

  • Immutable static assets or config loaded once at module scope
  • Shared caches intentionally designed for cross-request reuse and keyed correctly
  • Process-wide singletons that do not store request- or user-specific mutable data

For static assets and config, see Hoist Static I/O to Module Level.