Part (a) of #2187. A twelfth worklist category, skattekonto_payment_due:
the earliest upcoming skattekonto charge whose sum exceeds the last synced
saldo, computed once in lib/worklist (server-side twin of the /skattekonto
page's Nästa dragning math) and rendered as one Betala row on Hem with the
shortfall, bankgiro 5050-1055, the OCR reference and the due date.
The row appears only when money has to move: a saldo that covers the charge
yields nothing, and no upcoming charge yields nothing. Ignored rows take
part, since Skatteverket draws them regardless of our flag. Without a
balance snapshot the full charge is the amount. Without an org number the
row keeps its bankgiro and date and drops the OCR.
No table, route or migration: /api/worklist/counts picks the category up
through getWorklistCounts, and Hem passes the computed row into the same
options wave as the expense payouts.
Part (b), a betalfil for the skattekonto payment, stays a follow-up: the
existing payment-file route is AGI-scoped.
Claude-Session: https://claude.ai/code/session_0179bdetHyofL6ATfQxB5wP5
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
An out-of-pocket purchase differs from any other receipt only in the
credit account, so the Underlag pane now asks one question for an
unmatched underlag (Företaget / Jag, privat / En anställd / Ingen ännu)
and books a privately paid receipt in place through POST
/api/expense-claims, replacing the "Andra sätt att bokföra" dropdown and
the deep link into the two-step wizard. The verifikat editor stays
reachable below as the escape hatch (BFL 5 kap 6-7 §).
The person owed surfaces in Att göra under a new Betala band, one row per
person (lib/worklist expense_payout, counted in the total and exposed to
agents through the attention resource). The Utlägg nav row is gated on
existing claims, the same hybrid gate as Körjournal, since the entry
point for a new utlägg is now the Underlag pane.
Claude-Session: https://claude.ai/code/session_01P8YsvPqjfGxGZUkGeBVUWQ
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* fix(mcp): explain the Claude-side steps after "Anslut till Claude" and tick the checklist on a real connection (#2133)
Lazy auth is by design: Claude lists the tools before any sign-in and the
first company-scoped call answers 401, which opens the Accounted sign-in.
Nothing told the user, so a "connected" status with an unanswered first
question read as a broken connection (Axel, Discord).
- Settings -> API & MCP: one sentence of expectation under the button, and
the step-by-step guide link moved from under two disclosures to directly
under the button.
- Docs (connect-claude / anslut-claude): new "What happens after you click"
section for Path A covering the connector dialog, the tools appearing
before sign-in, the first-call login + consent screen, "ask again", and
the "Required when the server asks" auth setting that only the manual
path mentioned.
- Hem checklist step "Anslut till Claude": deep link now carries
client=claude-connector like the settings button (claudeConnectorLink),
the footnote carries the same expectation line plus the guide link, and
the done-signal is an unrevoked api_keys row minted by the MCP OAuth
token route (OAUTH_MCP_KEY_NAME) instead of the in-app AI-profile flag,
which never meant "connected to Claude".
- Tests: claudeStepDone with/without a key row, deep-link snapshot.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7iJQwKiRTDWSMnRm4WM4L
* fix(mcp): correct consent-page claims, stop the completion PATCH loop, count OAuth keys past RLS (#2133)
Three skeptic refutations on PR #2147, fixed in one pass:
- Docs (EN + SV): the consent page shows the company active in the app and
pre-selects every scope for Claude's connector (founder decision
2026-08-26); it has no company picker and nothing to tick. Steps 3-4 of
the new section, the "Read-only by default" paragraph above it, the
sandbox note and the 10-minute test now describe Endast läs under
Behörigheter instead.
- Checklist completion: users with initial_setup_path NULL (skipped the
books question, then imported) hit the route's "Välj först hur du vill
komma igång" 400 and, with saving as an effect dependency, retried it
forever with a toast. completionPatchBody() records path=migration when
none was chosen, and a rejected PATCH is not retried within the session.
- hasMcpKey: api_keys' SELECT policy is company-scoped, so the user client
could not see companyless (NULL company_id) or archived-company keys and
the step stayed open for the user who had just connected. The head count
now runs through the service client with an explicit user_id filter.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7iJQwKiRTDWSMnRm4WM4L
* fix(mcp): surface a failed OAuth-key count and reserve the marker name (#2133)
CodeRabbit round on PR #2147:
- app/(dashboard)/page.tsx: a failed api_keys count answered count null,
which claudeStepDone read as "never connected". Throw to the error
boundary like the settings fetch does instead of guessing.
- app/api/settings/api-keys: reject a hand-minted key named
MCP-klient (OAuth) (400 VALIDATION_ERROR): that name is the marker the
Hem checklist reads as "connected to Claude", so a manual key with it
would tick the step without any connection. Test added.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7iJQwKiRTDWSMnRm4WM4L
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* fix(dashboard): offer bank connection in the all-clear Att gora state
When the Att gora worklist is all-clear but the company has no active
bank connection, the plain "Allt klart!" praised silence that was really
a setup gap: nothing flows in automatically. The empty state now says so
and offers "Anslut bank" linking to /settings/banking. Fetch errors
degrade to the ordinary all-clear copy, mirroring the emptyLedger rule.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oUS7VoKWMj3jqQmdnop6R
* fix(dashboard): reword no-bank hint so it holds for automatic non-bank feeds
Skeptic finding: a company on the Stripe transaction feed (nightly cron
into transactions) with no PSD2 connection would read "inga nya
transaktioner kommer in automatiskt" as a false statement. Reworded to
an invitation that is true for every cohort.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oUS7VoKWMj3jqQmdnop6R
* fix(dashboard): scope the no-bank hint to bank transactions
CodeRabbit: "nya transaktioner" could still read as covering non-bank
feeds; say "banktransaktioner" / "bank transactions".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011oUS7VoKWMj3jqQmdnop6R
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Hem was one ~33-query render behind a single fallback: the greeting waited
for the slowest worklist scan, and the request also paid a sequential
bank_file_imports read after the batch, a second scan of the suggested
matches (getWorklistCounts counted the same 200 rows the pane listed) and
an awaited stale-dismissal delete on the read path.
- page.tsx awaits only what the greeting shell and the redirects need
(settings, profile, agent profile, the Skatteverket flag); the notice
line, the setup checklist and the Att göra + Fortsätt panes are async
server components behind their own Suspense (hem-sections.tsx). RSC
streaming applies to client navigations too, so the greeting paints
first on every visit and each block fills in as its queries land.
- DashboardContent becomes the shell with three slots; HemNotices keeps
the one client-side action (the wrong-account sign-out); HemSkeletons
are the two fallbacks.
- getWorklistCounts accepts the suggested matches the caller is already
fetching (a promise, so it stays parallel); listSuggestedMatches runs
once at the scan cap and the pane shows the first five.
- countInboxDocuments runs its id chunks in one wave instead of N
sequential round trips.
- getCompanyNotices takes deferReap; Hem passes Next's after() so the
stale-dismissal delete runs after the response.
- bank_file_imports joins the checklist section's batch.
Tests: worklist aggregate (precomputed matches skip the rescan), notices
aggregate (deferReap receives the reap; the delete does not run inline
and runs when the task does).
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>