fix(skatteverket): show a banner when the connection is dead (#1925)
* fix(skatteverket): make a dead Skatteverket connection impossible to miss A company whose SKV session had expired got no usable signal anywhere. /skattekonto/saldo answers 200 for a token already flagged needs_reconsent (it keeps the stale snapshot visible on purpose), so the page looked healthy on load, and the reconnect AttnLine lived inside the `data` branch: a company that had never completed a sync had no snapshot, so clicking "Synkronisera nu" set the reconnect message and rendered absolutely nothing. The 401 branch returns before the toast, so the click was a silent no-op. On /transactions the same banner was gated on the 'skatteverket' source filter, which most users never select. Skattekonto page: - probe /status on load through the shared skvStatusNeedsReconnect predicate, so the prompt appears before the user clicks anything - render the reconnect line at page level, regardless of `data` - swap the "Synkronisera nu" header action for "Anslut igen": the sync cannot succeed while the session is dead, and offering it is what made the failure look like nothing happening - give the expired case its own StartCard instead of the onboarding one, which reads as "you never set this up" to someone who did - drop the "klicka pa Synkronisera nu" hint while a reconnect is pending Transactions page: show the reconnect line on every source filter. Max one attn line per page is preserved: reconnect outranks the imported-rows line and the saldo shortfall, the latter because a shortfall computed from a stale snapshot must not outrank "the data is stale". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(skatteverket): show a banner when the connection is dead Cut back to a banner and nothing else, after three skeptics refuted the first cut. /skattekonto: the reconnect line lived inside the `data` branch of the saldo section, so a company with no snapshot (never completed a sync) saw nothing at all when the sync it just clicked died on an expired session. The 401 path returns before the toast, so the click was a silent no-op. Move the line to page level and probe /status on load so it also appears without clicking, since /skattekonto/saldo answers 200 for a token flagged needs_reconsent and nothing else in the payload reveals a dead session. /transactions: show the same line on every source filter. Nothing is hidden or removed while the flag is true: the saldo tiles, the Kronofogden line, "Synkronisera nu", the empty state and the shortfall warning all stay exactly as they were. Prod has 151 of 162 connected companies with every token dead, so a flag that hides anything hides it from nearly everyone, and the shortfall warning's action is the page's only route to the bankgiro and OCR. Also: a healthy probe clears a message left by an earlier failed sync (previously only syncNow's success path did, so a transient 401 kept the banner up until a remount), and the existing visibility refetch now covers the reconnect state so the banner does not survive the consent that fixed it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(skatteverket): guard status probe against stale responses A fire-and-forget /status probe from an earlier reload could resolve after a later one and overwrite needsReconnect with an outdated result, restoring the reconnect banner right after a successful sync. Each reload now bumps a probe sequence and a result is applied only while it is still the latest. Also puts the 2026-08-26 decision record in the required [date] decision: why format. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
4af7469523
commit
dcb5d329a3
@@ -1254,6 +1254,7 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
|
||||
[2026-08-26] No ratchet on direct requireAuth() calls in app/api: requireAuth() is the MFA (AAL2) guard withRouteContext itself calls, and .claude/rules/api-routes.md sanctions it for routes without a company context (onboarding, account, user prefs). The 20 remaining direct callers skip request ids and the canonical envelope, not MFA; migrating them is a consistency campaign, not a security fix, so it was not folded into the bypass PR.
|
||||
[2026-08-26] defer_invoice_booking (#967) now gates booking on every door, not just the dashboard: MCP send_invoice / mark_invoice_sent / create_supplier_invoice_from_inbox, v1 invoices send / mark-sent and supplier-invoices create, and the inbox convert route all checked accounting_method === 'accrual' and posted a verifikat at issue for deferred companies. All six now call booksInvoicesOnIssue() (lib/bookkeeping/booking-mode.ts), the same helper the dashboard routes use, so the setting has one meaning. No data repair attempted: vouchers already posted for deferred companies through these doors are legitimate entries and stay.
|
||||
[2026-08-20] The swedish-e-invoicing skill now names Upphandlingsmyndigheten as Sweden Peppol Authority across all eight files, not just the one that was flagged: the handover completed 1 July 2026 (regeringsbeslut Fi2025/01826) and the skill was written in future tense, so a partial fix would have left the atom internally contradictory and still pointed agents at peppol@digg.se. Four digg.se URLs were repointed to their verified 301 targets on upphandlingsmyndigheten.se; the fifth, DIGG Peppol testbadd, is a hard 404 with no redirect and no successor page at the new authority, so it was replaced with the SFTI Validex verification service (https://sfti.validex.net/) rather than left dead or guessed at. Historical attributions (Q4 2025 traffic statistics, the 0007:2021006883 Peppol-ID example) deliberately still say DIGG because they were accurate when published.
|
||||
[2026-08-26] A dead Skatteverket connection gets ONE ochre line, on /skattekonto (page level, no longer nested in the saldo branch where a company with no snapshot never saw it) and on /transactions (all source filters, reversing the 2026-08-14 filter gate): deliberately a banner and nothing else: no page takeover, no removed sync button, nothing hidden. Prod on this date has 151 of 162 connected companies with every token dead (97 of the 101 tokens touched in 30 days are needs_reconsent), because SKV personal sessions live 65 minutes and the nightly cron flags the row the next morning. At that rate a flag that HIDES anything hides it from nearly everyone, so the first cut of this change (reconnect StartCard replacing the page, sync button swapped for a reconnect link, shortfall and empty-state suppressed) was rejected by all three skeptics: it would have removed the saldo, the Kronofogden line and the page's only route to the bankgiro and OCR at exactly the moment the account is short. The banner is additive and stays honest at 93%. Known and accepted: /status is caller-scoped while sync is company-scoped (resolve-auth prefers a live row), so in a multi-member company a member whose own token died sees the line while the company can still sync. Left alone because prod has 3 such companies and the banner hides nothing; fix it by adding a company-scoped field to /status if that ever changes.
|
||||
[2026-08-26] Reference data (fiscal periods, cash accounts, settings, accounts, dimensions, templates, customers, suppliers, articles) moves behind SWR hooks in lib/reference-data with company-scoped keys, a server seed from the dashboard layout (periods, cash accounts, settings only; the chart of accounts can be hundreds of KB and is warmed lazily instead), explicit invalidateReferenceData() after writes, and a raw-reference-fetch ratchet in check:guards. Chosen over Cache-Control on the API routes (a browser HTTP cache would keep serving stale bodies after a mutate) and over Next 16 cacheComponents/partialPrefetching (38 of 81 dashboard pages are client components whose data lives in client fetches, so an app shell prefetch cannot carry it). Expected side effect: period.list and settings.get volume in the op-completed logs drops toward zero because those reads become browser-side Supabase selects; that is by design, not a broken route.
|
||||
[2026-08-26] AGENTS.md now defers to CLAUDE.md for every shared rule instead of duplicating it: the copy had drifted within weeks (no inline-rättelse path, cookie-first tenancy order, 100+ MCP tools). The four Codex-only constraints Emil added 2026-07-21 (erp-base staging-only migrations, prod writes and main pushes need his explicit approval, no local Docker) were kept in a labelled section rather than removed, because the erp-base Supabase project exists (ref pwxtzglxptnnvjrpixpg) and they describe his environment, not stale product facts; only the project-name spelling was corrected from erpbase.
|
||||
[2026-08-26] MCP tool counts in docs say "150+" (connect-claude, READMEs, rules, registry entry) instead of deriving the number from the tools array: lib/docs/content/connect-claude.ts is core code and core must never import from @/extensions/ (CI builds core with zero extensions), and an exact hand-written number (90+, 100+, 120) had drifted three times already. Regenerate by counting `name: 'gnubok_` in server.ts when the order of magnitude changes.
|
||||
|
||||
Reference in New Issue
Block a user