Six textarea call-sites had been copy-pasting the same className with
the text-base sm:text-sm iOS-zoom defence; the audit flagged it as a
drift risk. New shadcn-style primitive in src/components/ui/textarea.tsx
mirrors <Input>'s shape and bakes in the defence, the WCAG 2.5.5 tap-
target floor (min-h-11 sm:min-h-9), autoCapitalize="sentences" /
spellCheck defaults for prose, and the autoComplete="off" + password-
manager ignore posture from <Input>.
Migrates four call-sites onto the primitive (bugreport, medications
JSON import, side-effect notes, admin feedback note) — saves ~16 LOC
of inline copy-paste and ~850 chars of repeated className strings.
The coach-input textarea stays as-is: it's an auto-grow composer with
a transparent bg wrapped in an outer "fake input" div, so migrating
would require nullifying nearly every primitive default.
11 new unit tests lock the contract (iOS zoom class, tap-target floor,
forwardRef sentinel, override semantics, attribute pass-through).
Four audit reports from the v1.4.43 discovery round: analytics 9s root cause, mobile-UI WCAG sweep, QoL copy gaps, security PII + log-growth.
Also adds *.har to gitignore so production HAR captures (which carry auth headers and cookies) do not land in the repo by accident.
B3 (typed-classification catch-block migration) applied to both
activity + sleep sync paths. B4 (IntegrationStatus surface
unification) and B7 (IntegrationStatus row freshness audit) are
explicitly deferred to v1.4.44 with the architecturally-correct
recipe restated so the next wave can pick them up cleanly.
Orchestrator hit 78% context with 9 implementation waves dispatched
and running in worktrees. Fresh session picks up at cherry-pick +
W10 QA round + reconcile + release endgame.
Handoff doc inventories every dispatched wave with its agent id and
scope, the post-merge develop+main rebase pattern that recurred in
PR #193, the Marc-voice + PII cleanup checklist for cherry-pick
review, and the docker BuildKit cache verify-loop per the v1.4.42
post-deploy lesson.
Estimated 2-3h fresh session for the closure work.
Carry-over from the v1.4.42 marathon W10 reconcile — the six W10
reviewer reports (code-reviewer, security, design, senior-dev,
product-lead, simplifier-residual) for the release-trail audit-input
that gated the v1.4.42 tag.
Captures the gate-flip outcome: 35 unused exports → 0, 52 unused types
→ 0, exit code 0 on the final `pnpm knip` run. Documents the
`ignoreExportsUsedInFile` + `ignoreIssues` ignore-block rationale,
inventories every dropped symbol, and flags the two W3 / W4 reconcile
callouts (`describeInjectionSite` re-export, `listSupportedTimezones`
re-export) along with the merge-order constraint they imply.
Records the helper landing + widgets-route swap and stages the
v1.4.43 rollout queue (41 sites in src/app/api/** still on the
issues[0].message pattern). Includes the rewrite recipe + iOS-contract
prioritisation so the follow-up wave can pick up without re-discovery.
Carry-over from the v1.4.41 marathon reconcile — the six W10 reviewer
reports (code-reviewer, security, design, senior-dev, product-lead,
simplifier-residual) were not committed before the squash-merge to
main. Lands them on develop so the next marathon's audit-trail has
the inputs that gated the v1.4.41 tag in one place.
Captures the helper extract + 6 sibling-route wire + cache-shape
reconcile + test pins. Notes the medication-compliance stub-row
recogniser fix and the pulse-status-timeout assertion flip as
risk callouts.
Mirror the bmi-status (v1.4.37) timeout-stub persist into the two
sibling endpoints. On a 20 s provider stall the route now writes a
sentinel auditLog row keyed to today's Berlin day so the next mount
short-circuits at the cache lookup instead of re-racing the same
provider call for the rest of the day.
This eliminates the ~14 s warm response on every iOS dashboard open
when the provider has stalled once that day. Response envelope
unchanged; iOS v0.5.4 contract preserved.
Tests cover both arms: the persist-on-timeout path (one create with
timeout:true, model:"timeout-stub", today's dateKey) and the
short-circuit-on-stub path (subsequent mount returns cached text,
provider never invoked).
Append a Postgres connection-pool sizing section to the self-hosting
scaling guide. Documents the v1.4.40 W-POOL default (20 slots per
container), the container_count × DATABASE_POOL_MAX × max_connections
envelope (with a 1-5 container ready-reckoner table), the env-var
override path, and PgBouncer as the escape hatch for deployments that
outgrow the per-process pool model. Cross-references src/lib/db.ts and
the empirical trace that motivated the bump.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-v1.4.41 prompts lived in two homes — `src/lib/ai/prompts/` (per-
metric system prompts + safety contracts) and `src/lib/insights/prompt
*.ts` (user-prompt builder + comparison snapshot + the `compactSections`
helper). A new reader could not tell from filenames which file to
extend; both directories looked equally authoritative. Audit rec #3
flagged the split as the only undiscoverable seam in the AI layer.
Move both files into `src/lib/ai/prompts/`:
- `lib/insights/prompt.ts` → `lib/ai/prompts/insight-system-prompt.ts`
- `lib/insights/prompt-compact.ts` → `lib/ai/prompts/compact-sections.ts`
- co-located tests follow
Three importers updated: `app/api/insights/generate/route.ts`,
`lib/ai/coach/snapshot.ts`, the generate route test (the
`vi.mock('@/lib/insights/prompt')` path moved too). Prompts now have
ONE home and ONE mental model.
Captures SB-7 follow-up (check-user endpoint shipped), SB-8 (merge
semantics documented, no code change), SB-9 (already retired, no
streak surface exists), and the dashboard-widgets PUT 422 root cause:
the Zod validator is correct and additive-safe; the live 422s from
iOS v0.5.4 are an iOS-side payload bug (likely an unknown widget id
or an out-of-range order value). No server fix viable.
ls src/app/api/streak returns No such file or directory. No web
frontend code paths reference /api/streak/...; the only streak
references in the API tree are derived fields inside
/api/insights/targets (streakDays, streakHighlight).
Decision: nothing to deprecate. The route family either never existed
or was removed before the audit. iOS can safely treat /api/streak/*
as 404. Document filed so a future audit does not re-open the
question.
The dashboard layout resolver in src/lib/dashboard-layout.ts is
append-only on read: new widget ids added to DASHBOARD_WIDGET_IDS
automatically appear for every existing user on next GET, with
visible / tileVisible / order taken from DEFAULT_DASHBOARD_LAYOUT. No
database migration is ever required when introducing a new widget.
Documents the merge contract so a future widget-introduction does not
ship a one-shot SQL migration that would clobber every user's
customisation. PUT preservation rules captured: chartOverlayPrefs is
preserved-on-omit, comparisonBaseline is not. Investigation-only — no
code change.
Adds the v1.4.40 row + the four prior v1.4.39.x stacked rows to
.planning/round-audit-marathon-closure.md (v1.4.39.1/.2/.3/.4 were
documented inline in the corresponding memory files but the table
hadn't picked them up).
Commits the six v1.4.40 QA reviewer findings files (senior, security,
specialist, UX, product-lead, simplifier) so the audit trail stays
self-contained.
Adds round-v1440-perf-verify.md anchored on the live deploy commit
1114cf7e (squash-merge), Coolify deploy timestamp 2026-05-21T12:55:02Z,
docker workflow 26221332323. Includes the AP-2 .p8 install runbook for
SB-5 live effect.
Creates the new src/lib/rollups/ aggregator directory and relocates
the main measurement rollup module:
- src/lib/measurements/rollups.ts → src/lib/rollups/measurement-rollups.ts
- src/lib/measurements/__tests__/rollups.test.ts →
src/lib/rollups/__tests__/measurement-rollups.test.ts
Every importer of @/lib/measurements/rollups is rewritten to
@/lib/rollups/measurement-rollups. The orphan sibling reference inside
src/lib/measurements/rollup-read.ts (still in its old home until the
next batch) is promoted to the @/lib alias so the chain keeps
typechecking through the in-flight move.
Pure relocation — no behaviour change, no exported-surface change. The
umbrella exists so the rollup contract is discoverable from a single
directory before the iOS sprint locks the boundary.
Inventory of touched files, test matrix, and commit map for the
v1.4.40 soft-delete-filter sweep that closes Senior Infra+DB audit
Critical Finding #3.
Pins the SB-5 (apns time-sensitive interruption-level) and SB-6
(/api/notifications/status events map) contracts.
Note: the SB-6 route + test changes landed inside an earlier
`fix(dashboard-summary)` commit (1bcaae47) due to a cross-wave
pre-commit-hook race that auto-folded my staged changes; this
commit ships the wave report + records the proper SB-5 + SB-6
intent in the log for the release-notes sweep to pick up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five assertions on the route handler:
- 200 on an unauthenticated GET.
- Content-Type is exactly application/json with no charset parameter
(Apple's swcd/aasa-validator refuse a charset and the failure mode
is silent — Universal Links just stop firing).
- Cache-Control allows public CDN mirroring.
- JSON shape matches the SB-4 spec exactly.
- applinks App ID matches webcredentials App ID so a rotated
Team ID never half-lands.
Includes the v1.4.40 phase report.
Five planning artefacts produced during the v1.4.39 review pass:
frontend, ghosts (dead-code drift), infra-db, organization, and a
cross-cutting empirical trace. Kept under .planning/ rather than the
versioned docs site because they are working notes scoped to a single
release window.
Closes the audit-marathon closure series with rows for v1.4.38.5
through v1.4.38.8 (the four-hotfix chain that landed between v1.4.38.4
and v1.4.39) plus v1.4.39 itself.
Adds round-v1439-perf-verify.md anchored on the live deploy
(commit hash d84922cd, deploy uuid ujgsxpa46esyzybjldgxjeox) and the
public-surface 10x burst median 86 ms. Authenticated heavy-endpoint
verification awaits the first browser session against the live deploy.
Six QA reviewers (senior / security / specialist / ux / product-lead /
simplifier) ran on the v1.4.39 diff; their finding docs land here for
traceability. The v1.4.40 backlog captures every Medium + Low the
reconcile deliberately deferred — F-SEC-M-02 RLS (v1.5), F-M-01
long-window consumer (v1.5), F-M-02 unused tx parameter, F-M-03 partial
index post-convergence, F-M-05 RollupBucketRow consolidation, F-M-06
per-agent worktree process note, UX-M1-3 compliance + mood polish,
Specialist-M01 UNION arm cleanup, Simplifier dead-exports + duplicate
tz helpers, plus low-severity observability polish. UX-M4 is marked
obsolete (Specialist-H2 widen closed it).
Captures the cumulative-metric sum_value rollup work — writer +
boot-backfill discovery widening + consumer swaps in
`/api/measurements?groupBy=day` and `/api/dashboard/summary` +
new `rollup-read-cumulative.ts` helpers. Documents the
`/api/analytics` A2 skip-cumulative deferral to v1.4.40 (file
owned by W-SINCE in this phase) and the per-source W8c deferral
to v1.5. Surfaces the cross-agent commit-message drift recurrence
for the marathon retrospective.
The per-type loop inside `buildAnalyticsResponse` calls
`fetchMeasurementSeriesChunked` without a `since` filter, so a single
default-slice request pulls every measurement ever for the user across
15 chunked rounds. The v1.4.38.8 per-type fast-path gate made this
unreachable in the common case, but a regression on those gates would
re-trigger Marc's 74.6 s cold full-slice mount.
- Add `ANALYTICS_LIVE_WINDOW_DAYS = 90` + `liveSince` constant.
- Thread `since: liveSince` into every per-type chunked read.
- Annotate `meta.analytics.bp_aggregate.live_since` (ISO) so the next
perf-verify can prove the cap is in effect on a live request.
Defense-in-depth only — the rollup fast-path is unchanged because it
never reaches the per-type loop. Worst-case bound drops from ~347 k
rows to ~5 k rows on Marc's tenant (matching the
comprehensive-aggregator + summaries-slice 90-day windows).
Trade-off: `summarize().avg30LastYear` returns null on the fall-through
path; the dashboard's year-over-year overlay shows "no prior data"
until the rollup tier warms. Acceptable on a degraded path that
v1.4.38.8 already made rare.
Tests: new `src/__tests__/api/analytics/since-cap.test.ts` pins the
90-day floor on every per-type findMany, pins the `live_since`
annotate, and pins that the slim slice is untouched. Existing chunked
paging integration test reseeded with timestamps inside the 90-day
window so it still exercises the two-chunk boundary.
Three additive Prisma additions land in one commit so the v1.4.39 wave
agents (W-MOOD, W-MED, W-SUM) build against a stable generated client:
- `mood_entry_rollups`: per-(user, granularity, bucket) mood stats
(count / mean / min / max / sd). Reuses the existing
`measurement_rollup_granularity` enum.
- `medication_compliance_rollups`: per-(user, medication, day)
scheduled / taken / skipped triple. `day` is the user-tz-anchored
YYYY-MM-DD string so multi-instance reads do not re-derive boundaries.
- `measurement_rollups.sum_value`: nullable column. Cumulative-metric
read paths consume it directly; boot backfill folds NULLs on first
reach.
Migrations 0070 / 0071 / 0072 use IF NOT EXISTS + DO $$ EXCEPTION WHEN
duplicate_object guards (mirror 0061 / 0067 / 0069). No ALTER on
existing tables that downgrade row contracts, no DROP. Backward-safe
read paths land in the wave agents that follow.
Includes `.planning/round-v1438-perf-analysis.md` — the read-only audit
that anchors the wave plan.