fix(bank): 90-day lookback is the default: long PSD2 requests make some banks kill the session (#1964)
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5
parent
53c4d0d194
commit
c2f055b903
@@ -133,7 +133,12 @@ export function AccountPickerDialog({
|
||||
const chartError = Boolean(chartLoadError)
|
||||
const [ledgerByUid, setLedgerByUid] = useState<Record<string, string>>({})
|
||||
|
||||
const [lookbackMode, setLookbackMode] = useState<LookbackMode>('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<LookbackMode>('fast')
|
||||
const [customSubMode, setCustomSubMode] = useState<CustomSubMode>('date')
|
||||
const [customDate, setCustomDate] = useState<string>('')
|
||||
// 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"
|
||||
/>
|
||||
<span>
|
||||
<span className="block">Senaste 90 dagar <span className="text-muted-foreground">(snabbt)</span></span>
|
||||
<span className="block">Senaste 90 dagar{!gapFill && <span className="text-muted-foreground"> (rekommenderas)</span>}</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
det längsta de flesta banker lämnar ut utan extra godkännande
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
@@ -734,6 +742,7 @@ export function AccountPickerDialog({
|
||||
<span className="block">Sedan räkenskapsårets början</span>
|
||||
<span className="text-xs text-muted-foreground tabular-nums">
|
||||
från {settingsLoaded ? fiscalYearStart : '…'}
|
||||
{settingsLoaded && daysBetween(fiscalYearStart) > 90 && ': vissa banker avbryter kopplingen vid så långa förfrågningar'}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
@@ -793,13 +802,16 @@ export function AccountPickerDialog({
|
||||
)}
|
||||
|
||||
{showLongRangeHelper && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Din bank returnerar oftast max 90 dagar. Behöver du äldre transaktioner kan du{' '}
|
||||
<p className="attn text-[12.5px]">
|
||||
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{' '}
|
||||
<Link
|
||||
href="/import?mode=sie"
|
||||
className="text-foreground underline underline-offset-2"
|
||||
>
|
||||
importera via SIE eller bankfil
|
||||
SIE eller kontoutdrag (CSV)
|
||||
</Link>
|
||||
. Vi visar exakt vad banken returnerade efter sparat val.
|
||||
</p>
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -270,10 +270,13 @@ export const SIE_DROP_HTML = `<!DOCTYPE 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 = `<!DOCTYPE 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 = `<!DOCTYPE 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'));
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user