Classification preferences gains "Classify newly imported transactions with
AI", stored as classify_on_import in config.toml and on by default, so existing
configurations keep their behaviour. It covers CSV imports and bank
synchronization alike.
With it off, no import path contacts the provider: classification falls to the
new provider-free rules path, where an opted-in merchant rule still applies its
category and tags, an alias match still attaches its merchant, and everything
else arrives on the editable fallback without a provenance error that would
suggest the provider had failed. Analyse remains available on demand.
CSV import is now mapping-driven: N26, ING (metadata preamble, Windows-1252,
German decimals) and Kontist exports are recognized locally, and any other
layout can have its columns proposed by the configured model from a sample in
which letters are replaced by x and digits by 0. Proposals are untrusted: every
column must name a supplied header, money must come from one signed column or
one debit/credit pair, and formats must be from a closed list.
Uploading no longer imports. /api/import is replaced by prepare/confirm/cancel:
prepare parses, deduplicates and previews the exact facts, and only confirming
at the reviewed revision writes them. ING and AI-mapped facts carry no
transaction reference, because repeating SEPA mandate references must never
become a transaction identity.
Kontist authorized but shared no accounts: psu_type was hardcoded to
personal, and Enable Banking documents that a psu_type mismatch can
yield a consent without the expected accounts. The bank listing now
reports each institution's supported psu_types, the connect form offers
only those, the chosen type reaches POST /auth, and an unsupported
combination is refused before the user is sent to a bank. The choice is
stored per consent so reconnecting reuses it; consents predating the
choice stay personal.
Also repairs the frontend derivation, which the Montserrat dependency
broke: npmDepsHash was stale and web/public was missing from the
fileset, so the traced duck icon never reached the built assets.
The sidebar brand mark was a hand-drawn inline path. Replace it with the
delivered vector trace, served from the Vite public directory so the Go
binary embeds it and the mark stays editable as an asset rather than as
JSX. Its eye and chart bars are evenodd cutouts, so they read as the
navy sidebar showing through.
Set the wordmark in Montserrat Bold and the tagline in Montserrat
Medium at 0.4em tracking. The subsets are bundled from @fontsource
instead of linked from Google Fonts: the Content-Security-Policy allows
fonts from 'self' only, and a self-hosted workspace should not fetch its
own brand from a third party. Tracking moves from 2px to em so it holds
at every breakpoint's tagline size.
Three defects made new connections silently vanish while removed
accounts returned:
- A single shared account the journal cannot represent (securities or
card entries without IBAN, stable identification or currency) aborted
the entire consent. Usable accounts are now linked and the rest
counted and reported.
- A consent that linked nothing was stored, redirected as success and
later reaped by session recovery. It now fails with the reason.
- Callback failures rendered a bare JSON error page and were never
logged. They now log and redirect into the app with the reason shown.
- Deleting an account left its session binding, so the next connect or
sync recovered the binding and re-added the account. Account deletion
now releases bindings, consents and cursors before committing.
GET /api/banking/institutions lists the banks Enable Banking can connect
for a country (personal AIS, connectable consents only), with logos
restricted to https Enable Banking hosts to match the CSP image
allowlist. The connect form offers a filterable dropdown with bank
logos, falling back to the previous free-text input when the list is
unavailable or banking is not configured.
Manual history imports failed opaquely once a bank capped lookback on an
established consent (N26 rejects date_from beyond ~90 days with
WRONG_TRANSACTIONS_PERIOD). Backfill now requests the documented longest
fetching strategy, reports the coverage the bank actually provided, and
non-2xx responses surface allowlisted documented error codes instead of a
generic fallback. Dead-session codes map to reconnection. Failed syncs
retry hourly so a stale sync banner no longer persists for a day.