From c2f055b903f3c0c5be2432304e83911950aa3eef Mon Sep 17 00:00:00 2001 From: Jakob Wennberg Date: Wed, 26 Aug 2026 22:36:06 +0200 Subject: [PATCH] fix(bank): 90-day lookback is the default: long PSD2 requests make some banks kill the session (#1964) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit E2E #11: the account picker's fiscal-year default sent a 365-day request for a 405-day-old fiscal year; Swedbank answered by TERMINATING the session: zero transactions, connection expired, no error surfaced (initial_sync_requested_from 2025-08-26, returned min/max null). 90 days worked. - Default lookback mode is now 'fast' (90 days), labeled rekommenderas on a first connect; the fiscal-year option stays but carries an inline 'vissa banker avbryter kopplingen' note when its span exceeds 90 days, and the long-range helper names the real failure mode + the SIE/CSV path for older history. - SIE drop card: stage + arm in ONE click (three clicks was one too many): after the verdict, the single button reads 'Bokför: oåterkalleligt (BFL 5 kap 5 §)' and the deliberate click commits with confirmed=true. - Skill: never re-ask an answered question; when a connect card rendered, do not paste the URL as text too. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 --- .../components/AccountPickerDialog.tsx | 24 ++++++++++++++----- .../__tests__/sie-drop-widget.test.ts | 8 +++---- .../general/mcp-server/skills/onboarding.ts | 7 +++++- .../general/mcp-server/widgets/sie-drop.ts | 18 ++++++-------- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/extensions/general/enable-banking/components/AccountPickerDialog.tsx b/extensions/general/enable-banking/components/AccountPickerDialog.tsx index df970513..05f17ede 100644 --- a/extensions/general/enable-banking/components/AccountPickerDialog.tsx +++ b/extensions/general/enable-banking/components/AccountPickerDialog.tsx @@ -133,7 +133,12 @@ export function AccountPickerDialog({ const chartError = Boolean(chartLoadError) const [ledgerByUid, setLedgerByUid] = useState>({}) - const [lookbackMode, setLookbackMode] = useState('fiscal-year') + // Default 'fast' (90 days): the known-good PSD2 window. The fiscal-year + // default used to send 365+-day requests that some banks (Swedbank) answer + // by TERMINATING the session: zero transactions, connection expired, no + // error surfaced (E2E 2026-08-26). Longer ranges stay available as explicit + // choices with the risk spelled out. + const [lookbackMode, setLookbackMode] = useState('fast') const [customSubMode, setCustomSubMode] = useState('date') const [customDate, setCustomDate] = useState('') // Newest transaction date this CONNECTION has already imported (date null on @@ -161,7 +166,7 @@ export function AccountPickerDialog({ ) setSelected(initial) setSaveError(null) - setLookbackMode('fiscal-year') + setLookbackMode('fast') setCustomSubMode('date') setCustomDate('') lookbackTouched.current = false @@ -716,7 +721,10 @@ export function AccountPickerDialog({ className="mt-1" /> - Senaste 90 dagar (snabbt) + Senaste 90 dagar{!gapFill && (rekommenderas)} + + det längsta de flesta banker lämnar ut utan extra godkännande + @@ -734,6 +742,7 @@ export function AccountPickerDialog({ Sedan räkenskapsårets början från {settingsLoaded ? fiscalYearStart : '…'} + {settingsLoaded && daysBetween(fiscalYearStart) > 90 && ': vissa banker avbryter kopplingen vid så långa förfrågningar'} @@ -793,13 +802,16 @@ export function AccountPickerDialog({ )} {showLongRangeHelper && ( -

- Din bank returnerar oftast max 90 dagar. Behöver du äldre transaktioner kan du{' '} +

+ De flesta banker lämnar bara ut cirka 90 dagar utan extra godkännande, och vissa + (till exempel Swedbank) avbryter hela kopplingen vid längre förfrågningar: då hämtas + inget alls och banken måste kopplas om. Säkrast är 90 dagar här och äldre historik + via{' '} - importera via SIE eller bankfil + SIE eller kontoutdrag (CSV) . Vi visar exakt vad banken returnerade efter sparat val.

diff --git a/extensions/general/mcp-server/__tests__/sie-drop-widget.test.ts b/extensions/general/mcp-server/__tests__/sie-drop-widget.test.ts index 30c1fd42..582a277d 100644 --- a/extensions/general/mcp-server/__tests__/sie-drop-widget.test.ts +++ b/extensions/general/mcp-server/__tests__/sie-drop-widget.test.ts @@ -18,13 +18,13 @@ describe('SIE drop widget', () => { expect(widget?.html).toContain("addEventListener('drop'") }) - it('carries the approval in-card: two-click BFL confirm on the staged import', () => { + it('carries the approval in-card: stage+arm in one click, BFL text on the commit button', () => { const html = widget!.html expect(html).toContain("callTool('gnubok_approve_pending_operation'") - // High-risk approve arms confirmed=true only on the second, deliberate - // click: the human acknowledgment, never a default. + // confirmed=true rides the deliberate click on a button whose label IS + // the irreversibility statement: the human acknowledgment, no default. expect(html).toContain('args.confirmed = true') - expect(html).toContain('BFL 5 kap 5') + expect(html).toContain('Bokför: oåterkalleligt (BFL 5 kap 5 §)') }) it('passes exact bytes through tools/call with a sha256, never retyped or fetched', () => { diff --git a/extensions/general/mcp-server/skills/onboarding.ts b/extensions/general/mcp-server/skills/onboarding.ts index 36a6ce08..4bb9e2d9 100644 --- a/extensions/general/mcp-server/skills/onboarding.ts +++ b/extensions/general/mcp-server/skills/onboarding.ts @@ -58,6 +58,9 @@ Open with exactly three questions, together: 3. **Vilken bank har företaget?** (so the bank connect link later opens that bank's consent directly instead of a picker) +Never re-ask a question the user already answered in this conversation +(the opening round included): reuse the answer. + Then call \`gnubok_lookup_company\` with the org number. The registry answers most of the form; present the facts as a SHORT summary to confirm ("Jag hittade Example AB, Storgatan 1 i Stockholm, godkänd för F-skatt och @@ -151,7 +154,9 @@ reaches far enough back anyway. Call \`gnubok_connect_bank\` (pass \`bank\` from step 1 so the link opens that bank's consent directly) AND \`gnubok_connect_skatteverket\` in the same -turn; on claude.ai/Desktop both render connect cards with buttons. +turn; on claude.ai/Desktop both render connect cards with buttons. When a +card rendered, do NOT paste the URL as text too: the card button IS the +link, and duplicate raw URLs read as clutter. - Bank: BankID + PSD2 consent, then an **account selection dialog** in the browser: transactions start syncing when the user saves it. Banks cap diff --git a/extensions/general/mcp-server/widgets/sie-drop.ts b/extensions/general/mcp-server/widgets/sie-drop.ts index 8702657c..7dae6462 100644 --- a/extensions/general/mcp-server/widgets/sie-drop.ts +++ b/extensions/general/mcp-server/widgets/sie-drop.ts @@ -270,10 +270,13 @@ export const SIE_DROP_HTML = ` }).then(function(res) { const sc = parseResult(res); if (sc && sc.operation_id) { - stagedOp = { id: sc.operation_id, risk: sc.risk_level || 'high', armed: false }; + // Stage + arm in ONE step: the next click is already the deliberate + // BFL acknowledgment (three clicks was one too many, E2E #11). The + // irreversibility text on the button IS the surfacing. + stagedOp = { id: sc.operation_id, risk: sc.risk_level || 'high', armed: true }; el('import').disabled = false; - el('import').textContent = 'Godkänn bokföringen'; - note('Importen är förberedd. Godkänn här i kortet så bokförs verifikationerna, eller säg till i chatten.'); + el('import').textContent = 'Bokför: oåterkalleligt (BFL 5 kap 5 §)'; + note('Importen är förberedd. Bokförda verifikat kan inte raderas, endast rättas med storno. Klicka för att bokföra, eller säg till i chatten.'); } else { el('import').textContent = 'Import förberedd'; note('Importen är förberedd och väntar på godkännande i chatten eller i Accounted.'); @@ -289,12 +292,6 @@ export const SIE_DROP_HTML = ` }); function approveStaged() { - if (stagedOp.risk === 'high' && !stagedOp.armed) { - stagedOp.armed = true; - el('import').textContent = 'Bekräfta: oåterkalleligt (BFL 5 kap 5 §)'; - note('Bokförda verifikat kan inte raderas, endast rättas med storno. Klicka igen för att bokföra.'); - return; - } el('import').disabled = true; el('import').textContent = 'Bokför…'; const args = { operation_id: stagedOp.id }; @@ -307,8 +304,7 @@ export const SIE_DROP_HTML = ` }); }).catch(function(err) { el('import').disabled = false; - stagedOp.armed = false; - el('import').textContent = 'Godkänn bokföringen'; + el('import').textContent = 'Bokför: oåterkalleligt (BFL 5 kap 5 §)'; note('Godkännandet misslyckades: ' + (err && err.message ? err.message : 'okänt fel')); }); }