CI ran lint with continue-on-error because 23 errors predated it. All are
fixed, so the flag is gone and a typecheck step is added beside it — nothing
else in CI compiles the test files, so a type error in a fixture could only
ever be found on a developer's machine.
Real fixes, no blanket disables and no suppressions:
- react-hooks/set-state-in-effect (6): each was "reset or seed state when an
input changes", so each becomes a state adjustment during render behind a
previous-value guard — the documented React pattern for it. Two were also
latent bugs. transactions/page.tsx depended on `others`, rebuilt by .filter()
every render, so the effect already ran every render, one commit late.
shared/page.tsx depended on the `tagIds` array identity, so the row window
was reset more often than "changing any filter" claims — including after
"Show more", the one interaction it must not undo; the key now joins by
value. csv-import-modal reads localStorage once at mount, which is a lazy
useState initializer, not an effect (SSR-guarded, though the modal is
mounted from client state and never prerenders).
- react-hooks/static-components (3): SortIcon was declared inside SharedPage,
making it a new component type on every render. Hoisted to module scope with
the sort state passed as props.
- @typescript-eslint/no-explicit-any (9): the dynamically imported route
handlers in two integration tests. Typed as their real signatures with one
`as unknown as NextRequest` cast at the fixture boundary, so the call sites
and response bodies get checked instead of opted out.
- react/no-unescaped-entities (5): exact-equivalent entities (', "),
so no rendered text changes.
Also fixes the one pre-existing tsc error (unit/order-ingestion.test.ts:511):
`options` is optional, and `?? 0` keeps the assertion failing on an absent
list rather than silencing it with a non-null bang.
9 warnings remain (no-unused-vars, exhaustive-deps). eslint exits 0 on
warnings, so lint blocks on errors only — deliberately, since none of the
remaining warnings should stop a push.
App-wide retheme done at the token layer: Tailwind's zinc scale is remapped
to warm ink/paper neutrals and indigo to copper in globals.css, so every
page inherits the palette. Fraunces (serif display) added for page titles
and hero figures; all figures now render in mono with tabular numerals.
Analytics page redesigned around a month spine — twelve clickable columns
scaled to each month's spend that act as hero, context, and period
navigation. Adds a 'What changed' top-movers panel vs the previous month,
replaces the 8-line category trend chart with per-category sparkline small
multiples, heat-tints the six-month ledger table, and restyles the Pareto,
pace chart, and cashflow strip. Kept: Pareto, cumulative-vs-typical pace,
drill-downs, regular/occasional split.
Insights and Merchants restyled to the same kit; chart tokens centralised
in category-colors.ts (CHART). Cleared the pre-existing lint errors in
insights (typed tooltip, removed any-casts).
- Add reconciled_with_id column to transactions (links manual → statement tx)
- CSV import wizard: 4-step modal (upload → map columns → review → done)
- Handles any bank format via column mapping with localStorage presets
- Single signed or separate debit/credit column modes
- Editable preview table before committing
- Auto-tags all imported rows with 'csv-import'
- Batch reconcile page: shows all unreconciled manual transactions with
potential statement matches (date ±3 days, amount ±1%) pre-fetched
- Select matches across multiple rows, apply all at once
- Copies overrides/tags/splits from manual → statement tx atomically
- Manual tx marked reconciled (linked), hidden from main transactions view
- Transactions with no matches shown separately
- Import CSV button on transactions page
- Reconcile nav item in sidebar