From 8a3bea3415e2ae087c813e2d3f3480f6704c3c78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 16 May 2026 10:26:21 +0200 Subject: [PATCH] docs(planning): reframe v1.5 plan around incremental v1.4.x patches v1.5 on the web is a version-bump-only marker, not a coordinated release sprint. Every functional change the iOS client depends on lands in v1.4.29 -> v1.4.30 -> v1.4.31 -> v1.4.32 ahead of the iOS app shipping. The web then enters freeze, the iOS engineering work runs in parallel against the live web API, and v1.5.0 ships the day after Apple review clears. The day-by-day iOS track schedule stays the same but now numbers iOS-engineer-days rather than calendar days; the calendar window stretches across whatever wall-clock time the web patches plus iOS tracks take in parallel. --- .planning/v15-strategic-plan.md | 247 +++++++++++++++++++------------- 1 file changed, 147 insertions(+), 100 deletions(-) diff --git a/.planning/v15-strategic-plan.md b/.planning/v15-strategic-plan.md index f901b5c58..5f170a7bb 100644 --- a/.planning/v15-strategic-plan.md +++ b/.planning/v15-strategic-plan.md @@ -10,53 +10,69 @@ sources: 6 R-A through R-F research outputs + v1429-backlog + iOS handoff pack ## 1. Executive summary +### Release-shape correction (maintainer directive) + +v1.5 on the web is **a version-bump-only marker**, not a coordinated +web-plus-iOS sprint. Every functional change the iOS client depends on +lands in a sequence of small v1.4.x patches that ship before iOS goes +to TestFlight. The web then enters freeze. The iOS engineering work +runs in parallel against the already-live web API. When the iOS app +clears Apple review and launches, the web bumps to v1.5.0 with a +CHANGELOG entry that names the milestone — no functional diff. This +removes the coordinated-release risk, lets iOS engineers consume +stable production endpoints from day one, and keeps each web patch +small enough to roll back independently. + ### Product-Lead view -The maintainer asked for three things: a clean closure of the v1.4 -line on the web, a focused iOS sprint that ships v1.5 as a real -release rather than a v0.4.x iOS build with a marketing label, and an -Apple-Health-depth story that says yes to workouts and breadth without -chasing reproductive-cycle or nutrition tracking. The recommendation -is to ship **v1.4.29 as a tight one-day patch** that closes the four -critical and high findings from the dashboard audit plus the mobile -tile equal-height and the settings drag-list compactness, **skip -v1.4.30**, freeze the web after v1.4.29, and run a **10 working day -iOS sprint** that pairs every HealthKit read with a visible iOS -surface so App Store review never sees an authorised-but-invisible -type. The differentiator stays the Coach drawer; workouts are the -single largest perceived-value-per-LOC add; the rest of Tier 1 is the -breadth play that lets the iOS app finally show the ten metrics it -already reads. +The maintainer wants the v1.4 line to ship the iOS-facing surface +incrementally, not as one large sprint at the end. v1.4.29 closes the +polish backlog (dashboard performance, mobile-tile symmetry, settings +drag-list, x-axis tick density, chart touch-action). v1.4.30 lands +the server-prep the iOS sprint depends on (SyncMode schema, MoodEntry +note column, two new MeasurementType enums, categorisation overlay, +APNs paste, daily-stats helper). v1.4.31 + v1.4.32 land the Tier 1 +HealthKit web surfaces (Workouts end-to-end, 10 chart cards for the +invisible-but-stored metrics, hearing-event chips, walking-steadiness +gauge, HKStateOfMind read surface). After v1.4.32 the web freezes: +emergency hotfixes only, no feature work. iOS development continues +in parallel through this window and ships when ready. The differentiator +stays the Coach drawer; workouts are the largest perceived-value-per-LOC +add; the rest of Tier 1 is the breadth play that finally surfaces what +iOS already reads. ### Senior-Dev view -Architectural posture is conservative. After v1.4.29 the web app -freezes — only security patches, dependency updates, and the additive -iOS-server-prep changes the sprint requires (two Prisma columns, two -new bulk endpoints, one sync-state endpoint, two new MeasurementType -enum entries, one new MoodEntry column) are allowed. No new web -features, no UI rewrites, no non-additive schema changes. The -locked-contracts file at `.planning/v15-ios-handoff/08-locked-contracts.md` -holds. Nothing in the proposed scope flags as breaking. The largest -single risk is the v1.4.29 P0 `aggregate=daily` regression on -`/api/measurements` — it 500s in production today and the v1.4.28 -suite mocked `prisma.$queryRaw` past the bug. Fixing C2 before wiring -C3 is the only correct sequence; both ride in v1.4.29 in that order. +Architectural posture is conservative through every v1.4.x patch. +Each patch is an atomic, independently-revertible unit: one Prisma +migration max, one Zod schema delta max, one endpoint added or one +endpoint set hardened. The locked-contracts file at +`.planning/v15-ios-handoff/08-locked-contracts.md` holds; every change +flags additive. The largest single risk is the v1.4.29 P0 +`aggregate=daily` regression on `/api/measurements` — it 500s in +production today and the v1.4.28 suite mocked `prisma.$queryRaw` past +the bug. Fixing C2 before wiring C3 is the only correct sequence; +both ride in v1.4.29. After v1.4.32 the web sits stable and the +freeze closes the surface area where new bugs can be introduced +during the iOS sprint. ### iOS-Dev view -10 working days + a 3-5 day Apple-review buffer. The iOS repo is -further along than the handoff pack assumed (28 finds in R-E, 5 -critical, but the Coach drawer is the only Critical that is "no -native code at all" — the rest are gaps to close, not greenfield -work). Day 1-2 server prep blocks everything; Day 3-5 splits into -three parallel tracks (Coach SSE drawer, SyncMode + Workouts iOS -ingest, daily-stats service for cumulative types); Day 6-8 closes -breadth; Day 9-10 polish and Apple-review prep. **Key risk**: App -Store rejection if a HealthKit type is authorised but the app has no -visible surface (R-F §2.5). **Locked decision**: every HK read pairs -with a surface in the same track. Without the Coach drawer native -build, v1.5 is materially v0.4.2 iOS. +Roughly 10 working days of iOS-engineering time, running parallel to +the v1.4.30 → v1.4.32 web patches. The iOS repo is further along than +the handoff pack assumed (28 finds in R-E, 5 critical, but the Coach +drawer is the only Critical that is "no native code at all"). iOS +work picks up each web patch's surface as soon as it deploys: Track A +(Coach SSE drawer) starts whenever the APNs paste in v1.4.30 lands; +Track B (SyncMode + Workouts ingest) starts when v1.4.30's +sync-state endpoints + Workouts API route deploy; Track C +(daily-stats service for cumulative types) starts when v1.4.30's +daily-stats helper deploys. By the time Track A/B/C are mid-flight, +v1.4.31 + v1.4.32 are deploying the chart-card surfaces the +Apple-review pairing rule demands. **Locked decision**: every HK +read pairs with a visible surface — no authorised-but-invisible +types. Without the Coach drawer native build, v1.5 is materially +v0.4.2 iOS. --- @@ -96,50 +112,93 @@ move aggregation to SQL, rewrite `summarize()` for single-pass). 2-3 days plus integration tests. Lands during the v1.5 server-prep window if calendar permits, otherwise rides to v1.5.x. -### v1.4.30 — Final web polish (skip recommendation) +### v1.4.30 — iOS-server-prep menu -Recommend **skip**. The R-A Option A web-side preparation folds into -v1.5's Day 1-2 server prep. The v1.4.29 deferrals in -`v1429-backlog.md` are design Mediums, UI-conformity Mediums, i18n -drift, and `` migration carry-forwards — none are -blockers for v1.5 and none are visible to the iOS app. Carry them to -v1.5.x. Ship v1.4.30 only if the iOS sprint slips past Day 5 and a -quiet web release helps confidence; default is to tag v1.5 directly -after the sprint. +Ship 2-3 days after v1.4.29. Every item is iOS-additive; the web UI +either does not consume the new surface or wires it in v1.4.31. + +| Item | Source | Effort | +|---|---|---| +| SyncMode server-side: `syncVersion Int @default(1)` on `Measurement`, `deletedAt DateTime?` soft-delete column, `/api/sync/state` endpoint, `/api/mood-entries/bulk` + `/api/medications/intake/bulk` bulk-backfill endpoints | R-E C-2 | M | +| `MoodEntry.note TEXT NULL` — replaces the `tags: ["note:"]` workaround that contaminates the Coach evidence shelf | R-E H-5 | S | +| `HKWorkoutType` server route — verify `/api/workouts/batch` (in v1.4.25 W8d) middleware + caps match the new pattern, add `pickCanonicalWorkoutRows()` for Apple Watch × Withings ScanWatch dedup | R-F T1.1 | S | +| Categorisation overlay: `src/lib/measurements/categories.ts` TypeScript map driving both the iOS permission picker and a future web Insights nav | R-F §4 | S | +| Two new MeasurementType enums: `WALKING_STEADINESS`, `AUDIO_EXPOSURE_EVENT` | R-F T1.4 + T1.5 | XS | +| R-A Option A server-side: `dailyStatsExternalId()` helper + `CUMULATIVE_HK_TYPES` set in `src/lib/measurements/apple-health-mapping.ts`; handoff-doc lock for the `externalId` shape | R-A §6 | S | +| APNs `.p8` paste into Coolify (1-hour Marc-action) | R-E C-3 | external | +| Real-Postgres integration-test container (paid down once, every aggregate test rides it) | R-B Critical+ | S | + +iOS impact: every change is additive on the wire. The +`/api/sync/state` endpoint is net-new (no iOS consumer yet), +SyncMode columns expand the `Measurement` shape with defaults so +existing iOS calls round-trip unchanged, the new MeasurementType +enums are net-new values iOS can encounter without 422 once the +codegen catches them. + +### v1.4.31 — HealthKit Tier 1 web surfaces, wave A + +Ship 2-3 days after v1.4.30. Adds the visible surfaces iOS will pair +with the corresponding HK reads. + +| Item | Source | Effort | +|---|---|---| +| Workouts end-to-end on the web — list page (`/insights/workouts`), detail page, dashboard "Recent workouts" tile | R-F T1.1 | M | +| Chart cards for 5 of the 10 invisible-but-stored metrics: HRV, RestingHR, SpO2, BodyTemperature, ActiveEnergyBurned | R-F T1.3 | M | + +### v1.4.32 — HealthKit Tier 1 web surfaces, wave B + freeze marker + +Ship 2-3 days after v1.4.31. Closes the breadth wave. + +| Item | Source | Effort | +|---|---|---| +| Chart cards for the remaining 5 invisible-but-stored metrics: FlightsClimbed, WalkingRunningDistance, AudioExposureEnv, AudioExposureHeadphone, TimeInDaylight | R-F T1.3 | S | +| Hearing-event chips on `/insights/puls` (or the relevant insights sub-page) | R-F T1.4 | S | +| Walking-steadiness gauge | R-F T1.5 | S | +| HKStateOfMind read surface integrated with `/insights/stimmung` (the existing mood page picks up APPLE_HEALTH-sourced mood entries cleanly via the source-priority pipeline) | R-F T1.2 | S | +| Source-priority editor — the `/api/auth/me/source-priority` endpoint already locks for iOS in v1.4.25 W8c; the web has nothing today. Add the two-axis editor under `/settings/sources` so the iOS side can mirror the same UI shape | R-E C-5 (web-side helper) | M | +| CHANGELOG **WEB-FREEZE marker** — explicit line noting that web functionality is complete for v1.5 and that subsequent v1.4.x tags are limited to hotfixes + dependency updates until the iOS app ships | this plan | — | + +After v1.4.32 tags on `main`, the web enters **freeze**. ### Web-freeze posture -After v1.4.29 tags on `main`, the web app enters **freeze**. - -Allowed during the v1.5 sprint: +Allowed during the freeze (v1.4.32 → v1.5.0): - Security patches (CVE feeds, dependency vulnerabilities) - Dependency updates that ride existing test infrastructure -- iOS-server-prep changes the sprint requires (additive endpoints + - Prisma columns documented in §3 Day 1-2) - Hotfix-only emergencies (the v1.4.28.1 dashboard-save pattern) +- Tightly-scoped reactive fixes if iOS testing surfaces a real gap + on a v1.4.32 endpoint — but only as additive corrections, never + as new feature work -Not allowed during the v1.5 sprint: +Not allowed during the freeze: - New web features - UI rewrites or design refactors -- Schema changes that aren't additive -- The C1 architectural lift on `/api/analytics` unless it folds - cleanly into the iOS-server prep +- Schema changes that are not additive +- The C1 architectural lift on `/api/analytics` (defers explicitly + to v1.5.x — see §4) -Document this posture in the v1.5 closure record. When the sprint -ends and v1.5 ships, the freeze ends — v1.5.x patches resume normal -web cadence. +### v1.5.0 — version bump marker + +Ships the day after iOS clears Apple review. Single commit on +`main`: `package.json` `1.4.32` → `1.5.0`, CHANGELOG entry stating +"iOS native client now live on the App Store; web functionality +unchanged since v1.4.32." No source diff outside the version bump +and CHANGELOG. Triggers GHCR rebuild + auto-deploy so the running +image's `/api/version` sentinel matches the public release tag. --- -## 3. v1.5 iOS sprint shape +## 3. iOS development track (parallel to v1.4.x patches) -10 working days. The day-by-day shape pulls from R-E §recommended -sprint length and R-F Tier 1 track structure, with R-F's "wave" -vocabulary kept in the table headers because the substring is -load-bearing on the source documents; substituting "track" or -"stream" in the prose preserves the maintainer's discipline. +Roughly 10 working days of iOS-engineering time. Runs in parallel +with the v1.4.30 → v1.4.32 web patch sequence rather than as a +coordinated release sprint. Each iOS track picks up its supporting +web surface as soon as the corresponding patch deploys. The day +numbering below is **iOS-engineer-day**, not calendar day; the +calendar window stretches across whatever wall-clock time the web +patches + iOS tracks take in parallel. ### Day-by-day schedule @@ -282,8 +341,9 @@ navigates to a feature that needs it (R-F §2.5 option 2). | iOS HK reads pairing rule | Every read paired with a visible surface in the same track — no authorised-but-invisible | R-F §2.5 + open Q #3 | | Categorisation shape | UI-side TypeScript overlay `src/lib/measurements/categories.ts`, NOT a DB column | R-F §4 | | Sleep storage shape | Per-stage rows (5-axis unique key) — no split into `SLEEP_DEEP` / `SLEEP_REM` / etc. enum entries | R-F open Q #6 | -| Web freeze trigger | v1.4.29 tag on `main` | This plan §2 | -| v1.4.30 ship-or-skip | Skip; tag v1.5 directly after the sprint | This plan §2 | +| Web freeze trigger | v1.4.32 tag on `main` (after the Tier 1 wave B closes) | This plan §2 | +| v1.5 release shape | Version-bump-only marker the day after iOS clears Apple review. All functional work lands incrementally in v1.4.29 → v1.4.32. | Maintainer directive 2026-05-16 | +| Web patch sequence | v1.4.29 polish → v1.4.30 iOS server-prep → v1.4.31 Tier 1 wave A → v1.4.32 Tier 1 wave B + freeze | This plan §2 | | AVG/SUM cumulative-type fix | Rides v1.4.29 | R-A §6 + R-B C2 sequence | | C2 P0 sequencing | Lands before any client wires `aggregate=daily` | R-B Critical | | iOS workout dedup ladder | Ship `pickCanonicalWorkoutRows()` with the existing measurement ladder (Apple ≻ Withings ≻ Manual); tune metric-aware (route → Apple wins; HR zones → Withings wins) in a v1.5.x follow-up | R-F open Q #4 | @@ -298,41 +358,28 @@ changes. Nothing in this plan flags as breaking. ## 6. Open questions for the maintainer -1. **One pass through this plan for sharpening.** Anywhere the - sequencing reads wrong, anywhere the deferral feels too generous, - anywhere the iOS-day estimate looks aspirational. The plan is - built to fit a 10-day sprint and the calendar assumes clean - parallel dispatch on Day 3-5; if either assumption needs softening - the calendar shifts by 1-3 days. - -2. **Whether to ship v1.4.30 at all.** Default recommendation is - skip — fold the R-A Option A server prep into v1.5's Day 1-2. - Counterargument: a quiet v1.4.30 with the R-A web-side helpers + - the C1 `/api/analytics` rewrite would let the iOS sprint start - with a clean web baseline. The tradeoff is one week of additional - calendar before the iOS sprint clock starts versus a smaller - v1.5 server-prep day. - -3. **Whether to drop unused HK reads instead of adding cards.** R-F +1. **Whether to drop unused HK reads instead of adding cards.** R-F §2.5 calls out the App Store reviewer risk for authorised-but- - invisible types. The proposed plan (R-F T1.3 chart-card breadth) - is the higher-perceived-value path; the alternative (trim - `defaultReadTypes` to the visibly-used subset) is the lower-risk - path. If the calendar tightens, the trim is the fallback. + invisible types. The proposed plan (R-F T1.3 chart-card breadth + in v1.4.31 + v1.4.32) is the higher-perceived-value path; the + alternative (trim `defaultReadTypes` to the visibly-used subset) + is the lower-risk path. If a v1.4.x patch tightens, the trim is + the fallback. -4. **Whether to defer C-4 RefreshScheduler to v1.5.1.** R-E flags - this as Critical (midnight-401 bug). The mitigation (the existing - 401-bridge in `APIClient.swift:135-158`) is reactive but works. - If the Day 6 schedule slips, deferring the proactive scheduler - to v1.5.1 is survivable. +2. **Whether to defer C-4 RefreshScheduler to a post-v1.5 iOS + patch.** R-E flags this as Critical (midnight-401 bug). The + mitigation (the existing 401-bridge in `APIClient.swift:135-158`) + is reactive but works. If iOS Track B's timeline tightens, + deferring the proactive scheduler to a v1.5.x iOS-side release + is survivable. -5. **Whether the Apple-Health-XML import (`export.zip`) belongs in - v1.5.** R-F §2.6 and open Q #7. Out of the original brief, but the - highest-leverage non-iOS-app deliverable in the broader research. - A web-only user can upload years of HK history before the iOS app - ships on their device. ~200 LOC server-side, reuses the existing - batch endpoint. Lift into v1.5 if the calendar permits; defer to - v1.6 if not. +3. **Whether the Apple-Health-XML import (`export.zip`) belongs + before the freeze.** R-F §2.6 and open Q #7. Out of the original + brief, but the highest-leverage non-iOS-app deliverable in the + broader research. A web-only user can upload years of HK history + before the iOS app ships on their device. ~200 LOC server-side, + reuses the existing batch endpoint. Lift into v1.4.32 if the + calendar permits; defer to v1.6 if not. ---