Commit Graph

16 Commits

Author SHA1 Message Date
null 1f6073823b test(e2e): harden control census against count-driven label churn
Two robustness fixes after the census flaked under full parallel probe
load (not a product regression — the failing diff was '# transaction' vs
'# transactions', pure pluralization):
- normalize collapses count-driven plurals so a data-count of 1 vs N no
  longer churns the snapshot (the census tracks controls, not the data in
  their labels)
- settle waits for the nav shell + 3 stable count samples (was 2), and the
  project retries 2x — deterministic snapshots mean a real control change
  still fails every retry while a load-flake passes

Re-baselined from a clean seed. Probe 33/33 stable across runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 07:14:05 -05:00
null 64ab37ae39 test(e2e): census gets its own project, ordered before the mutating probes
The census baselines were captured after api.probe had already created
its adversarial bills (Zero/Big/<script>) in the scratch DB, so snapshot
content depended on spec ordering — the first census diffs were probe
artifacts, not product changes. Now: a dedicated 'census' project
(read-only, pristine seed data) runs via project dependencies before
'probe' (api.probe/webauthn mutate state), and the default UI projects
ignore the spec. Re-baselined from a prepare-db-fresh DB; probe chain
33/33 across three consecutive runs; default e2e 27 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 22:23:35 -05:00
null 92439e74e6 test(e2e): automated per-page control census (Phase 7, IMP-CODE-07)
QA_PLAN Appendix E was never filled in manually — Cycle 1 signed off on
playbooks alone, which is how the WebAuthn ghost feature hid. This spec
enumerates every visible interactive control (role + accessible name) on
all 15 authed pages and snapshots the normalized, deduped, sorted list;
a new/removed/renamed control is now a reviewable diff every probe run.
Numbers normalize to '#' (dates/amounts/counts), and a self-stabilizing
settle loop samples the control count until steady (fixed waits raced
the data queries). Probe suite: 33 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 18:21:46 -05:00
null 522eaaf101 chore(deps)!: tailwind 3 -> 4 via @tailwindcss/vite (4i)
Official codemod (run in a clean worktree; docker-owned data/ blocks it
in-tree): tailwind.config.js inlined into client/index.css as CSS-first
@theme/@utility (config file deleted — v4 auto-detects content, which
also retires the .tsx-content-glob failure class from the TS migration),
33 template files' class renames applied, tailwindcss-animate loads via
the v4 @plugin bridge. Build runs through @tailwindcss/vite; postcss
config is now plugin-free. tailwind-merge -> 3 (v4 class model).

Visual-regression gate: only diffs were a ~2px global preflight shift +
the version string (baseline predated 0.41.1); re-baselined both login
screenshots in this commit after review. webauthn.probe confined to the
probe project (it enables WebAuthn for the seeded user, racing the
parallel UI projects' password logins).

e2e 27 passed, probe 18/18, PROD SMOKE PASS, typecheck/lint/tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 18:03:15 -05:00
null 88a32a983f feat(auth): passkey / security-key UI — WebAuthn ships end to end
The backend existed since 99abca9 with no way to reach it (QA-B1-01).
Now wired:

- LoginPage: requires_webauthn second step mirrors TOTP — the key prompt
  fires automatically after the password; cancel/retry + back-to-sign-in
- ProfilePage: PasskeySection (mirrors TotpSection) — enroll with a name,
  list keys, password-gated per-key remove + remove-all
- webauthnService: WEBAUTHN_ORIGIN stays authoritative; otherwise accept
  the browser's origin when its hostname equals the RP ID — fixes real
  dev/e2e enrollment where the Vite UI port differs from the API port
  (the browser already enforces RP ID ⊆ origin, so no widened trust)
- Deploy safety: WEBAUTHN_RP_ID documented in .env.example + a boot-time
  prod warning in utils/env.cts (localhost-bound keys silently fail
  behind a real domain)
- e2e/webauthn.probe.spec.js: CDP virtual authenticator drives the full
  lifecycle (enroll -> key sign-in -> password-gated removal), retiring
  Cycle 1's 'needs a human with a key' assumption. Probe suite 18/18.

Server 252/252, client 52/52, typecheck + build clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 17:30:14 -05:00
null f587d09d4a style: apply Prettier across client + server (no behavior change)
One-time repo-wide `prettier --write` (isolated commit so future diffs
stay reviewable). Mechanical formatting only.

Verified unchanged: typecheck + typecheck:server + check:server clean;
server tests 232/232; client tests 50/50; Vite build succeeds.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 14:23:53 -05:00
null f71b433323 feat(ui): refine OnboardingWizard, LoginPage, and SettingsPage 2026-07-06 12:22:47 -05:00
null abb9f7d443 fix(migration): repair server.js/.cts launch + require refs missed in the JS→TS migration
Full-deployment QA against a copy of the real DB surfaced launch/require
references the server .cts migration didn't update. All are non-source
tooling/edge paths, which is why typecheck/check/tests stayed green:

- e2e/setup/prepare-db.js: require('../../db/database') and
  require('../../scripts/seedDemoData') → .cts. This broke `npm run
  test:e2e*` entirely (harness couldn't load). [material]
- playwright.config.js + scripts/prod-smoke.sh: webServer/boot command
  `node server.js` → `node server.cts`. Also blocked the e2e + prod-smoke
  suites. [material]
- setup/firstRun.js → .cts, require('../services/auditService') → .cts,
  and server.cts call site → require('./setup/firstRun.cts'). Latent:
  server.cts only reaches firstRun when userCount===0, but database.cts
  auto-seeds a default admin during getDb() first, so the path isn't hit
  in normal boot — fixed defensively so it can't crash if ever reached.
- .env.example: doc reference `node server.js` → server.cts.

Verified: typecheck:server + check:server clean; server test suite
226/226; e2e probe 17/17 (was 0 — suite couldn't boot before); the
production Docker image builds and boots on a copy of the real
production DB (44 bills, 1159 payments) with /api/health 200 and all 64
page endpoints returning 2xx.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 12:05:40 -05:00
null 3ac0509e96 docs(qa): retarget QA_PLAN recon/census to .tsx after the TS migration
The living QA doc's copy-pasteable coverage-recon greps pointed at
client/App.jsx, Sidebar.jsx, <Page>.jsx and the B-UI primitive census
listed button.jsx/input.jsx/etc — all now .tsx, so the commands errored
('No such file'). Retargeted to .tsx (+ useAutoSave.test.ts) and noted
that recon greps target .tsx and 'npm run typecheck' is now a guard.
Behavior-only migration: client 42/42 + e2e probe 17/17 stay green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 11:06:15 -05:00
null 6a1b2f62b2 feat(data): two-pane shell + connection hero + deep-linking (Batch 1)
Rewrite the Data page shell into a settings-style two-pane layout (sticky goal
-nav on desktop, segmented on mobile) with:
- ConnectionHero — 5 states so a network blip is never mistaken for "not
  connected" and a server without SimpleFIN never shows a dead Connect button
  (loading / disabled / error+retry / not-connected / connected±needs-attention);
  Sync-now handles partial errors, 429, and failure with toasts.
- DataNav — <nav> landmark, aria-current, keyboard, responsive.
- ?section= deep-linking via useSearchParams (URL source of truth → localStorage
  → default; migrates the old 3-tab key), so refresh/back-button work.
- Goal-based regroup into 4 panes with plain-language titles/subtitles/icons
  passed via cardProps (every section component reused unchanged).
- Lazy panes: ImportSpreadsheet/ImportMyData code-split (own chunks) + only the
  active pane mounts; framer-motion cross-fade (reduced-motion aware);
  focus-to-heading on switch.
- Repoint BankTransactions "Open Data" → ?section=bank-sync; add /data to the
  authed axe sweep.

Build clean (heavy panes split into their own chunks); client suite 46 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 15:02:36 -05:00
null 5fe1f6499b test(qa): B-UI primitive behavior spec (dialog/select/disabled)
- e2e/b-ui.spec.js: functional checks axe can't assert — Add Bill dialog opens
  with a focus trap and Esc cancels with no bill created (Cancel = no side
  effect); the category Select opens by mouse and keyboard and lists options;
  the sort-direction button stays inert (disabled) in Custom order. Read-only,
  so safe in the parallel suite. Directly covers the B-UI batch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 22:03:52 -05:00
null ad474f1ac1 test(qa): admin/status authorization probe + B10/B11/B12 coverage notes
- api.probe: assert a regular user is 403 on /api/admin/*, /api/status,
  /api/about-admin (read + write) — B1/B11 authorization
- confirmed (static): settings PUT whitelists USER_SETTING_KEYS (no
  mass-assignment), notifications route splits requireAdmin/requireUser
- docs: mark B10/B11/B12 probed

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:48:05 -05:00
null 1bd282f47b fix(qa): Analytics "expected" gates by occurrence (matches Tracker/Summary)
- analyticsService: only add a bill's expected_amount in months it actually
  occurs (resolveDueDate), so annual / off-month quarterly bills no longer
  inflate the expected-vs-actual line every month (QA-B5-03, same root as B5-01)
- add a Tracker<->Analytics reconciliation guard to e2e/api.probe.spec.js
- docs: archive QA-B5-03; cycle log

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:23:37 -05:00
null a15ff056b3 fix(qa): Summary excludes bills not due in the month (reconciles with Tracker)
- routes/summary: filter the expense list by resolveDueDate so annual and
  off-month quarterly bills no longer inflate the monthly total / "monthly
  result" — the Summary now agrees with the Tracker for the same month (QA-B5-01)
- add a Tracker<->Summary reconciliation guard in e2e/api.probe.spec.js
- docs: archive QA-B5-01; track QA-B5-02 (SimpleFIN unpaid_this_month residual)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:19:35 -05:00
null 98c8fab176 fix(qa): resolve a11y nested-interactive on Categories & Snowball rows
- CategoriesPage: category rows are now a plain container with a dedicated
  chevron toggle button, instead of role=button rows nesting action buttons
- PlanStatusBanner: split the collapsible header into a name/progress toggle,
  sibling action buttons, and a chevron toggle (actions no longer nested in the
  trigger button)
- add e2e/categories.spec.js expand regression; all 8 authed pages now pass axe
- docs: archive QA-B14-02 to HISTORY v0.41.0; QA plan status/cycle-log

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:02:15 -05:00
null 029c227685 fix(qa): seed demo data amounts, bill amount validation, negative USD format, a11y aria-labels, Playwright E2E setup (batch 0.41.0 QA) 2026-07-02 20:36:09 -05:00