package.json said 0.40.0 while HISTORY.md's shipped release was v0.41.0, so
/api/version, the release-notes badge (has_new_version), and the update
checker all reported the wrong version. tests/versionSync.test.js now fails
the suite whenever the two drift again.
(QA-B0-03)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
De-risks the flagship. The app is synchronous (better-sqlite3) but Kysely
executes async, so db/kysely.cts uses Kysely as a TYPE-SAFE BUILDER only:
build the query (columns + user_id/deleted_at scoping + types checked at
compile time), `.compile()`, then run via sync helpers (all/get/run) on the
shared connection. No async ripple, no change to the sync model.
- Schema typed for `categories` (extend as adopted).
- tests/kyselyPoc.test.js proves byte-identical rows vs the raw SQL and
that compiled SQL is parameterized (injection-safe).
- Pattern documented in CODE_STANDARDS; adopt for new/refactored queries;
it type-enforces user-scoping (the biggest data-isolation win) and
removes Row=any at the call site.
typecheck:server + check:server clean; 248/248 server tests.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A financial app must prove its money math, not sample it. tests/
moneyProperties.test.js fuzzes utils/money.mts: toCents always integer;
dollars→cents→dollars == roundMoney (no drift); sumMoney is cent-exact;
integer cents round-trip; classic float-drift cases exact; invalid inputs
never yield a bogus number. 6 properties, all hold.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pillar A enforcement so standards can't drift:
- .forgejo CI now runs format:check + lint (client+server) + typecheck
(client) + typecheck:server, in addition to the existing check:server /
tests / build. Previously lint and typecheck were NOT run in CI.
- lefthook pre-commit auto-formats staged files (prettier) + lints;
pre-push typechecks. Auto-installed via the `prepare` script.
- Knip config (advisory `npm run knip`) for dead-code/unused-export
review; the design-system ui/** is ignored to keep signal clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Standardization foundation (Pillar A):
- Prettier (.prettierrc.json, .prettierignore) + `format`/`format:check`
scripts; style tuned to the house style (single quotes, semicolons,
2-space, printWidth 100).
- .editorconfig for editor-level consistency.
- Extend the flat ESLint config to lint server `.cts` (Node globals, TS
parser, "focused signal" rule set mirroring the client) so `eslint .`
now covers both halves; `lint` → `eslint .`.
- Wire `format:check` into the `ci` script.
`npm run lint` is green (0 errors; pre-existing intentional patterns kept
as advisory warnings to ratchet later).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the JS→TS server migration. server.js → server.cts (Node
type-stripping, no build step), and every launch/config reference now
points at it: package.json (main/dev:api/start/check:server) and the
Docker CMD.
scripts/seedDemoData.js is required at runtime by routes/user.cts, so it
crosses into the server runtime — migrated to .cts and its require of
db/database updated to the .cts path. The remaining scripts/*.js are
standalone dev/ops tooling (outside the runtime and the check:server
boundary); their stale extensionless requires of now-.cts modules were
repaired so they still run, and the PM2 ecosystem config entry point was
updated to server.cts.
Verified: 0 runtime .js remain in the server tree; typecheck:server and
check:server clean; 226/226 tests pass; `node server.cts` boots and
/api/health returns 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Continues the incremental server TS migration (after utils/*.mts and
paymentValidation.cts): converts 10 services to .cts (CommonJS TypeScript,
run natively via Node type-stripping — no build step), type-checked by
tsconfig.server.json.
Migrated: totpService, amountSuggestionService, encryptionService,
paymentAccountingService, statusService, aprService, driftService,
analyticsService, spendingService, billMerchantRuleService.
- types/db.d.ts: shared minimal better-sqlite3 Db/Statement types (the lib
ships none), reused across the migrated modules.
- Every require of a migrated service updated to the explicit .cts extension
(extensionless require does not resolve .cts) across routes / services /
db migrations / server.js / workers / tests — require-only changes.
- package.json: check:server find pattern now includes *.cts (it silently
skipped .cts before, so paymentValidation.cts had no node --check gate).
Behavior-preserving (types only). Verified: typecheck:server 0,
check:server 0, full server suite 226/226, real boot → /api/health 200.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Dockerfile: npm ci (reproducible, lock-pinned, matches CI) instead of npm
install; add a HEALTHCHECK against the /api/health liveness route.
- package.json: pin engines node>=22.18.0 — the server require()s .mts/.cts and
relies on Node's default TS type-stripping (unflagged only on >=22.18), which
was previously undocumented on a floating base image.
- .dockerignore: exclude .env / .env.* so a stray env file can't be baked into
the image by COPY . .
- docker-compose: set TRUST_PROXY=true (behind the reverse proxy, so Secure
cookies + client IP for rate-limit/audit are correct); flip CSRF_HTTP_ONLY to
true (SPA reads the token from an endpoint, so no JS cookie access needed);
remove the active default INIT_ADMIN_PASS; document TOKEN_ENCRYPTION_KEY (set
it to move at-rest secrets off the DB-resident key); drop obsolete version key.
Not included: TOKEN_ENCRYPTION_KEY itself — that's a secret to generate and
inject into the deployment env (the app self-migrates to the env-key scheme on
next boot); documented in compose + .env.example.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Delete unreachable in-file dialogs: MonthlyStateDialog block (setShowMbs
never called) and inline PaymentModal in TrackerRow + MobileTrackerRow
(setEditPayment only ever null; editing works via PaymentLedgerDialog).
- Delete fully-unreferenced files: tracker/EditableCell.tsx,
ui/confirm-dialog.tsx, ui/separator.tsx (+ orphaned @radix-ui/react-separator).
- Simplify OverdueCommandCenter dead branch (early-returns null when empty).
- Correct stale roadmap/FUTURE docs: tracker keyboard nav is implemented.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Establishes the server-TypeScript foundation and migrates utils/money →
utils/money.mts with branded Cents/Dollars (mirroring the client), so the
cents↔dollars boundary — the origin of the reconciliation bug class — is a
compile error for any typed server caller.
Approach (no build step): Node 25 runs .ts natively (type-stripping).
Migrated modules use the explicit-ESM .mts extension (the project is
"type":"commonjs", which disables .ts ESM auto-detection) and are required
from the CJS callers via Node's require(esm) — verified working. Infra:
tsconfig.server.json (allowJs + checkJs:false → incremental like the
client), npm run typecheck:server, check:server extended to .mts, wired
into ci. 28 require sites repointed to money.mts.
typecheck:server clean; full server suite 225 + e2e probe 17/17 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The maintained line, and it forwards rejected async handlers to the error
middleware natively — closing the Express-4 gap where a throw in an async
route became an unhandled rejection that hung the request (this is why no
throwaway asyncHandler wrapper was needed). Small surface: only three
bare-* routes needed the path-to-regexp-v8 named-wildcard form
(/api/*splat, /legacy/*splat, /{*splat}); no removed APIs in use, all
req.query uses are reads. Suite 225 + probe 17/17 green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
As files convert to TypeScript they must keep the react-hooks enforcement.
Added a .ts/.tsx config block using the typescript-eslint parser with the same
rules-of-hooks/exhaustive-deps/react-refresh rules; swapped core no-undef +
no-unused-vars (type-blind) for TS-aware equivalents. Verified 'npm run lint'
traverses .ts and flags issues there; 0 errors on the existing .ts files.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adopting TypeScript incrementally (the move off plain JS). Upgraded jsconfig ->
tsconfig with strict + noUncheckedIndexedAccess, but allowJs + checkJs:false so
every existing .js/.jsx keeps resolving and running untouched — only .ts/.tsx
get type-checked. Added 'npm run typecheck' (tsc --noEmit) and wired it into
'npm run ci'. Installed typescript 6 + @types/react/react-dom 19. Client-scoped
(Vite resolves the '@/' paths); the CommonJS server is a separate TS story.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Added babel-plugin-react-compiler to the Vite React plugin. The compiler
auto-memoizes components/hooks at build time, so manual useMemo/useCallback
become largely unnecessary going forward (existing ones are left in place —
harmless, and the compiler adds the rest). Safe here because the codebase is
rules-of-hooks clean (enforced by eslint-plugin-react-hooks).
Validated: production build succeeds and the e2e probe passes 17/17 with the
compiler on — every authed page renders axe-clean and Tracker/Summary/Analytics
reconciliation holds. (Build time ~2.2s -> ~6.2s from the compiler pass; the
runtime win is automatic memoization.) The compiler ESLint rule needs
eslint-plugin-react-hooks v6 — a future upgrade.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ESLint surfaced 6 errors, incl. real bugs invisible before:
- ImportTransactionCsvSection called importErrorState() without importing it —
its own error handler would throw ReferenceError on a failed CSV import.
- client/components/MobileTrackerRow.jsx was a dead duplicate (unused; the live
one is tracker/MobileTrackerRow.jsx) with undefined-setter refs → deleted.
- StatusPage dbOk had a dead '?? true' (constant boolean LHS) — restored the
intended default-true-when-unknown.
- MobileBillRow redundant !! in a ternary condition.
Lint is now 0 errors; wired 'npm run lint' into 'npm run ci'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was no linting at all — nothing enforced rules-of-hooks (conditional-hook
crashes) or exhaustive-deps (stale closures) across 125 client components/pages.
Added an ESLint flat config (eslint.config.mjs) scoped to client/, an 'npm run
lint' script, and the devDeps. First run: 0 rules-of-hooks violations (good),
6 errors + 13 exhaustive-deps warnings to work through next.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- scripts/prod-smoke.js + prod-smoke.sh: build, boot `node server.js` serving
dist/ against a scratch DB, and drive the real artifact with Playwright
(login + lazy routes) to confirm the vendor-chunk split loads in production
- npm run smoke:prod; passes green
- docs: B15 harness command + status
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace all Save buttons on the Settings page with debounced auto-save:
- useAutoSave hook: debounce with latest-payload-wins, flush() for blur,
pending-edit flush on unmount, status machine (idle/saving/saved/error)
with saved fading back to idle. Covered by 6 Vitest tests (fake timers).
- SaveStatus pill (framer-motion) in the page header and notification card
headers — Saving…/Saved/Save failed.
- Timing per control: toggles/selects/channel ~150-400ms; typed inputs
(email, URLs, grace period, drift pct) 900ms + flush on blur.
- Push token never auto-saves mid-type: saves on blur only, so a partial
token can never overwrite a working one.
- Notification cards no longer refetch parent settings on save (would
clobber in-flight edits under auto-save).
- Decision: no undo toast — settings are non-destructive and instantly
re-editable; undo would add noise without safety.
- vitest include now picks up .jsx tests; jsdom + @testing-library/react
added as devDependencies.
- Copy pipeline-report.py from Pipeline project into scripts/
- Update TOOLS.md and MEMORY.md to reflect workflow consolidation
- (includes all uncommitted v0.36.0 changes from prior session)
- Removed hasBoth, compact prop, 2xl:min-w-[700px], 2xl:hidden Last Month,
narrowed columns — back to original single-column tracker layout
- Removed 2xl:max-w-[2000px] from Layout/Sidebar/AdminShell/footer/nav
- Kept S badge in all 4 locations and bucket remaining/Done header
- Added mkdocs/ to .gitignore
- Bump v0.33.8.6 -> v0.33.8.7
- Layout, AdminShell, Sidebar, footer, mobile nav: 2xl:max-w-[2000px]
- Content area grows from ~1436px to ~1936px, enough room for
dual-bucket tracker at 2xl+
- Bump v0.33.8.5 -> v0.33.8.6
- New backfillDataSource export and POST route for 89-day history pull
- Auto-seed 89 days on first connect, 30 days for routine syncs
- sinceEpoch() replaced with sinceEpochDays(days) with explicit param
- Status page errorRow now filters AND status = 'error'
- Full status page redesign: colored top borders, card icons, section labels,
health banner with glowing dot, consistent spacing
- Bump v0.33.8.3 -> v0.33.8.4
- Sub badge (indigo) in all 4 locations, toggleable in Bills prefs
- SimpleFIN Sync card on Status page
- dailyWorker.start() now called on startup
- tracker.overdue_count uses real SQL query
- Status page accuracy: dynamic headers, Degraded state, worker last_error check
- Removed SimpleFIN prefix from Recommendations title
- Bump v0.33.8.2 -> v0.33.8.3