Performance audit fixes: fewer redraws, fewer database trips, faster CI (D-146)
Merged
BI
1d ago+1851+1851+0123456789012345678901234567890123456789-637-637-012345678901234567890123456789
Requested by Josh · project thread
Builds the 11 November 2026 performance audit fixes that Josh approved ("Go ahead with all the issues and fixes for all the findings", recorded as D-146). Synthetic data only. No signed clinical value or clinical rule changes.
Signed-in screens (fewer redraws)
- An unchanged ("not modified") 3-second poll keeps the same snapshot object; only the last-checked time moves, through a small separate context (
ward-shared-freshness.ts, provided insideWardSharedAccess). Before, every poll rebuilt the whole provider value. - The shell values memo depends only on the connection fields it uses;
useDeskInScopeno longer depends on the clock. - Hidden tabs stop the poll timer and restart it when the tab becomes visible.
useStatisticsLivereads its 11 fields throughuseWardFlowSelect.- The ward screen's heavy figures (capacity, bed states, occupancy, breakdown, roster, form alerts) are memoised in
ward/use-ward-screen-figures.ts.
Backend (fewer database round trips)
- Unchanged member poll: about 4 statements down to 1. The sign-out check is cached for 5 s per sign-in, and is immediate for a sign-out through the same server. The membership, break-glass and workspace-stamp reads are one statement.
- Executive polls are answered from the cached report when nothing has changed.
- Save inside the workspace lock: 12 round trips down to 8. The workspace update and record changes are one statement, and the audit row and command receipt are one statement. It is still one transaction with its audit row. The push check stays inside the save (P011).
- Supabase pool
maxraised from 4 to 10. The portable preset stays at 4. Supabase's session pooler total is often about 15, so more than one backend copy at 10 each needs checking. - Migration 018 adds three read-path indexes (break-glass actor, access-history audit rows, record views), with a rollback file, preflight entries and the setup/retention undo chains updated. It has not been applied anywhere.
- The front-door signing key is imported once per secret.
CI
- Browser journeys and the release gate run two shards per job (
WARD_JOURNEY_SHARDS=2). The phone release gate (20.9 min) was the critical path in a measured 26-minute PR run. Release gate timeout 25 to 35 min. - Unit shards wait only for static checks, not the production build.
- A PR that changes only unit tests skips the production build.
- The static typecheck reuses a TypeScript incremental cache, saved from main.
Docs
- D-146 in
decisions.md, with the M84 phone speed target. D-145 is claimed by open PR #1623. - A capacity and scaling row in the production-readiness register (backend and demo app each one copy until limits move to a shared store).
- One sign-in start helper in
src/lib/front-door-pass.ts, with a small Ward-local copy (ward-sign-in-started-at.ts) so the ward's first load doesn't carry the front-door signing code;tests/ward-sign-in-started-at.test.tsruns the same cases on both.
Not done, on purpose
- Seed data stays in the first load. The live signed-in build already swaps it out (
LIVE_BUILD_DEMO_DATA), so lazy loading would only move the demo's bytes to a second request. - The 60-second full read stays, because hand-off windows depend on it for privacy.
- The legal sweeps still run with coverage, because a separate step would idle workers and could break the engine coverage floor.
- The jsdom setup is unchanged, because nothing safe could be trimmed (measured).
- The daily scheduled run is not skipped, because the release uses it as acceptance.
- Follow-ups: #1618 (send only changed records), #1619 (one tab polls for all), #1620 (decision log archive).
Evidence
- Local, fresh:
npm run verify:pr-local(lint, typecheck, backend typecheck, docs checks, 45 related test files: 447 tests): passed.- Backend suite against a local Postgres 16 (
WARD_TEST_DATABASE_URL=postgresql://postgres@127.0.0.1:5433/wardflow_test npm --prefix backend/ward-flow test): 837/837 passed. NODE_OPTIONS=--max-old-space-size=8192 npm run build: passed.- Phone limits (
node scripts/check-script-budget.mjsafter the build): after the latest main merge this PR keeps main's limits and raises only the ward screen by its own +0.7 KB (616.5 to 617.2), under the 5 KB allowance; history indocs/script-budget-history/2026-11-11-pr1624-performance-audit.md. - After merging main cc755fa: ESLint on changed files,
node docs/ward-flow/journey/verify.mjs --check, and 21 related test files (262 tests): passed. - Simulated mid-range phone (demo mode, 4x CPU): first paint 1.0 to 1.2 s and blocking 0.65 to 0.91 s, essentially unchanged. These fixes target signed-in mode and the server, which were not measured live.
- Run by the helper agents: about 330 focused front-end test files and the CI contract checks.
- Not run locally: the full unit suite and browser journeys (hosted CI runs them); any hosted database or Supabase.
Generated by Claude Code
🤖 Generated with Claude Code