Home
Viably

CHANGELOG

What's shipped.

User-facing updates — new features, performance wins, fixes, and security hardening. Synced live from the engineering changelog on every deploy.

  1. v3.23.0

    June 12, 2026Post-June-6 financial veracity, calm UX, demo extraction, and landing refresh

    What shipped - financial veracity and cost-of-living accuracy

    • Security

      Financial veracity audit findings were remediated (#702).

      The June 11 financial audit found 24 calculation, formula, constant, and financial-data issues; the same release remediated or formally waived every finding. APR clamping now surfaces disclosures and preserves payday-loan negative-amortization behavior, fixed per-paycheck deductions no longer overstate part-time net pay, mixed legacy/component pay rules preserve scalar differentials, percentage components use stable bases, exempt rules stop accruing weekly overtime, and frontend rounding now mirrors backend half-away-from-zero money behavior.

    • Polish

      State tax and payroll constants were corrected for 2026 (#702, #736, #737, #738).

      Hawaii, Vermont, California, South Carolina, Wisconsin, Oregon, Maine, Minnesota, Massachusetts, Georgia, flat-tax states, MFJ schedules, HOH brackets, EITC earned-income approximations, and OBBBA overtime/tips disclosure all received either updated 2026 treatment or explicit waiver disclosure. Follow-up gates now verify tax vintage labels, COL tax approximation labels, and the financial audit ledger.

    • Polish

      Federal overtime and employee-side payroll taxes were modeled (#752).

      The federal qualified overtime-premium deduction now covers tax years 2025-2028 with caps, phaseout, MFS ineligibility, sunset behavior, and visible assumptions, while CA, NY, NJ, WA, and HI employee-side payroll programs now feed paycheck estimates through capped 2026 tables. The same hardening slice closed transport benchmark netting, APR clamp disclosure, and debt-bill aliasing follow-ups from the deep-dive audit.

    • Polish

      Resident local income taxes now feed paychecks and viability (#759).

      NYC resident tax and Maryland county/Baltimore City local income tax estimates now flow through the annual estimator, per-period paycheck adapter, paycheck tax profiles, PTO recomputes, viability targets, sensitivity analysis, alternate-ZIP comparisons, and income display rows. OH municipal taxes and PA EIT remain explicitly disclosed as unmodeled instead of being half-modeled.

    • Polish

      Cost-of-living transport and housing moved to truer source data (#709, #726).

      The transport fuel multiplier no longer double-dampens state gasoline-price differences, and HUD ZIP-level SAFMR data now replaces county FMR when a ZIP is SAFMR-covered. The committed SAFMR dataset covers 38,601 unique ZIPs, NYC 10001 now resolves through the ZIP SAFMR blend, and COL methodology moved through 3.7.0 and 3.8.0 with golden-test updates.

    • Polish

      Viability provenance and residual claims were reconciled (#701, #710, #723).

      Stale ACS/MIT/COL provenance-gap claims were re-verified against current code and turned into no-overclaim invariants, five previously unresolved viability findings were proven fixed and locked, and the obligations audit now executes under the heavy gate instead of self-attesting.

    • Speed

      Financial-context scale and warmup paths were hardened (#740, #748).

      ACS ZCTA rents warm at startup, and the financial-context cache uses Redis when `REDIS_URL` is configured while preserving the local single-worker fallback. Multi-worker startup now requires Redis so safe-to-spend context cannot drift across workers.

    What shipped - calmer dashboard, login, landing, and app surfaces

    • Security

      The calm UX gate became enforceable (#673, #686, #730).

      `audit:calm` now guards jargon, page-job headers, registry drift, KPI count, and single-CTA budgets. User-visible jargon was removed from recovery-lane, allocation, and income-floor pages; every money surface has a page-job contract; and Playwright webServer backend wiring was corrected for the calm KPI/CTA E2E budget.

    • New

      Dashboard hierarchy and notice arbitration shipped (#711).

      The dashboard now has one primary hero, a priority-ordered notice dock capped at two items, first-run notices collapsed into one slot, time-horizon labels on money figures, an inline stability explainer, and a true empty state for fresh accounts instead of fabricated-looking placeholder values.

    What shipped - Pay Rules Studio, public demo, and architecture

    • Polish

      Pay Rules Studio decomposition continued without behavior drift (#735, #743-#747).

      Contract templates, the component history rail, the default tax profile card, pay bucket sections, pay rule basics, and preview shift controls were extracted from the large pay-stack page while preserving template data, test IDs, and pay-rule behavior.

    • Polish

      Public demo seed code moved out of the giant router (#695, #749, #750, #753).

      Demo PDF writing, pay-rule component builders, static fixture data, and seed orchestration were extracted into `backend/services/demo/` modules. Fixture values, seeded persona anchors, route auth/reset guardrails, and public-session guardrails stayed unchanged while `backend/routers/demo.py` became smaller and easier to review.

    • Polish

      CSV shift imports now share one parsing contract (#755).

      Legacy `/api/import/shifts/*` preview and confirm endpoints route through canonical CSV import primitives for byte caps, row caps, formula uninject, shift identity, and fingerprints while preserving the legacy response shape. Upload caps are now aligned at 10,000 rows and 5 MB, and oversized uploads return HTTP 413.

    Fixes

    • Security

      Pay-stack and settings regressions were fixed (#654, #655).

      Audit-log rows missing an id now get stable keys, and pay-stack preview requests strip server-managed fields before sending payloads to the backend.

    • New

      Local and CI ergonomics were hardened (#646, #693, #707).

      The frontend production launch config moved to port 3001 to avoid dev/prod EADDRINUSE collisions, the backend pre-push gate was unblocked after source-count drift, and pure branch-delete pushes no longer run the heavy pre-push gate.

    • Polish

      Dead CSS and preview surfaces were removed (#682, #684).

      No-op `gpu-scroll` / `gpu-grid` classes and production-disabled design preview pages were deleted so the shipped app and audits track only live surfaces.

    Documentation and validation

    • Security

      Whole-app documentation caught up (#675, #687, #688).

      The comprehensive app spec and status specification now reflect remediated audit items instead of stale June 6 assumptions.

    • New

      Validation expanded with source contracts and golden locks.

      The release tranche added or updated financial, COL, dashboard, calm-gate, Pay Rules Studio, demo extraction, Redis cache, import/export contract, local-tax, and obligations tests. Representative verified gates included 455 backend financial tests, 232 frontend unit tests, 2,528 backend unit tests for SAFMR, 2,542 backend unit tests for demo fixture extraction, 2,562 backend unit tests in the import/export hardening train, 2,610 backend unit tests after local-tax propagation, and Webpack production builds where the Windows Turbopack symlink limitation still applies.

  2. v3.22.0

    June 6, 2026Viability financial-correctness remediation, real data provenance, and a backend API-contract, governance, and UI-elevation program

    What shipped — financial correctness

    • Polish

      Viability tax math now uses real 2026 figures (#619, #616).

      Real 2026 brackets and standard deductions landed for 18 progressive-tax states, Ohio's 0% first bracket is honored, the FICA wage base respects Section-125/HSA exemptions, and 2026 401(k)/HSA contribution caps are enforced. The dashboard net now includes refundable EITC and ACTC instead of dropping them, and `compute_taxes` no longer drops flat state tax for non-bracket states.

    • Polish

      The viability score became continuous and internally consistent (#620).

      The score curve is now continuous (no discontinuous jumps at band edges), the risk band can no longer contradict the score label, and alt-ZIP comparison honors the active benchmark mode and the user's bills instead of silently dropping them.

  3. v3.21.0

    June 3, 2026Monochrome design-system elevation, June security hardening, and query-layer TypeScript migration

    What shipped

    • New

      The app pivoted to a monochrome "Vercel" design language (ADR-015).

      Color classes across UI components were repointed to new design tokens, display headings were tightened app-wide, and typographic consistency (Vercel/Apple wording) was applied across authenticated and public surfaces.

    • Polish

      A staged UI elevation program (P1-P8) landed.

      P1 foundation scales + an OKLCH-even neutral ramp that de-tints the dark theme; P2 component-primitive parity (states, tokens, semantic variants, Vercel-tight radii, Sonner toasts, unified inputs, Spinner/Kbd/indeterminate Progress); P3 layout primitives, prose measure, and a fluid display-type system across authed pages and public heroes; P4 motion primitives (`AnimatedNumber`, `Segmented`) plus cohesive interaction motion, spacing rhythm, and tactile `.vb-tap` press on shared icon buttons; P5 chart palette aligned to the monochrome neutrals; P8 a visual-regression harness that locks the design system in. See `docs/design/ui-elevation-roadmap.md` and `docs/design/visual-regression.md`.

  4. v3.20.0

    May 31, 2026Mobile-first answer spine, route-audit closeout, and financial-trust hardening

    What shipped

    • New

      Answer-first route structure expanded across the app.

      Dashboard, shifts, paycheck, forecast, time off, bills, debts, budget, allocation, goals, household, income floor, recovery lane, snapshots, decisions, monthly review, and reports now lead with a direct answer and move proof/math into disclosure surfaces.

    • Polish

      Mobile money clarity became a route contract.

      Mobile proof summaries, command surfaces, tap-target polish, skeletons, load-error notices, and route-experience tests were added so primary money routes stay usable at phone widths.

    • New
  5. v3.19.1

    May 23, 2026Release feed recovery and changelog parser coverage

    What shipped

    • New

      Release parser accepts the newer changelog section taxonomy.

      `backend/services/changelog_parser.py` now treats headings such as Security, Runtime, Cost-of-living, Refresh pipelines, Documentation, Registries shipped, Real bugs fixed, High-impact gates, Refactor, Code-review triage, and P0-P3 as release-detail sections.

    • Polish

      Non-release sections remain excluded.

      Scope, Verified, Tests, and implementation notes can still exist in `memory/CHANGELOG.md` without being promoted into public release bullets.

  6. v3.19.0

    May 23, 2026Pay Rules Studio, four-bucket pay taxonomy, and paycheck-period accuracy

    What shipped

    • Polish

      Pay Rules Studio becomes the primary pay setup surface.

      Pay-rule editing moved out of duplicated Settings/Income panels and into the unified Pay Rules Studio workflow.

    • Polish

      Four user-facing pay buckets replace the earlier three-bucket language.

      Worked-shift pay is now explained as baseline pay, incentives, differentials, and premium pay/overtime so users do not have to reason in backend-oriented component names.

    • Polish

      Stack behavior copy is simplified.

  7. v3.18.0

    May 22, 2026Payday Safety spine, native iOS foundation, and evidence-first actions

    What shipped

    • New

      Payday Safety contract models and tests added.

      The backend now has explicit payday-safety DTOs/tests around the canonical safe-week contract so surfaces can share language without duplicating math.

    • New

      Dashboard actions became evidence-backed.

      Primary recommendations now lean on source facts and deterministic financial context rather than generic "smart recommendation" copy.

    • Polish

      Fast Start telemetry shipped.

      First-answer flows can track when a user reaches their initial Payday Safety answer, which helps measure whether onboarding is doing its real job.

  8. v3.17.0

    May 21, 2026Assumption-change notification refresh and mobile security hardening

    What shipped

    • New

      Financial assumption notification refresh job added.

      A backend refresh path regenerates affected financial notifications after core money assumptions change.

    • Polish

      Related notification UI updates shipped.

      The frontend can show refreshed/fallback notification states instead of silently showing stale assumptions.

    • Polish

      Financial notification fallback states improved.

      Notification surfaces now handle missing or pending refresh data more honestly.

  9. v3.16.0

    May 19, 2026P0-P3 stabilization, paystub proof, PyMongo Async migration, and release-gate hardening

    What shipped

    • New

      Safe-week financial context was centralized.

      The financial calculation path was refactored around a shared safe-week contract so dashboard, forecast, notifications, and related surfaces can agree on the same answer.

    • Polish

      Paystub reconciliation shipped.

      Users can compare predicted pay against actual paystub data, giving the forecast engine a proof-and-calibration loop instead of only estimates.

    • New

      Paystub guided actions and actuals application workflow added.

  10. v3.15.1

    May 18, 2026Security patch rollup after mobile-readiness release

    Security and reliability fixes

    • Polish

      Plaid disconnect revokes items and clears stored bank credentials.

      Plaid disconnect revokes items and clears stored bank credentials.

    • Polish

      Startup workplace repair avoids full collection materialization.

      Startup workplace repair avoids full collection materialization.

    • Speed

      Bootstrap auth cache checks include the user boundary.

      Bootstrap auth cache checks include the user boundary.

  11. v3.15.0

    May 18, 2026Mobile launch readiness, push infrastructure, instant-app performance, and architecture runway

    What shipped

    • Polish

      Browser push notifications shipped behind a VAPID gate.

      `services/web_push.py` and `routers/push_notifications.py` add `/api/notifications/push/*` subscribe, unsubscribe, and config endpoints. When `WEB_PUSH_VAPID_PUBLIC_KEY`, `WEB_PUSH_VAPID_PRIVATE_KEY`, or `WEB_PUSH_VAPID_SUBJECT` are unset, the API returns `enabled: false` with the missing fields so Settings can show an honest disabled state instead of a broken subscribe button.

    • New

      Notification scheduler skeleton added.

      `services/notification_scheduler.py` lays the groundwork for shift reminders, payday-arrival alerts, tight-week pings, and paycheck-landed notifications without firing production pushes yet.

  12. v3.14.0

    May 17, 2026May catch-up: security, performance, methodology, and dataset provenance sprint

    Security and deploy hardening

    • Polish

      Tenant-scoped pay rules.

      Shift create/update now rejects pay-rule ids that do not belong to the authenticated user, and pay-rule ids moved to full UUID values instead of short predictable ids.

    • Polish

      Stripe checkout ownership tightened.

      Checkout and portal return URLs now come from a server-side allowed-origin path instead of trusting a client-provided origin, and checkout status polling is bound to the authenticated user.

    • Security

      CSRF posture tightened.

  13. v3.13.0

    May 01, 2026Hackathon-judge audit: 15 P0/P1/P2 fixes + observability dashboard

    P0 — security + money math

    • Polish

      Committed-in-repo secrets rotated.

      `JWT_SECRET`, `ADMIN_PASSWORD`, and `RATE_LIMIT_BYPASS_KEY` were sitting in `backend/.env` as production-shaped values (64-char hex, 24-char random, specific bypass token). Every fork of the repo inherited the same JWT signing key — a token minted against one deploy validated against every other. Rotated all three to visually-obvious placeholders (`dev-jwt-secret-rotate-before-production-deploy`, `change-me-before-prod`, `viably_test_bypass_2026`). Extended `services/hardening.py` with a `_COMMITTED_DEV_SECRETS` frozenset; any production boot still carrying one of these values now hard-fails startup with a specific "matches a value committed to the repo" error. Dev/preview boots unchanged.

    • Polish

      Public Prometheus scrape + operator health leak closed.

      `/api/metrics`, `/metrics`, `/api/_health/cors`, and `/api/_health/auth-domains` were anonymous — any attacker could fingerprint process uptime, live `viably_audit_log_writes_total{action="auth.login"}` rates, and the full CORS allow-list without authentication. Bearer-gated with a new `METRICS_TOKEN` env var (constant-time compare, accepts both `Authorization: Bearer` and `X-Metrics-Token` headers so Prometheus's `bearer_token_file` config works without change). Dropped `app_env` from both health-endpoint responses — pure recon signal telling attackers whether a deploy had relaxed dev-mode rate limits.

  14. v3.12.0

    Apr 30, 2026DRY consolidation sweep: four shared registries + contract tests

    Registries shipped

    • Polish

      `lib/route-protection.js`

      protected URL prefixes + public-app paths. Previously hand-duplicated between `middleware.js` and `lib/auth-context.js`. `PUBLIC_APP_PATHS` gained `/changelog`, `/methodology`, `/trust` which fixed a logout-then-back-button redirect loop.

    • Polish

      `lib/personas.js`

      the 4 persona-landing records (slug, nav_label, SEO copy, card content, icon name). Consumed by `app/sitemap.js`, `app/for/[persona]/page.js`, `components/landing/PersonaCards.jsx`, and the landing footer. **Fixed a live 404**: the landing surfaced a `/for/teachers` card and footer link to a page that didn't exist; meanwhile `/for/prn` was a fully-built SEO page with zero inbound links (orphaned). Consolidation exposed both gaps; the contract test's drift-guard prevents recurrence.

  15. v3.11.0

    Apr 29, 2026Production reliability sprint: refresh-storm fix, mobile shifts overhaul, observability rollout, changelog parser fix

    Real bugs fixed

    • Polish

      Production refresh-storm (P0)

      . The `/api/auth/refresh` interceptor in `lib/api.js` had no cooldown — when a session genuinely expired, every concurrent 401 (totp/status, bootstrap, security-score, …) raced to refresh the moment the previous attempt resolved. Production logs showed sustained waves of refreshes hitting the auth_strict rate limiter into 429. Added a 30-second `refreshCooldownUntil` window: once `/auth/refresh` fails with 401/429/transport error, every subsequent 401 propagates immediately without retrying. Added `REFRESH_SKIP_PATTERNS` so background pollers (`/totp/status`, `/billing/status`, etc.) never trigger refresh on 401 in the first place. Verified: 20-call burst against `/totp/status` now produces exactly 1 refresh attempt (was 20).

    • Fix

      Settings → Security tab crashed with React #130

      . `RecentActivityPanel.jsx` imported `Activity` from `@phosphor-icons/react`, but that name was renamed to `Pulse` in v2.x — the bundled `undefined` rendered as JSX raised React error #130, which trips the global ErrorBoundary. CI never caught it because no spec mounted the Security tab in a production build. Fixed import + added `tests/e2e/10-settings-security-tab.spec.js` regression that asserts every panel `data-testid` mounts.

  16. v3.10.24

    Apr 26, 2026Critical bug fixes from deep-dive code review

    Fixes

    • Fix

      Iowa earner at $50k/year, single, before fix: state tax computed as ~$18,625

      Iowa earner at $50k/year, single, before fix: state tax computed as ~$18,625

    • Fix

      After fix: state tax = **$1,600.76** (verified)

      After fix: state tax = **$1,600.76** (verified)

    • Fix

      Pre-fix users would have seen take-home ~$1,400/month instead of ~$3,200/month —

      Pre-fix users would have seen take-home ~$1,400/month instead of ~$3,200/month — a 56% understatement

  17. v3.10.23

    Apr 26, 2026Critical bug fixes from deep-dive code review

    What shipped

    • Polish

      `<ErrorState onRetry={fn} />` — full-card retry surface.

      `<ErrorState onRetry={fn} />` — full-card retry surface.

    • Polish

      `<PartialDataNotice message="..." onRetry={fn} />` — inline amber banner for "se

      `<PartialDataNotice message="..." onRetry={fn} />` — inline amber banner for "section X failed but the rest rendered".

    • Polish

      All empty-state CTAs use `<button type="button">` (keyboard-reachable, focusable

      All empty-state CTAs use `<button type="button">` (keyboard-reachable, focusable).

  18. v3.10.21

    Apr 26, 2026Critical bug fixes from deep-dive code review

    What shipped

    • Polish

      Web Vitals

      LCP, INP, CLS captured via `PerformanceObserver`. No external dependency. Color-coded by Google's good/needs-improvement/poor thresholds. Cards show "unavailable" empty state if browser doesn't support the metric.

    • Speed

      API requests + client cache

      Total request count, cache size, hit rate (with raw hit/miss counts), eviction count.

    • Polish

      Top endpoints

      8 most-called paths in this tab.

  19. v3.10.20

    Apr 26, 2026Critical bug fixes from deep-dive code review

    Real bugs fixed

    • New

      `/planning` prefetched but doesn't exist.

      R16's `TOP_ROUTES` array in `(app)/layout.js` listed `/planning`, which has no Next.js page. Every dashboard mount fired a wasted `router.prefetch("/planning")` and a 404 for the page-data warm. Replaced with `/budget` (real route, high traffic).

    • New

      Unbounded `getCache` Map in `lib/api.js`.

      No size cap → long-running tabs leaked memory. Added `MAX_CACHE_ENTRIES = 250` with insertion-order LRU eviction (drops oldest 25% on cap-hit).

    • Speed

      Non-deterministic cache keys.

  20. v3.10.19

    Apr 26, 2026Critical bug fixes from deep-dive code review

    What shipped

    • Speed

      Sequential warm: 92-151 ms (cache hits at ~92 ms).

      Sequential warm: 92-151 ms (cache hits at ~92 ms).

    • Fix

      10× concurrent stress: 157-220 ms (matches R18, no regression).

      10× concurrent stress: 157-220 ms (matches R18, no regression).

    • Polish

      Prometheus exports `viably_bootstrap_cache_hits_total{backend="local"}` and `...

      Prometheus exports `viably_bootstrap_cache_hits_total{backend="local"}` and `..._misses_total{backend=...}` — verified live (`hits=7` after 7 cache hits).

Viably · Financial OS for shift workers

HomePricingSign in
  • Security

    Plain-language UI/UX follow-up landed (#703, #733).

    The full UI/UX and cognitive-load audit produced an anchor report, then Phases 2-4 tightened plain language, flow friction, and polish without changing underlying financial math.

  • Fix

    Login and public landing polish caught up (#667, #672, #751, #754).

    The login dashboard bounce was fixed, clipped landing-hero descenders were repaired, and the landing page gained the week-rail motif, GSAP scroll choreography, a focused hero motion layer, and phone-preview sheen so the public surface now reflects the current product instead of an older static story.

  • Polish

    Premium typography was stabilized after the serif experiments (#645, #649, #659, #662, #666, #670).

    The app moved to Figtree plus self-hosted IoskeleyMono, limited display serif use to safe editorial contexts, reverted sidebar serif navigation, and standardized money numerals on aligned mono figures.

  • Fix

    Import and export API contracts were hardened (#756, #757, #758).

    Legacy shift import preview/confirm now declare fixed response models and enforce auth through FastAPI dependencies, bills import moved auth into the same dependency pattern, and shift/bill/debt export auth now runs before handler bodies while preserving Pro gates and streaming CSV behavior.

  • Polish

    Settings, household, and shared helpers were cleaned up (#647, #651, #652, #661, #689, #742).

    Settings split into domain homes, the deleted Household feature left behind no orphaned typedefs, relocation obligations were mapped and browser-verified, duplicated frontend helpers were centralized, and visual status-pill helpers were shared.

  • Security

    Deep-review hardening landed across contracts, resilience, docs, and CSRF trust (#696, #698).

    The deep-review remediation train addressed financial honesty, API contracts, resilience, documentation, and CSRF/SameSite trust-chain follow-ups, then re-aligned stale backend validation tests so the verification suite matched current contracts.

  • Polish

    Outflow handling and advice got more honest (#621, #632).

    Bills absorbed by the benchmark are surfaced rather than hidden, heuristic cost-of-living advice is flagged as such, shift-gap guidance is computed on a typical-net basis (not best-case net), and the TY2026 refundable Child Tax Credit cap ($1,700, IRS Rev. Proc. 2025-32) is verified and documented.

  • Fix

    Financial-truth P0s and the viability cache key were fixed (#402, #412).

    Composition-level financial-truthfulness P0s were closed, and the viability cache key now incorporates the overtime field and material pay-rule inputs so changing them busts the cache.

  • Polish

    Composition-seam coverage and executable obligation verification landed (#618).

    A composed end-to-end viability golden plus COL/waterfall/sales-tax invariants now guard the full pipeline, the input cache key busts on ZIP/income/filing/household/bills/debts, and the obligation audit actually executes and records its verification commands instead of self-attesting.

  • Polish

    Cost-of-living inputs are now real, sourced data (#626, #631).

    Synthetic `acs_rent` was replaced with real ACS B25064 rent (25,751 ZCTA rows + 51 state medians), MIT Living Wage coverage claims were corrected to honest state-level, EIA natural-gas data was refreshed, and a provenance contract test now asserts that every displayed label matches its actual derivation.

  • Polish

    The viability frontend stopped fabricating numbers (#623, #431, #636).

    Compare and DTI surfaces render through `MoneyLabel` (no more fabricated `$0` / `0%` when data is missing), viability panels render an honest missing-state, an `analyze()` race guard prevents stale results from overwriting fresh ones, the category bar gained a screen-reader summary, and a display-contract tail surfaced absorbed bills and closed the remaining VF-F2/VF-F5 gaps.

  • What shipped — backend contracts and structure

    • Polish

      Backend routes moved to typed, injected API contracts (ADR-016, T5).

      Sixteen routers — dashboard, forecast, shifts, paycheck calendar, budget, time off, pay rules, goals, workplaces, household, shift templates, scenario, and the remaining T5 set (#607) — were migrated to `Depends(current_user)` authentication and `response_model` envelopes, each with new contract tests, plus active-session API responses are now typed (#580). Behavior is preserved (`extra="allow"` envelopes guarantee fields without changing payloads).

    • Polish

      The largest backend files were decomposed (ADR-004).

      `passkey.py`, `col_admin.py`, and `admin.py` were split into router packages behind thin shims, and `viability_engine.py` was decomposed in phases (dataset-meta, display, debt, and obligations seams extracted into `services/_viability_engine/`) with zero golden-test edits.

    • Polish

      Route pagination and document-save contracts were salvaged (#615).

      Useful in-flight work was recovered and landed as route pagination coverage and document-save ownership/contract checks.

    What shipped — governance and best practices (F-series)

    • Polish

      Local enforcement gates were restored and hardened (#401, #409, #435, #439).

      A local pre-push enforcement gate (F1) runs the fast lint/typecheck/unit subset (CI is billing-blocked), with the backend gate resolving the venv interpreter and a Windows Yarn fallback (#584).

    • Polish

      A strict-TypeScript ratchet was started and grown (#421, #556).

      Strict typing began (F4) and expanded to pricing and the query fetchers/persistence layer.

    • Polish

      Money-validation and contract coverage expanded (#403, #424, #426, #544, #560, #561, #586).

      Frontend contract validation, backend auth contract models, and a money-validation contract were added and extended across multiple endpoints (F2).

    • Polish

      Supply-chain and infrastructure governance landed (#407, #418, #420, #428).

      Supply-chain provenance gates, a Docker-digest Dependabot train (F10), and an IaC waiver documenting platform-owned provisioning (F8) were added.

    • Polish

      Repo-wide formatting and a coverage floor were enforced (#413, #551, #587).

      Prettier tooling plus a repo-wide format and `format:check` gate (F11) landed, alongside component + financial-store unit tests with a coverage floor (F7).

    • Polish

      Data-at-rest encryption was documented in the deploy checklist (#542/#550, F6).

      Data-at-rest encryption was documented in the deploy checklist (#542/#550, F6).

    • Polish

      Dependency drift was guarded (#488).

      A canonical requirements base plus a runtime-drift guard prevent silent dependency divergence (P2-6).

    What shipped — UI elevation follow-up (A/B/C tracks) and surface

    • Polish

      Design-system enforcement and performance budgets landed (#400, #408).

      A P8 design-lint enforces the `--vb-*` scales and a P7 lighthouse gate enforces CLS/LCP/TBT budgets.

    • Polish

      The A-track normalized iconography, radii, and motion primitives (#422, #430, #440, #443, #445, #433, #437).

      A shared `<Icon>` wrapper + iconography scale, exact radii migrated to `--vb-radius-*` tokens, `Segmented` (pricing) and `AnimatedNumber` (score heroes) adoption, and a client-navigation focus-to-`#app-main` a11y fix.

    • Polish

      The B-track extended contrast gates and data-viz (#447, #449, #456).

      A contrast gate now covers authed money surfaces (B2), shared chart primitives and DOM chart colors align to semantic tokens (B3).

    • Polish

      The C-track de-tinted the light theme to pure achromatic (#450/#451, #452).

      Residual `text-violet-500` was dropped from the populated trend-chart header.

    • Polish

      The frontend stack was consolidated.

      Icons standardized on Phosphor (dead `form.jsx` and `lucide-react` removed) and buttons consolidated onto the shared `Button` component.

    • Polish

      Typography was unified (#629).

      574+ hand-rolled labels collapsed onto `.vb-label`, 386 non-label sites moved to `text-xs`, a fluid type scale and a weight ladder were adopted, and a type-floor design-audit rule was wired into the pre-push gate (sub-floor and rem-ratchet counts now zero).

    What shipped — mobile, performance, and developer experience

    • New

      Mobile nurse UX was reintegrated (#604, #606).

      The mobile-first nurse experience was reintegrated, with a post-login dashboard handoff hint.

    • Speed

      Login and navigation latency dropped (#588, #591, #605, #609).

      Dev defaults to Turbopack with Sentry alias parity, login/sidebar route prefetching and cache priming plus a route-transition benchmark landed, and login latency / CSP hydration were fixed.

    • Polish

      Generated artifacts stay fresh (#589, #608).

      Generated OpenAPI/API typedefs and release artifacts were refreshed.

    • Polish

      Scenario analysis was brought back online (#638).

      The `/scenarios` LLM path resolves a provider via env fallback (Emergent → Gemini → Groq) and soft-grounds output (ungrounded dollar figures are redacted instead of returning a 503), with the AI-003 obligation preserved.

    • Polish

      Repo hygiene (#453, #475, #481, #489, #492).

      A root Flutter duplicate, generated marketing logs, root `backend_test.py`, and a dead smoke-workflow were untracked/retired, and `_calculate_shift_gross_legacy` dead code was removed (#436/#438).

    Fixes

    • Security

      Security hardening batch.

      Email verify-code uses an atomic attempt gate with constant-time comparison (#498); the reset-password token is stripped from the URL after capture (#539); tokens in the public-audit PDF link are escaped (#540); the Apple receipt bundle check hard-fails and accepts in-app purchases (#513); mobile web refresh tokens now rotate (#571); a stale CSP `connect-src` waiver was closed (#578); the audit log declares its auth dependency (#583); preserved session security hardening was promoted (#585); security boundaries were hardened (#404); and the account-deletion obligation was verified (#574).

    • Fix

      Concurrency and performance fixes.

      WebSocket Redis pub/sub commands are serialized against the reader loop (#509), distributed-cache transient failures use temporary backoff instead of permanent disable (#510), S3 objects are cached in `materialize_to_local_path` (#497), idempotency completed-response TTL is anchored to completion time (#522), and un-stamped in-flight jobs are no longer requeued as stale (#520).

    • Fix

      Financial correctness fixes.

      The money-rounding contract is documented HALF_UP (the inverted "banker's" comment was corrected) and `compute_taxes` net is floored at zero to match `estimate_net_from_gross` (#469); `reverse_gross_from_net` threads self-employment income (#523); the transport COL category key is unified (#521); anomaly baseline/current values are made consistent with the displayed delta (#537); COL freshness is anchored to true data vintage (#538); the essentials reserve is prorated by actual days with disclosure (#487); state-tax fallbacks and shift-window assumptions are disclosed (#477); the monthly payday anchor is preserved (#575); consecutive-weekend shift flags are backfilled (#581); capped pay-rule shift repoints are repaired (#582); and deduction overshoot is exposed in the pay trace (#579). The `money()` formatter regression in the pay-stack review was restored (#496).

    • Fix

      CRITICAL mobile regression closed (#535).

      The mobile offline outbox was passing a FastAPI `Depends` sentinel as the user, raising a `TypeError` and bricking keys; the auth dependency-injection path is fixed.

    • Polish

      Tail-round robustness (#566-#573).

      A payment-ledger immutability guard plus Stripe webhook dedup (#566); persona attribution bound to signup context, no cross-user pollution (#567); OCR quota reservation released on extraction failure (#568); backend robustness for ISO week, CSV cap, OCR original-size, and funnel lease (#569); edge hardening with `TrustedHostMiddleware`, `perf_metrics` cardinality bounds, and an nginx Cloudflare-IP fix (#570); and FE polish for `USCostMap` NaN, `ShareLinkModal` local expiry, and `AnimatedNumber` mid-tween (#572).

    • Fix

      Smaller correctness fixes.

      Funnel signups are attributed to a single persona (no cross-user double-count, #536); the goal Contribute action is guarded against double-submit (#511) with the goals contract subsequently restored (#534); the onboarding default date off-by-one (local date parts vs UTC) is fixed (#512); the Plaid connection `created_at` is preserved on re-exchange (#519); a feedback screenshot with a bad extension returns 400 not 500 (#524); silent except-pass blocks are logged and a background task holds a strong reference (#476); and reverse-gross preserves "unavailable" instead of fabricating `$0`.

    • Security

      Whole-app audit reconciliation (#593, #594, #610, #611, #612, #576, #577, #592).

      A multi-phase audit-remediation drive (error/cache, auth/offline, mobile-a11y, financial-display, light-charts, payroll-math, security-hardening, backend-tails) plus a round-two pass closed correctness, a11y/mobile, and test-coverage findings; verified financial audit residuals were closed and the audit backlog docs / suppression reasons reconciled.

    Polish

    Login background and landing hero were upgraded.

    A dark-first gradflow login shader (pinned `gradflow` 0.1.0, lazy + reduced-motion fallback) and an aurora effect on the landing hero shipped, with an `A11Y-MOTION-LOGIN` waiver documenting the animated-for-all-users decision.

  • New

    The onboarding tour was refreshed.

    Tour steps and interaction handling were updated to match the new design language.

  • Polish

    App-level device protection shipped.

    An app-lock feature with biometric authentication and Step-Up Authentication gained a Target ID so re-auth challenges bind to the specific sensitive action.

  • Polish

    The TanStack Query data layer migrated to TypeScript (L17).

    `lib/query/persistence`, `lib/query/client`, and `lib/query/fetchers` are now `.ts`, with global style and cache-life type definitions added.

  • Polish

    Worker queue reliability was hardened.

    A reliable processing-list queue, Postgres migration checksums, header-based export-truncation flagging, and streamed file-object uploads replace the prior best-effort paths.

  • Security

    The stack-maximization program was formally closed.

    Its implementation closure report was published (Task #375); status now lives in `docs/audit/stack-maximization-closure-report.md`.

  • Fixes

    • Security

      June security-hardening batch.

      Weak JWT secret is now fatal in production; scheduler lease TTL and paycheck-calendar sort guards added; route-limit bypass key handling reworked; recovery-code race closed; quiet-hours, demo seed/reset, and email-expiry paths fail closed; CSV import strips BOM and round-trips/dedupes correctly; path and image handling hardened.

    • Security

      Edge and infrastructure hardening preserved.

      Mongo socket timeout, Postgres pool, nginx/Vercel edge config, compose secret handling, idempotency race + atomic claim coverage, account-erasure registry, and mobile security hardening were carried forward with tests.

    • Polish

      Rate limiter degrades gracefully.

      It now falls back to in-memory storage when Redis is unreachable, with improved Redis tracker error handling.

    • Fix

      Financial correctness fixes.

      Goal required-monthly-contribution uses calendar months; payroll golden fixtures and zero-value-present handling added across pay structures; pay-stack functionality and query invalidation improved; the app-shell mode strip renders backend money labels instead of reformatting client-side.

    • Security

      Audit ledger reconciled.

      `PRIV-DELETE-001` downgraded to Implemented; deferred H-3 / queue / CSP items waived; demo reaper, cache fail-open, upload cap, and export-truncation flag landed on final files.

    Shared backend money display labels became the standard.

    Dashboard, Paycheck, Forecast, Shifts, Bills, Debts, Goals, Budget, Viability, Reports, Pay Stack, Household, Recovery Lane, Documents, and related proof panels now prefer backend-owned labels/receipts over frontend-only money wording.

  • Fix

    Paycheck and safe-to-spend math were tightened.

    Fixes covered null-hours crashes, Decimal-style paycheck reconciliation, trace gross/net reconciliation, allocation penny handling, essentials reserve behavior, monthly pay-period tiling, payday-boundary bill policy, and predictive-shortfall cushion sanity bounds.

  • Polish

    Pay Stack and Pay Rules Studio were hardened.

    The visual pay stack now preserves backend receipt labels, avoids cramped overlap, uses shared money formatting, and keeps the four-bucket pay model aligned with pay-rule previews.

  • New

    Operating mode behavior was expanded.

    Mode changes now invalidate/refresh more financial surfaces and appear on dashboard/paycheck/forecast surfaces instead of remaining a mostly decorative setting.

  • Security

    Route-audit fixes landed.

    Auth refresh, public viability share, bills filtering, import dedupe, COL public download/search/admin guards, mobile sync, mobile outbox, documents, notifications, releases, observability, admin repair, demo caps, and frontend route efficiency issues were addressed.

  • Security

    A route-audit closure matrix was added.

    `docs/audit/route-audit-2026-05-30-closure.md` reconciles the source report count mismatch, classifies every source bullet as fixed, duplicate, accepted with reason, or validation-limited, and lists the gates run.

  • Polish

    Flutter is now the recorded mobile direction.

    ADR-013 and Task #251 retired the old native iOS path from active scope and made `viably_flutter` the sole mobile client scaffold.

  • Polish

    Self-service account deletion shipped.

    The privacy route, UI, and E2E coverage now support account deletion without relying on manual admin work.

  • Polish

    The public domain/email migration moved to `viably.tech`.

    Canonical SEO URLs, contact email references, and mobile API base configuration were updated from the older domain.

  • Fix

    Local developer recovery improved.

    Session-start hooks and dev-stack recovery tooling were added to reduce broken-localhost restarts and stale-build confusion.

  • New

    Hyper-Waterfall process docs were added.

    The repo now has a documented staged workflow for large audits and implementation closeouts.

  • Fixes

    • Polish

      Public share privacy tightened.

      Public viability-share output now strips sensitive income/residual fields and avoids dev-salt viewer hashing in production.

    • Polish

      Legacy refresh-token revocation hardened.

      Sid-less refresh tokens no longer bypass session revocation.

    • Fix

      Mobile sync cursor loss fixed.

      Incremental sync now advances from delivered changes instead of server time, preventing skipped records.

    • Polish

      Import duplicates reduced.

      Shift CSV import deduplicates within the batch and against existing shift keys.

    • Polish

      COL data routes are safer and cheaper.

      Public downloads require a state filter, search supports cursor-style pagination, and refresh-all checks the canonical collection.

    • New

      Documents and OCR routes are more bounded.

      Reprocess reserves quota, extraction lists can page, file preview streams object-storage data, and save/update routes verify ownership.

    • Polish

      Notifications, releases, and observability avoid avoidable hot-path work.

      Independent reads run concurrently, release notification tasks are retained, and public observability recompute is single-flight cached.

    • Polish

      Money formatting audits pass again.

      Contract surfaces now call shared `formatCurrency` helpers instead of reintroducing local `$${amount}` formatting.

    • Polish

      Viability compare now respects the ZIP compare cap.

      Free accounts are blocked before the expensive side-by-side analysis path runs.

    Fix

    Regression coverage added for heading drift.

    `backend/tests/unit/test_changelog_parser_release_sections.py` proves that representative newer headings are parsed while operational sections stay out.

  • Polish

    Real changelog catch-up versions are guarded.

    The same test asserts that the restored May catch-up versions from v3.15.0 through v3.11.0 remain parseable with public release items.

  • Polish

    Local release rows can be resynced deterministically.

    Restarting the backend now runs the changelog-to-release sync with the restored parser and yields a semver-ordered feed that keeps the latest release first.

  • Raw "stack group" language is hidden behind clearer user wording: how the item combines, whether it adds, the highest item wins, it replaces a base rate, it multiplies pay, or it is preview-only.

  • Polish

    Advanced "competes with" behavior remains available when needed.

    Highest-wins and replacement-style pay items can still define the competing group without making every user learn the internal model first.

  • Polish

    Pay component history is tracked.

    Pay-rule component edits now have a history trail so changes to a differential, bonus, or override can be audited rather than silently overwriting the past.

  • Polish

    Draft pay-rule preview is hardened.

    Draft preview payloads validate nested pay-rule shapes before calculation so malformed preview data cannot crash the pay engine or bypass expected type boundaries.

  • New

    FLSA classification and pay structure fields added.

    Pay rules now capture employee classification and base-pay structure concepts, giving the studio a clearer legal/accounting frame before users customize variable pay.

  • New

    Numerical guardrails were added to pay calculations.

    Pay Stack inputs now constrain extreme values and rounding behavior before they flow into gross/net estimates.

  • New

    Bucket totals and counted-item summaries ship in pay receipts.

    `describe_shift_pay` style output can show per-bucket counts and totals so the visual receipt matches what the engine counted.

  • Security

    Diagnostics and rounding options were expanded.

    The pay stack audit view now exposes more calculation notes and rounding choices for users with complex contracts.

  • Polish

    Preview shift and visual receipt coverage expanded.

    E2E tests cover the Pay Stack visual pay-contract builder, preview shift behavior, taxonomy map labels, bucket focus controls, and calculation-mode visibility.

  • New

    Pay Rules Studio layout was tightened for responsive use.

    The page received multiple layout/refactor passes to reduce overlap, improve narrow-width readability, and make the bucket summary less cramped.

  • Polish

    Next paycheck period calculation now uses the active workplace/pay-rule period.

    Financial context computes the next paycheck window from the relevant workplace/pay schedule rather than falling back to an overly narrow or default period.

  • New

    Dashboard paycheck integration uses the corrected period.

    The next-paycheck dashboard section now has the backend data path needed to show all qualifying worked/planned shifts for the period instead of one undercounted shift.

  • Fixes

    • Polish

      Historical shifts are preserved when pay rules change.

      Pay-rule updates preserve prior estimates where appropriate while recalculating affected current/future estimates.

    • Polish

      Settings pay-rule duplication was removed.

      Legacy Settings references now route users toward Pay Rules Studio.

    • Polish

      Income pay-rule duplication was reduced.

      E2E coverage and references were updated so the app stops presenting multiple competing pay-rule editors.

    • Polish

      E2E auto-verify is restricted to explicit test environments.

      Test-only email verification behavior cannot activate accidentally in normal development or production-like runs.

    • Polish

      Financial impact events are redacted from worker refresh results.

      Worker-facing refresh payloads no longer echo sensitive impact details.

    • Polish

      Repository hygiene scripts and setup docs were refreshed.

      Local setup and hygiene checks now reflect the current repo shape.

  • Polish

    Payday Safety methodology was documented.

    The docs now explain the contract and method so judges, operators, and future maintainers can trace how the app reaches the headline answer.

  • Polish

    Native iOS core implementation began.

    `mobile/native_ios` gained the foundation for an iOS app that mirrors backend truth instead of forking financial formulas.

  • New

    Swift version configuration added.

    Native iOS build configuration now declares the Swift version expected by the project.

  • New

    Mobile routing and navigation structures were added.

    The native client gained route/navigation scaffolding for the main Viably surfaces.

  • Polish

    ShiftForm and PayRulesSection were refactored onto newer UI primitives.

    The web pay setup path became more accessible and more consistent with the newer component system.

  • New

    CONTRIBUTING and SECURITY guidance added.

    Repository-facing docs now make local contribution and security expectations clearer.

  • New

    Payday Safety verification script tests added.

    Unit coverage checks the focused verification path for the Payday Safety tranche.

  • Security

    CSP compliance tests added.

    QA coverage now verifies important CSP behavior rather than relying only on manual browser inspection.

  • Fixes

    • Polish

      Native remembered-login behavior hardened.

      iOS stale keychain restore paths now block reinstall session restoration and default remembered login to off.

    • New

      Payday Safety telemetry only fires for the first answer.

      The telemetry endpoint is gated so repeated dashboard visits do not spam first-answer events.

    • Polish

      Pay-rule recalculation behavior improved.

      Pay-rule logic preserves historical shifts and recalculates affected estimates when current assumptions change.

  • Polish

    Mobile API documentation updated.

    Mobile-facing API docs were brought forward with the newer notification and fallback behavior.

  • Polish

    Client auth cleanup was hardened on logout failure.

    Logout clears client state even if server revocation fails, reducing shared-device and stale-session risk.

  • Polish

    Mobile outbox storage is scoped by user.

    Offline/mobile queued state is isolated per user and cleared on logout.

  • Polish

    Signup auto-verification bypass removed.

    Header-based signup auto-verification can no longer bypass normal email-verification expectations.

  • Polish

    Admin and trial credentials rotated.

    Local/test credentials and CI references were updated after credential hygiene work.

  • Polish

    Plaintext legacy password docs were redacted.

    `test_credentials.md` no longer carries the old plaintext password value.

  • Polish

    Docker Compose Postgres/Redis exposure tightened.

    Local infrastructure defaults were updated to avoid unnecessarily exposed services and weak credentials.

  • Polish

    Public demo object storage cleanup hardened.

    Demo seeding now limits object-storage exhaustion risk.

  • Fix

    Native duplicate declaration fixed.

    `ViablyAppModel` no longer declares `currentUserID` twice.

  • Reconciliation can now drive user-visible next steps and apply corrected actuals back into financial records.

  • Polish

    OCR failure handling improved.

    Document/paystub flows expose richer failure metadata so users and operators can tell what failed instead of seeing a dead-end upload.

  • Polish

    Worker queue diagnostics improved.

    Background work gained better status visibility for follow-up processing.

  • Polish

    Demo mode was enriched.

    Demo seeding now better supports a judge-grade story with realistic money state, paycheck composition, and safe-week context.

  • New

    Paycheck composition and Safe Week Hero were improved.

    Dashboard money components became more explainable and visually useful.

  • New

    Dashboard responsiveness improved.

    Dashboard components were tuned for better layout behavior across viewports.

  • Polish

    Financial insights gained grounding evidence.

    Recommendation surfaces now carry more specific source facts instead of generic advice copy.

  • Polish

    Financial stability calculations and notifications improved.

    Stability math and notification triggers gained better coverage for the "short before payday" story.

  • Polish

    CSV import/export money handling improved.

    Import paths now carry gross/net estimates, source details, auto-match defaults, and better money handling.

  • Polish

    Shift import templates show pay-rule context.

    CSV import displays pay-rule information so imported shifts can be reviewed against the correct contract.

  • New

    Frontend TypeScript configuration added.

    The app gained a TypeScript configuration path while preserving the existing JavaScript codebase.

  • New

    Frontend UI contract tests added.

    Unit coverage now locks selected UI contracts, native iOS scaffold behavior, and P1 obligation expectations.

  • Refactor

    • Polish

      Mongo client migrated from Motor to PyMongo Async.

      Backend Mongo access moved from `AsyncIOMotorClient` toward `AsyncMongoClient`, and related tests/imports were updated.

    • Polish

      Server imports and test user registration were cleaned up.

      The server path and test registration helpers were refactored for the newer backend shape.

    • Polish

      Integration tests now use environment-based admin credentials.

      Tests no longer rely on hardcoded admin credentials.

    • Polish

      README was refreshed for product and architecture clarity.

      The repo front door now better describes Viably's product promise, architecture, and verification paths.

    Security and correctness

    • Polish

      Public release projection no longer leaks creator ids.

      Public release responses exclude internal creator identifiers.

    • Polish

      Release-gate dotenv loading avoids shell evaluation.

      Release and pre-push dotenv handling no longer treats env files as executable shell.

    • Polish

      Backend env defaults were hardened for metrics tokens.

      A source-controlled metrics token cannot be mistaken for a production operator secret.

    • Security

      CSRF origin checking was hardened against forwarded-header spoofing.

      CSRF origin checking was hardened against forwarded-header spoofing.

    • Polish

      Internal revalidation auth now runs before body parsing.

      Internal revalidation auth now runs before body parsing.

    • Polish

      OAuth cookie/session exchange no longer shares cookies across exchanges.

      OAuth cookie/session exchange no longer shares cookies across exchanges.

    • Polish

      MFA recovery links were hardened against origin spoofing, and token execution was repaired after URL redaction.

      MFA recovery links were hardened against origin spoofing, and token execution was repaired after URL redaction.

    • Polish

      Long bcrypt passwords are handled correctly in auth flows.

      Long bcrypt passwords are handled correctly in auth flows.

    • Polish

      Server-side email verification is enforced for authenticated APIs.

      Server-side email verification is enforced for authenticated APIs.

    • Polish

      Passwordless-account login failure paths were hardened.

      Passwordless-account login failure paths were hardened.

    • Polish

      JWT secret misconfiguration no longer leaks configuration detail.

      JWT secret misconfiguration no longer leaks configuration detail.

    • Polish

      Unverified JWT fallback now correctly returns to session auth.

      Unverified JWT fallback now correctly returns to session auth.

    • Fix

      Rate limiter fallback fails closed, authenticated rate-limit keys are set before route auth, and alert endpoint keying was fixed.

      Rate limiter fallback fails closed, authenticated rate-limit keys are set before route auth, and alert endpoint keying was fixed.

    • Fix

      DDoS bypass scope preserves WAF/blocklist checks and route soft-block parsing was fixed.

      DDoS bypass scope preserves WAF/blocklist checks and route soft-block parsing was fixed.

    • Polish

      CORS wildcard extras are rejected.

      CORS wildcard extras are rejected.

    • Polish

      Logout now clears client state on API failure and avoids refresh-loop behavior.

      Logout now clears client state on API failure and avoids refresh-loop behavior.

    • Fix

      Authenticated GET cache invalidation and API cache-control no-store bypasses were fixed.

      Authenticated GET cache invalidation and API cache-control no-store bypasses were fixed.

    • Polish

      Financial stores reset on auth boundaries and stale load promises cannot overwrite current user state.

      Financial stores reset on auth boundaries and stale load promises cannot overwrite current user state.

    • Polish

      Service/outbox owner sync preserves same-owner queued entries while protecting cross-user state.

      Service/outbox owner sync preserves same-owner queued entries while protecting cross-user state.

    • Polish

      Document upload/OCR and feedback fields are bounded against DoS.

      Document upload/OCR and feedback fields are bounded against DoS.

    • Polish

      Public viability share snapshots and PDF exports sanitize untrusted fields.

      Public viability share snapshots and PDF exports sanitize untrusted fields.

    • Polish

      Public status/changelog/release feeds were hardened against information disclosure.

      Public status/changelog/release feeds were hardened against information disclosure.

    • Polish

      COL admin probe errors redact provider API keys.

      COL admin probe errors redact provider API keys.

    • Polish

      Admin CSV uploads are size-gated before parsing and manual upload file type handling was repaired.

      Admin CSV uploads are size-gated before parsing and manual upload file type handling was repaired.

    • Polish

      Health/readiness semantics and route rate limiting were hardened.

      Health/readiness semantics and route rate limiting were hardened.

    • Polish

      Public demo and snapshot endpoints were restricted or redacted where needed.

      Public demo and snapshot endpoints were restricted or redacted where needed.

    Data-quality fixes

    • Polish

      COL refresh writes stay behind the publish pipeline.

      COL refresh writes stay behind the publish pipeline.

    • Polish

      BEA SARPP long-shape parser and manual template ingestion were repaired.

      BEA SARPP long-shape parser and manual template ingestion were repaired.

    • Polish

      USDA refreshers now share PDF vintage requirements and update vintages only through the full parser.

      USDA refreshers now share PDF vintage requirements and update vintages only through the full parser.

    • Fix

      EIA, MIT, SAFMR, ZCTA, commute, local-date, ZIP timezone, and household-profile edge cases were fixed.

      EIA, MIT, SAFMR, ZCTA, commute, local-date, ZIP timezone, and household-profile edge cases were fixed.

    • Speed

      COL cache aliasing and redundant CPI index creation were corrected.

      COL cache aliasing and redundant CPI index creation were corrected.

    • Polish

      Monthly payday defaults and forecast pay-period underflow were repaired.

      Monthly payday defaults and forecast pay-period underflow were repaired.

    • Polish

      Mode cash runway no longer clamps non-positive values into unrealistic runway.

      Mode cash runway no longer clamps non-positive values into unrealistic runway.

    • Polish

      Debt months, income report month bounds, shared goal contributions, and extreme finite money values were normalized.

      Debt months, income report month bounds, shared goal contributions, and extreme finite money values were normalized.

    • Polish

      Missed shifts are excluded from hours analytics.

      Missed shifts are excluded from hours analytics.

    • Polish

      Consecutive shift premiums and gross-only paid-shift trace rows were reconciled.

      Consecutive shift premiums and gross-only paid-shift trace rows were reconciled.

    • Polish

      PTO unpaid policies no longer fall back to paid type.

      PTO unpaid policies no longer fall back to paid type.

    Ops and tests

    • Security

      Security checklist, pytest, audit, pre-push, smoke, Docker, and GitHub Action failure masking were repaired.

      Security checklist, pytest, audit, pre-push, smoke, Docker, and GitHub Action failure masking were repaired.

    • Polish

      Docker builds can skip optional OpenAPI typegen through `OPENAPI_TYPEGEN_OPTIONAL=1`.

      Docker builds can skip optional OpenAPI typegen through `OPENAPI_TYPEGEN_OPTIONAL=1`.

    • Polish

      Dependency updates landed for backend idna and related groups.

      Dependency updates landed for backend idna and related groups.

    • Polish

      Admin/test secrets were removed from tests and fixtures.

      Admin/test secrets were removed from tests and fixtures.

    • New

      P1 obligation, native iOS scaffold, and UI contract tests were added.

      P1 obligation, native iOS scaffold, and UI contract tests were added.

  • Security

    CSRF origin checks resist forwarded-header spoofing.

    CSRF origin checks resist forwarded-header spoofing.

  • Polish

    Shared OAuth HTTP client cookie handling is isolated.

    Shared OAuth HTTP client cookie handling is isolated.

  • Polish

    Lockfile and smoke workflows were hardened against branch/token exposure.

    Lockfile and smoke workflows were hardened against branch/token exposure.

  • Polish

    Internal revalidate authentication runs before request body parsing.

    Internal revalidate authentication runs before request body parsing.

  • Polish

    Changelog SSRF was removed by dropping host-derived fetch URLs.

    Changelog SSRF was removed by dropping host-derived fetch URLs.

  • Polish

    Sentry client events redact sensitive URL parameters.

    Sentry client events redact sensitive URL parameters.

  • Polish

    Secret-restore workflow was hardened against git leakage.

    Secret-restore workflow was hardened against git leakage.

  • Polish

    Broad `.env` ignore coverage and live-test secret removal were restored.

    Broad `.env` ignore coverage and live-test secret removal were restored.

  • Polish

    Public status feed and changelog heading filters were tightened.

    Public status feed and changelog heading filters were tightened.

  • Polish

    ErrorBoundary telemetry URL handling was hardened.

    ErrorBoundary telemetry URL handling was hardened.

  • Polish

    Financial stores reset on auth boundaries to prevent cross-user leakage.

    Financial stores reset on auth boundaries to prevent cross-user leakage.

  • Polish

    Missing Plaid credentials no longer block local disconnect cleanup.

    Missing Plaid credentials no longer block local disconnect cleanup.

  • Polish

    Merchant aliases use tenant-scoped uniqueness.

    Merchant aliases use tenant-scoped uniqueness.

  • Polish

    MFA recovery links resist Origin spoofing.

    MFA recovery links resist Origin spoofing.

  • Polish

    CORS extras reject wildcard origins.

    CORS extras reject wildcard origins.

  • Polish

    DDoS bypass and soft-block promotion behavior were tightened.

    DDoS bypass and soft-block promotion behavior were tightened.

  • Polish

    Credential docs no longer expose sensitive test values.

    Credential docs no longer expose sensitive test values.

  • Polish

    Public viability shares and PDF exports sanitize untrusted fields.

    Public viability shares and PDF exports sanitize untrusted fields.

  • Security

    OAuth callback state validation protects against login CSRF.

    OAuth callback state validation protects against login CSRF.

  • Polish

    Login `next` redirects reject backslash/open-redirect tricks.

    Login `next` redirects reject backslash/open-redirect tricks.

  • Polish

    Shared goal contributions reject non-finite values.

    Shared goal contributions reject non-finite values.

  • Polish

    Tax calibration fixtures and docs were sanitized.

    Tax calibration fixtures and docs were sanitized.

  • Fix

    Frontend auth GET cache invalidation race was fixed.

    Frontend auth GET cache invalidation race was fixed.

  • Polish

    Public snapshot headlines no longer leak private data.

    Public snapshot headlines no longer leak private data.

  • Polish

    Telemetry rate limiting and funnel attribution were hardened.

    Telemetry rate limiting and funnel attribution were hardened.

  • Polish

    COL refresh writes remain behind the publish pipeline.

    COL refresh writes remain behind the publish pipeline.

  • Fix

    Security checklist and CI failure masking were fixed so broken gates actually fail.

    Security checklist and CI failure masking were fixed so broken gates actually fail.

  • Security

    Offline outbox strips sensitive headers before queueing.

    `lib/idbOutbox.js` and `public/outbox-sync-sw.js` remove `Authorization`, cookies, CSRF tokens, and step-up confirmation tokens before mutations enter IndexedDB. Replay strips again so older queued browser entries are protected too.

  • Security

    Offline outbox hardening tests added.

    `tests/unit/test_offline_outbox_hardening.py` locks the header allowlist and replay-scrubbing contract.

  • Speed

    Idempotency cache scoped per user.

    The `idempotent(action_name)` decorator now resolves the authenticated user before keying so tenants sharing an IP cannot collide. Checkout, bills, shifts, workplaces, pay rules, and budget categories now honor `Idempotency-Key` for safe flaky-network replays.

  • Speed

    Cold tab-switches substantially improved.

    Dashboard to Viability transitions dropped from roughly 500-800 ms to roughly 100-150 ms across route prewarm, cache, and skeleton fixes.

  • New

    Idle prefetch now fires on every authenticated mount.

    The prefetch pass moved from dashboard-only with a 1.5 second delay to once-per-session on any authenticated route after a 500 ms idle delay.

  • Polish

    Priority-queue prefetcher shipped.

    `lib/prefetchPageData.js` now prioritizes hover, focus, touchstart, and pointerdown intent over background warming, caps API concurrency, and normalizes query-string routes to warm their base path.

  • Fix

    Navigation pending hints no longer shift layout.

    Sidebar links reserve a fixed hint slot and only show pending state when navigation is actually blocked.

  • Speed

    Router and API cache windows lengthened.

    Next.js dynamic stale time moved to 90 seconds, static stale time to 300 seconds, and the in-memory API cache window to 45 seconds so back navigation feels instant without hiding financial mutation invalidations.

  • Polish

    Bootstrap pre-warms viability data.

    `/api/bootstrap` now includes `/api/col/states-summary` and `/api/viability/latest`; `lib/auth-context.js::primeCache` seeds the client cache so the viability snapshot and staleness banner render from first paint.

  • Speed

    Viability layout reordered for faster first paint.

    The cost map moved below the form and results, and `viability/loading.js` now mirrors the new primary-interaction-first shape.

  • New

    Route-specific loading skeletons added.

    Dashboard, Viability, Shifts, Bills, Paycheck, and Income Floor now have layout-matched `loading.js` files instead of the generic app fallback.

  • New

    Brotli response middleware added.

    `services/brotli_middleware.py` can compress FastAPI responses before they reach the client.

  • New

    Pooled async HTTP client added.

    `services/http_client.py` replaces ad-hoc `httpx.AsyncClient` creation across services, reducing repeated TLS and connection setup cost.

  • Polish

    Cross-platform frontend maintenance scripts shipped.

    `frontend/scripts/build-analyze.js` and `frontend/scripts/clean-next.js` replace POSIX-only package scripts so Windows, Linux, and CI run the same commands.

  • Fix

    Redis-aware performance infrastructure added.

    Distributed cache, rate limiter, DDoS guard, and regression alerts use Redis when `REDIS_URL` is configured while preserving local fallback behavior.

  • New

    React Compiler enabled.

    `next.config.js` enables the React Compiler after compiler-safety linting passed, allowing heavy client pages like Dashboard and Viability to benefit from automatic memoization.

  • Polish

    Sidebar consolidated into task hubs.

    The 18-item flat navigation collapsed into Income, Bills and Debt, Planning, and Scenarios / Viability hubs, with Dashboard, Reports, Documents, and Settings as perimeter routes.

  • Speed

    React Query migration started.

    Multiple frontend components now use TanStack Query with keys aligned to bootstrap prewarming so primed cache entries are reused.

  • New

    Landing page redesigned.

    The public landing now leads with "Know what is safe before payday," trace-status stat cards, Methodology / Personas / Pricing / Trust navigation, and a faster "Try it" CTA.

  • Polish

    Accessible state cost map replaced `react-usa-map`.

    A local keyboard- and screen-reader-friendly cartogram now renders state tier coloring without depending on the stale third-party SVG package.

  • Fix

    Index-as-key anti-patterns fixed.

    `OnboardingTour.jsx`, `TestimonialCarousel.jsx`, and the `pressure-map` weekday row now use stable keys to preserve component identity under updates.

  • Polish

    Render-time ref mutation removed from `useViabilityData`.

    The `formRef.current = form` update moved into an effect, clearing a React 19 / Compiler antipattern while keeping async callbacks current.

  • Fix

    Next.js 16 middleware registration fixed.

    The app returned to the `frontend/middleware.js` convention so Turbopack registers middleware consistently, and CSP script loading was adjusted to keep nonce protection for inline scripts while allowing Next's self-hosted chunks.

  • Polish

    Flutter mobile reference app scaffolded.

    `mobile/viably_flutter/` contains Android and iOS toolchains, an architecture README, and shared fixtures for contract testing.

  • New

    Mobile BFF router added.

    `routers/mobile.py` exposes `/api/mobile/v1/*` auth, device, push, billing, bootstrap, sync, and changelog endpoints shaped for native clients.

  • New

    Hybrid mobile architecture contract tests added.

    `tests/unit/test_hybrid_mobile_architecture.py` verifies mobile payload shape, pagination, timestamp handling, and no internal `_id` leakage.

  • Polish

    Local LLM adapter replaced `emergentintegrations`.

    `services/llm_chat.py` preserves the `LlmChat` / `UserMessage` surface directly on LiteLLM, removing the dependency conflict between `emergentintegrations`, `openai`, and `litellm`.

  • Polish

    OpenAI and LiteLLM dependency line unblocked.

    `emergentintegrations==0.1.0` was removed, `litellm` moved to `1.85.0`, and `openai` moved to `2.24.0`, eliminating the fresh-install resolver conflict.

  • Polish

    JWT rotation binds refresh tokens to session ids.

    Refresh tokens carry an `sid` claim tied to the device cookie so replay from another device revokes the family instead of minting another token.

  • New

    MFA recovery execution hardened.

    Recovery now requires token and new password atomically, validates password strength before token mutation, and has updated integration coverage for empty and unknown token paths.

  • Polish

    Document storage abstraction shipped.

    Document uploads now go through an object-storage adapter with S3-compatible backend support and disk fallback via `DOCUMENT_STORAGE_BACKEND`.

  • New

    Postgres ledger scaffolding added.

    `services/postgres.py` and a Postgres-compatible ledger schema are DSN-gated so deployments without `POSTGRES_URL` continue on Mongo fallback.

  • New

    Redis worker queue added.

    `services/worker_queue.py` provides a BLPOP loop for future background work such as push scheduling, admin index work, and Stripe-event reconciliation.

  • Security

    Agent conventions documented.

    `AGENTS.md` now records pinned versions, test gates, and hardening invariants for incoming automated patches.

  • Security

    Audit triage verifier added.

    `scripts/triage_audit.py` validates audit claims before agents act on them, reducing false-positive bulk-edit risk.

  • Security

    Obligation verifier added.

    `tools/audit/verify-obligations.mjs` joined the obligation-tracking pipeline for `docs/audit/ShiftSavvyyObligations.csv`.

  • Polish

    Security-touching PR template tightened.

    `.github/PULL_REQUEST_TEMPLATE.md` now calls out extra checks for security, push-notification, and idempotency changes.

  • Polish

    CI workflow refined.

    `.github/workflows/ci.yml` was adjusted for action-runtime warnings and frozen-lockfile installs.

  • Polish

    Stale local artifacts moved out of source.

    Old `.codex-logs/` and `local-uvicorn.*.log` artifacts were removed from source-controlled paths.

  • Follow-ups

    • Speed

      Cache Components full migration remains deferred.

      The broad route-handler migration was attempted and cleanly reverted because `app/layout.js` still reads the CSP nonce through `await headers()`. `memory/CACHE_COMPONENTS_MIGRATION_AUDIT.md` records the Phase 0 options.

    • Polish

      Google Search Console sitemap submission remains manual.

      The app-side sitemap exists, but final submission still happens in Search Console.

    • Polish

      Postgres ledger activation remains operator-gated.

      Set `POSTGRES_URL` in the deploy panel to move from Mongo fallback to the Postgres ledger path.

    • Polish

      Web push activation remains operator-gated.

      Set VAPID public/private/subject env vars before production push can be enabled.

    • Polish

      Scheduler trigger rules remain the next retention step.

      The push stack is ready; shift reminder, payday-arrival, tight-week, and paycheck-landed rules still need to be wired to user financial context.

    Refresh and logout are no longer exempt from CSRF origin checks; middleware coverage pins the stricter behavior.

  • Security

    Document path traversal hardening.

    Document status path checks now use `commonpath` so prefix tricks cannot escape the intended document directory.

  • Polish

    Feedback upload validation hardened.

    Image uploads now validate file magic bytes instead of trusting filename or content-type metadata.

  • Polish

    Metrics and operator health privacy.

    Production metrics remain bearer-gated by `METRICS_TOKEN`, and health/debug endpoints avoid leaking deployment posture that helps reconnaissance.

  • Polish

    Frontend failure states made actionable.

    API timeouts, login failures, and app-shell unavailable states now surface retryable errors instead of leaving users stuck on indefinite loading.

  • Runtime and performance

    • Speed

      Next.js 16 and React 19 production path.

      The frontend is now aligned to the Next.js 16 / React 19 line with Cache Components, Partial Prerendering, streamed nonce-safe CSP boundaries, and a committed `yarn.lock` for reproducible installs.

    • Security

      Nonce CSP preserved under modern rendering.

      Request-bound CSP nonce work was isolated so Partial Prerendering can run without weakening strict `script-src` behavior.

    • Polish

      Image optimizer locked down.

      The public optimizer now uses explicit local/remote allowlists, WebP output, a single approved quality, disabled SVG optimization, no local IP access, and no redirects. Private document previews intentionally stay outside the optimizer path.

    • Polish

      Backend health now fails fast.

      `/api/health` uses `HEALTH_DB_TIMEOUT_SECONDS` so a down or slow MongoDB does not hang the lightweight liveness check; `/api/readiness` remains the stricter deploy gate.

    • Polish

      OpenAPI generation cleaned up.

      Backend `GET` and `HEAD` health handlers were split to remove duplicate operation IDs, and internal Next preview proxy routes are hidden from client generation.

    • Polish

      Accessible cost-map replacement.

      The `react-usa-map` dependency was replaced with a local keyboard/screen-reader friendly cost-map cartogram.

    • New

      Local startup repeatability.

      Added the PowerShell startup path so local frontend/backend/Mongo setup is less dependent on manual environment steps.

    Cost-of-living and viability accuracy

    • Polish

      Methodology advanced to v3.6.0.

      The cost-of-living engine now treats live source provenance as part of the methodology contract instead of burying vintages in source modules.

    • Polish

      Fourteen source families are tracked.

      The seed pipeline now registers every active COL source family in `source_vintages`, `/api/col/meta/version`, `/api/col/sources`, public methodology output, and synthesized record metadata.

    • Polish

      Dataset freshness registry made public-safe.

      Freshness status now reports aggregate counts without leaking internals; the current local run shows 14 total sources, 13 fresh, 1 due, 0 stale, and 0 unknown.

    • Polish

      Official-source refresh pass completed.

      COL sources were updated against current official or primary sources: BLS CPI April 2026, BEA 2024 RPP state/metro, HUD FY2026 FMR, Census ACS 2020-2024, EIA electricity and natural gas, USDA March 2026 Food Plans, MIT 2026 Living Wage, KFF/CMS 2026 benchmark premiums, AAA May 17 gas prices, and Tax Foundation 2026 local-tax tables.

    • Polish

      AAA gas prices corrected.

      The transportation fuel input now uses the May 17, 2026 AAA state table at three-decimal precision plus the same-day national average of `$4.513`; the ingest script preserves AAA precision and documents the national-average update step.

    • New

      Local taxes added to viability.

      Sales-tax and owner-property-tax estimates now use Tax Foundation 2026 rates, Census ACS 2020-2024 home values, explicit data-source metadata, and frontend tax-breakdown display support.

    • Polish

      Owner and renter housing provenance clarified.

      ACS rent, ACS tenant-paid utility allowance, ACS SMOC, HUD FMR, and BEA RPP now publish current vintages and clearer anti-double-counting notes.

    • Polish

      Viability calculator now carries source vintages.

      Location and calculation output includes the COL dataset version, source vintage map, and local-tax source ids so users and auditors can see what data powered a result.

    • New

      Methodology page aligned with the app.

      Public methodology text now derives source rows from live metadata, describes the current formula/fallback chain, and aligns with the app-improvement-goals anchor.

    Refresh pipelines and provenance controls

    • Polish

      BLS refreshers now prefer the public API.

      CPI temporal and regional refresh scripts use the BLS public API by default with spreadsheet fallback and official release-date metadata.

    • Polish

      Census ACS refreshers were modernized.

      Rent, utility allowance, SMOC, and home-value paths now understand 2020-2024 ACS 5-year table-based data and emit release-date provenance.

    • Polish

      EIA natural gas has a no-key fallback.

      The refresh path can use the EIA API when configured or the official monthly XLS file when not.

    • Polish

      USDA/KFF/AAA/CSV ingests now preserve published-at metadata.

      Manual and semi-automated ingests update `published_at`, source id, vintage, and coverage consistently.

    • Polish

      Refresh orchestrator target ids updated.

      Admin upload templates, refresh history, and source resolution now point at the current source ids and no longer advertise stale target names.

    • Fix

      Freshness tests added.

      A new provenance regression test locks that all current source families are registered, synthesis uses live vintages, ACS rent is current, and no source is stale or unknown.

    Documentation and operator readiness

    • Security

      README dataset provenance section added.

      The README now lists active data sources, vintages, refresh commands, and the public endpoints used to audit dataset freshness.

    • Polish

      Deploy checklist updated.

      Deployment now calls out COL methodology v3.6.0, data-provenance checks, current refresher commands, optional BEA/EIA keys, and post-reseed verification.

    • Polish

      App improvement goals reconciled.

      `docs/app-improvement-goals-anchor.md` now records the dataset freshness/provenance pass as completed and aligned with the shift-worker finance goal.

    • Polish

      May release docs preserved.

      The May 8 security/performance update and May 10 full-app update documents capture the broader runtime, security, and smoke-test work that preceded this changelog catch-up.

  • Polish

    Floating-point money drift in tax forecast.

    `services/tax.py` was rounding all 10 money fields on `TaxBreakdown` with `round(x, 2)` applied to Python floats — IEEE-754 representation error accumulates when a forecast sums 26 biweekly paychecks × 9 components. For a user cross-referencing against an Excel model, the $0.02 drift over a year destroys trust. Migrated every money field to `services.finance._round_money` (Decimal HALF_UP). Added `tests/unit/test_tax_decimal_rounding_accumulation.py` — 3 tests that sum 26 periods and assert drift ≤ $0.01 per field, plus a float-tail check that flags values like `0.30000000000000004`.

  • P1 — framework + dependency upgrades

    • Polish

      Next.js 14.2.35 → 16.2.4

      (skipped past 15). Killed 2 HIGH CVEs (HTTP request deserialization DoS, Server Components DoS) plus 4 other HIGH findings in transitive deps. Migrations: `headers()` → `await headers()` in `app/layout.js`, `{ params }` → `{ params: Promise<...> }` in `app/for/[persona]/page.js` + `app/s/[id]/page.js`, `middleware.js` → `proxy.js` file rename + `export function middleware` → `export function proxy` (Next 16 deprecation). `yarn audit --level=high` now reports **0 HIGH** (was 6).

    • New

      `.dockerignore` added.

      Docker build context was transferring ~1 GB per build (`.git` 213 MB + `frontend/node_modules` 729 MB + `.next/` + every committed `.env`). New `.dockerignore` drops context to <20 MB and keeps secrets out of image layers.

    • New

      Bundle-size optimization (120 KB parsed / 37 KB gzip per authed page).

      Bundle analyzer (new `yarn build:analyze` script) revealed `framer-motion` was loading on every authenticated route via an eager `NotificationCenter` import — even though the component only renders when the user clicks the notification bell. Converted to `next/dynamic({ ssr: false })` in `app/(app)/layout.js`. Post-fix analyzer confirms framer-motion isolated to its own 133 KB chunk, loading only on drawer open. Phosphor correctly per-route-split via existing `optimizePackageImports`.

    • New

      Sentry scaffolding (no-op when DSN empty).

      Added `sentry.{client,server,edge}.config.js`. `ErrorBoundary.componentDidCatch` now forwards to `Sentry.captureException` alongside the existing in-house telemetry beacon. Enable in production by setting `NEXT_PUBLIC_SENTRY_DSN` — no code change needed.

    • Polish

      `bcrypt.gensalt(rounds=12)` explicit.

      Was relying on the library default; a future `pip upgrade bcrypt` changing the default (as has happened twice in its history) could silently weaken hash cost. Explicit 12 matches OWASP 2025 recommendations and mirrors `_RECOVERY_BCRYPT_ROUNDS` in `services/totp.py`.

    • Fix

      Landing-page heading hierarchy fix.

      `MethodologyPreview.jsx` had an `<h3>` that jumped past the page's `<h1>` with no intervening `<h2>`. Lighthouse accessibility flag; screen-reader users navigating by heading hop from page title directly into widget subtitle. Promoted to `<h2>`.

    • Polish

      Python deps refreshed.

      `motor 3.3.1 → 3.7.1`, `pymongo 4.5.0 → 4.17.0`, `python-multipart 0.0.24 → 0.0.27` (closed file-upload DoS in older multipart — affects our receipt-OCR path), `postcss → 8.5.13` (top-level; CSS-stringify XSS patch).

    P2 — hygiene

    • Speed

      Service-worker cache purge on logout.

      `public/outbox-sync-sw.js` was caching `/api/bootstrap`, `/api/dashboard`, `/api/bills` into browser Cache Storage. Cookies cleared on logout but cache didn't — a tablet handed off from the day nurse to the night nurse could serve the previous user's safe-to-spend and bills on the next offline load. Added a `purge-cache` message handler to the SW; `auth-context.js:logout()` now `postMessage({type:'purge-cache'})`s the SW to `caches.delete()` every `viably-runtime-*` cache entry before the redirect.

    • Polish

      TypeScript for the four shared registries.

      `lib/personas.ts`, `lib/pricing.ts`, `lib/pro-features.ts`, `lib/route-protection.ts`. Minimal `tsconfig.json` with `allowJs:true, checkJs:false` so Next.js auto-detects TS without touching the 250+ existing `.js` files. Registry consumers now get IntelliSense on every import. All four contract tests updated to probe `.ts` file paths.

    • Polish

      FAQPage + BreadcrumbList JSON-LD.

      `/pricing` emits `FAQPage` structured data derived from the same `pricingFaqEntries(cfg)` helper that renders the visible UI — rich-result accordion in Google SERPs can never drift from the page copy. `/for/[persona]` emits `BreadcrumbList` (Home → For → Persona) for sitelinks under the search result.

    • Polish

      Rate-limit landing telemetry.

      `POST /api/telemetry/landing-event` tightened from the 120/min `default` bucket to the 60/min `write` bucket. Closes an anonymous Mongo-write vector — one visitor_id + one IP at 120/min could fill the `landing_events` collection with 172k rows/day.

    • Security

      Operator-env hygiene check.

      `services/hardening.check_operator_env` warns loudly in production logs if `METRICS_TOKEN` or `NEXT_PUBLIC_SENTRY_DSN` are unset — non-fatal (a hackathon demo can ship without Sentry) but visible in startup logs so operators see what they forgot. Preflight CI script classifies these as warnings (exit 0) while keeping all other issues deploy-blocking.

    Observability dashboard

    • New

      Grafana dashboard JSON

      at `/app/docs/grafana-dashboard.json`. Wires the existing `viably_audit_log_writes_total`, `viably_slow_requests_total`, `viably_rate_limit_hits_total`, `viably_auth_attempt_total`, and HTTP status counters into 10 pre-built panels (login volume + success rate, brute-force-bucket saturation, slow-query trail, CSP violation rate, p50/p95/p99 request latency, 4xx/5xx rates by route). Self-hostable with one `import dashboard JSON` click in Grafana against any Prometheus scrape target pointed at the backend's `/metrics` endpoint.

    Docs

    • Polish

      `/app/docs/production-env.md` — ship-blockers vs operator-hygiene vs optional env vars

      Exactly what to set on first deploy.

    • New

      `/app/docs/grafana-dashboard.json` — import once, ops visibility forever.

      `/app/docs/grafana-dashboard.json` — import once, ops visibility forever.

    Polish

    `lib/pricing.js`

    fallback monthly/annual/trial constants + 9 derived formatters (SEO title/desc, JSON-LD offer, FAQ answers, persona tagline). **Fixed a live price-drift bug**: the pricing FAQ rendered "After 14 days you're charged **$5**/month" (from a stale `?? 500` cents fallback) while every other surface said $8. Eight files migrated from hardcoded `$8/mo` / `$80/yr` / `14-day` literals. Env-override path (`NEXT_PUBLIC_PRO_MONTHLY_PRICE_CENTS`, etc.) flips every surface at once from the deploy panel.

  • Polish

    `lib/pro-features.js`

    `PEEK_ROWS` (PricingPeek comparison table), `FREE_FEATURES` / `PRO_FEATURES` (pricing-page narrative lists), `PRO_LIMIT_LABELS` + `PRO_LIMIT_GATE_IDS` (Pro-cap gate registry). Two downstream surfaces (`/pricing` + the landing's `PricingPeek`) had already drifted on tier copy — "Priority support" vs "Priority email support", "Receipt OCR · auto-categorize" vs "Receipt OCR — snap a photo". Contract test includes a **backend-alignment guard** that parses `backend/routers/settings.py::_PRO_LIMIT_TYPES` and asserts frontend ID set == backend allowlist byte-for-byte; catches the class of bug where a one-sided gate addition silently drops to generic banner copy or 400s on the first cap hit.

  • Side fixes

    • New

      Logout redirect loop: `middleware.js` was redirecting authed users to `/login` via an inverted check

      Flipped the route-protection predicate to use the new shared list.

    • New

      Cost-of-living preview tile on landing page: price was drifting between the mini-preview ($9/$72 placeholder) and the live `/api/billing/config` ($8/$80 real)

      Wired to the real endpoint with a graceful fallback.

  • New

    Mobile shifts page squashed + missing money values

    . Two latent bugs interacting: (a) `PageChrome` had `h-14` + `shrink-0` actions cluster which on /shifts (5-button toolbar) demanded 280 px and forced `<main>` wider than the viewport, parking row money + actions off-screen; (b) `VirtualShiftList` hard-coded `estimateSize: () => 84` for the desktop single-row layout, but mobile rows stack `flex-col` at ~169 px tall — so every absolute-positioned row sat ~85 px on top of the next, hiding money + actions of the row above. Fixed both: `<main>` now `min-w-0 overflow-x-hidden`, PageChrome stacks vertically below `sm`, and `VirtualShiftList` wires TanStack Virtual's `measureElement` to read each row's real height post-mount. iOS Safari verification (iPhone 14 device profile): 9 rows, 0 overlaps, every row has `$` + action buttons.

  • Polish

    Release notes regressed to v3.5.0

    . `/api/releases/latest` was using `db.releases.find_one()` with no sort — Mongo returned whatever the index decided was first (the lifespan seeder's pinned v3.5.0 row, or an admin-typo'd test entry like `0.0.eec616`). `/api/releases/whats-new` had a related bug — sorting by `published_at` instead of semver, so any release a sysadmin re-touched recently jumped above genuinely-newer semver versions. Both endpoints now sort via the same `_semver_sort_key` as `/public` and filter out non-parseable versions.

  • Polish

    Changelog parser was dropping 24 versioned sessions silently.

    Three independent parser bugs combined to produce a "release notes pinned at v3.10.0" symptom even though CHANGELOG.md had v3.10.1 → v3.10.24 plus this session 159: (a) `_SESSION_HEADER` regex didn't allow the `Rxx` revision marker (`Session 158 R25`); (b) the parser only consumed the FIRST `**Version:** vX.Y.Z` line per session header but session 158 R25 stacks 22 of them; (c) `_BULLET` regex only matched `-` not numbered `1. 2. 3.` items. Fixed all three: relaxed regex, implicit-session-split on subsequent Version lines, numbered-item support.

  • High-impact gates added

    • New

      Auto-update channel for stale tabs

      . `public/outbox-sync-sw.js` now `postMessage`s `viably:sw-updated` to every controlled client when a new SW version activates. `lib/serviceWorker.js` listens and fires a non-blocking sonner toast ("New version available — Reload") on the second-and-later activation per tab. Long-lived iOS Safari tabs now pick up new JS bundles without manual intervention.

    • Polish

      Proactive session-expired redirect

      . `api.js` dispatches `viably:session-expired` when `/auth/refresh` fails with 401 or transport error; AuthProvider listens, calls `/api/auth/logout` to clear the access cookie (otherwise the edge middleware bounces /login → /dashboard in a loop because it only checks cookie *presence*), then redirects to /login. Works regardless of which tab/component first noticed the dead session.

    • Security

      CSRF-safe Cooldown reset on login/logout

      . `api.invalidate()` (called on login + logout) now also resets `refreshCooldownUntil` and any in-flight refresh promise so a new session can refresh on its first 401.

    Observability

    • Polish

      `ops/grafana/dashboards/viably.json` extended from 10 → 13 panels: per-route ava

      `ops/grafana/dashboards/viably.json` extended from 10 → 13 panels: per-route availability (target ≥ 99.5 %), per-route p95 latency, 30-day error-budget burn.

    • New

      `ops/prometheus/alerts.yml` (new) — paging rules for 5xx > 2 %, p99 > 5 s, CORS

      `ops/prometheus/alerts.yml` (new) — paging rules for 5xx > 2 %, p99 > 5 s, CORS allowlist drop; warnings for 429 storm, 401 spike, bootstrap cache cold, per-route SLO burn.

    • Polish

      `ops/grafana/README.md` updated with import + provisioning + Datadog OpenMetrics

      `ops/grafana/README.md` updated with import + provisioning + Datadog OpenMetrics equivalent + recommended thresholds.

    Refactor

    • Polish

      `routers/documents.py` (835 LOC) → `routers/_documents/{_shared,upload,save,statement,status}.py` (each < 235 LOC)

      `routers/documents.py` is now a 4-line re-export shim. Audit tests (`test_r26_11_audit_cleanup.py`) updated to track relocated `save_*_from_extraction` handlers.

    Code-review triage

    • Polish

      ADR-008

      staged decomposition of monolithic page components (dashboard 594 LOC, viability 960 LOC, admin/col 760 LOC).

    • Polish

      ADR-009

      middleware complexity reduction (`_make_csrf_origin_check`, `_observability`, `_deferred_startup`) with characterization-tests-first approach.

    Polish

    Viability calculator would have flagged Iowa cities as "unaffordable" for normal

    Viability calculator would have flagged Iowa cities as "unaffordable" for normal salaries

  • Polish

    Heading element is `<h3>` consistently.

    Heading element is `<h3>` consistently.

  • Polish

    `role="alert"` + `aria-live="assertive"` on error variant only.

    `role="alert"` + `aria-live="assertive"` on error variant only.

  • Polish

    Icons are `aria-hidden="true"` (decorative).

    Icons are `aria-hidden="true"` (decorative).

  • Polish

    No reliance on color alone — every variant has a distinct icon.

    No reliance on color alone — every variant has a distinct icon.

  • Polish

    Focus-visible ring (`focus-visible:ring-2 focus-visible:ring-primary/40`) on eve

    Focus-visible ring (`focus-visible:ring-2 focus-visible:ring-primary/40`) on every button.

  • Polish

    `/bills` empty: unified component used + CTA reachable + no fake numbers

    `/bills` empty: unified component used + CTA reachable + no fake numbers

  • Polish

    `/budget` empty: unified component used + CTA reachable

    `/budget` empty: unified component used + CTA reachable

  • Polish

    `/debts` empty: unified component used

    `/debts` empty: unified component used

  • Polish

    `/shifts` empty: unified component used + CTA reachable

    `/shifts` empty: unified component used + CTA reachable

  • Polish

    Semantic structure: every `data-empty-variant` element has an `<h3>` + button

    Semantic structure: every `data-empty-variant` element has an `<h3>` + button

  • Polish

    Slow requests

    Last 10 requests ≥ 500 ms. Redacted: only (path, method, status, duration_ms, ts) — never query strings, headers, or bodies.

  • Polish

    Long tasks

    Count, total ms, max ms.

  • Speed

    Server-side bootstrap cache

    Reads `/api/metrics` Prometheus exposition; surfaces `viably_bootstrap_cache_{hits,misses}_total{backend}` with hit-rate calc.

  • Polish

    `frontend/lib/diagnostics.js` — NEW (~180 LOC)

    PerformanceObserver setup, fetch-wrap counter, mark/measure helpers.

  • Polish

    `frontend/lib/api.js` — added `api.diagnostics()` introspection (cache hit/miss/

    `frontend/lib/api.js` — added `api.diagnostics()` introspection (cache hit/miss/eviction/primed counters).

  • Polish

    `frontend/lib/auth-context.js` — invokes `initDiagnostics()` once on first mount

    `frontend/lib/auth-context.js` — invokes `initDiagnostics()` once on first mount.

  • New

    `frontend/app/(app)/admin/diagnostics/page.js` — NEW (~270 LOC)

    Read-only dashboard.

  • Polish

    `frontend/app/(app)/layout.js` — Skip link + `<main>` landmark with `id` and `ta

    `frontend/app/(app)/layout.js` — Skip link + `<main>` landmark with `id` and `tabIndex={-1}`.

  • Polish

    `frontend/tests/e2e/07-diagnostics-a11y.spec.js` — NEW

    3 tests:

  • Polish

    Skip link is keyboard-reachable, becomes visible on focus, anchors to `#app-main

    Skip link is keyboard-reachable, becomes visible on focus, anchors to `#app-main` on activation.

  • Polish

    `/admin/diagnostics` redirects non-admin users.

    `/admin/diagnostics` redirects non-admin users.

  • Polish

    `/admin/diagnostics` renders for admins with all expected sections + zero PII leaks (asserts no email/JWT/session_token strings in HTML)

    Skipped in production-like preview env where admin login is disabled.

  • `cacheKey(url, config)` used `JSON.stringify(config.params)` which preserves object insertion order. `{a:1, b:2}` and `{b:2, a:1}` produced different cache entries → cache hit rate dropped silently for any page that built params from variable-key sources. Replaced with a recursive `_stableStringify` that sorts object keys at every depth (arrays preserve order — semantically meaningful).

    High-impact gates added

    • Polish

      R16 idle prefetch now respects network/visibility.

      Previously the idle-prefetch burst fired regardless of `saveData`, `effectiveType`, `document.visibilityState`, or `navigator.onLine`. Added `shouldSkipPrefetchBurst()` mirroring the gates `prefetchPageData` already enforces. Backgrounded tabs and 2G clients no longer eat unnecessary network/CPU.

    • Speed

      Admin perf-dashboard polling pauses on hidden.

      `app/(app)/admin/perf/page.js` had a raw `setInterval(load, 5_000)` that kept polling indefinitely on background tabs. Added a `visibilitychange` listener: pauses when hidden, fires one immediate `load()` on visibility-resume, then resumes the interval. Eliminates needless backend pressure from unattended admin tabs.

    Docs

    • Speed

      `docs/adr-005-frontend-perf-rules.md` — NEW canonical rules of the road

      Codifies the policies established across R10–R21 (no `<Link prefetch=true>`, hover triggers two warmers, idle prefetch gated, cache keys stable, bundle hygiene, visibility-aware polling, perf budgets, observability). Designed so a future contributor can keep wins compounding instead of regressing.

  • Speed

    Logout flow correctly invalidates the cache.

    Logout flow correctly invalidates the cache.

  • Polish

    `/api/bootstrap` returns all 26 fan-in fields with no `_errors`.

    `/api/bootstrap` returns all 26 fan-in fields with no `_errors`.

  • Polish

    `/api/shifts/bootstrap` returns 4 expected fields.

    `/api/shifts/bootstrap` returns 4 expected fields.

  • Polish

    `/api/reports/bootstrap` returns 3 expected fields.

    `/api/reports/bootstrap` returns 3 expected fields.

  • Polish

    Quick-start (50-100 users, 1-5 min).

    Quick-start (50-100 users, 1-5 min).

  • Speed

    Perf targets per endpoint.

    Perf targets per endpoint.

  • Polish

    How to interpret p95 spikes.

    How to interpret p95 spikes.

  • Polish

    Headless CI-mode invocation.

    Headless CI-mode invocation.

  • Polish

    `viably_bootstrap_cache_hits_total{backend}` — labelled `redis` or `local`.

    `viably_bootstrap_cache_hits_total{backend}` — labelled `redis` or `local`.

  • Polish

    `viably_bootstrap_cache_misses_total{backend}` — same label.

    `viably_bootstrap_cache_misses_total{backend}` — same label.

  • New

    Two new metric rows in the table.

    Two new metric rows in the table.

  • New

    A new alert rule: `ViablyBootstrapCacheNotWarming` fires if hit rate drops below

    A new alert rule: `ViablyBootstrapCacheNotWarming` fires if hit rate drops below 40% for 15 min (signals Redis down or `REDIS_URL` unset on a multi-pod deploy).

  • Polish

    Datadog openmetrics list extended.

    Datadog openmetrics list extended.