diff --git a/e2e/achievements.spec.ts b/e2e/achievements.spec.ts index 9dacf0042..17e7c3390 100644 --- a/e2e/achievements.spec.ts +++ b/e2e/achievements.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from "./setup/test"; import { STORAGE_STATE_PATH } from "./setup/global-setup"; /** - * v1.4.15 phase-B4 — /achievements navigation smoke. + * v1.4.15 — /achievements navigation smoke. * * The page itself has unit-test coverage for the locked-vs-unlocked * card render; this spec answers the higher-level "can the user reach diff --git a/e2e/admin-api-tokens-mobile.spec.ts b/e2e/admin-api-tokens-mobile.spec.ts index 3f82cdb2d..7c45949ab 100644 --- a/e2e/admin-api-tokens-mobile.spec.ts +++ b/e2e/admin-api-tokens-mobile.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from "./setup/test"; import { STORAGE_STATE_PATH } from "./setup/global-setup"; /** - * v1.4.16 A3 — `/admin/api-tokens` must not produce horizontal page + * v1.4.16 — `/admin/api-tokens` must not produce horizontal page * overflow on a Pixel-5 viewport (393 CSS px). v1.4.15 added column- * hide breakpoints + tighter card padding, but the maintainer still saw a * scrollbar on prod. The v1.4.16 fix swaps the desktop `` for a @@ -75,7 +75,7 @@ test.describe("/admin/api-tokens mobile no-overflow", () => { ).toBeLessThanOrEqual(dims.innerWidth + 1); }); - // v1.4.18 phase A2 — production probe at Pixel-5 viewport pinned the + // v1.4.18 — production probe at Pixel-5 viewport pinned the // visible "scrollbar" the maintainer reported for the third time as the // `` mobile section strip, NOT the api-tokens table. The // strip carries 13 admin sections so its scrollWidth (~1700 CSS px) @@ -119,7 +119,7 @@ test.describe("/admin/api-tokens mobile no-overflow", () => { expect(stripDims.scrollWidth).toBeGreaterThan(stripDims.clientWidth); }); - // v1.4.19 phase A7 (4th attempt) — even with the mobile-strip + // v1.4.19 (4th attempt) — even with the mobile-strip // `no-scrollbar` fix from v1.4.18, the maintainer still reported a painted // scrollbar. The card body itself must not contribute any // horizontal-overflow culprit: every element inside the api-tokens diff --git a/e2e/ai-provider-dropdown.spec.ts b/e2e/ai-provider-dropdown.spec.ts index adacbdd2c..e8f6fa944 100644 --- a/e2e/ai-provider-dropdown.spec.ts +++ b/e2e/ai-provider-dropdown.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from "./setup/test"; import { STORAGE_STATE_PATH } from "./setup/global-setup"; /** - * v1.4.16 phase B2 — AI provider settings UX with single dropdown + * v1.4.16 — AI provider settings UX with single dropdown * driving the form below. Three things to lock in here: * * 1. /settings/ai exposes the active-provider Pulldown diff --git a/e2e/charts-mobile.spec.ts b/e2e/charts-mobile.spec.ts index f9c5cabd3..9572f49e3 100644 --- a/e2e/charts-mobile.spec.ts +++ b/e2e/charts-mobile.spec.ts @@ -4,7 +4,7 @@ import { STORAGE_STATE_PATH } from "./setup/global-setup"; import { mockDashboardSnapshot } from "./utils/mock-dashboard-snapshot"; /** - * v1.4.19 A2 — mobile-viewport regression spec for chart cards. + * v1.4.19 — mobile-viewport regression spec for chart cards. * * Two assertions per chart visible in the dashboard's initial paint: * diff --git a/e2e/dashboard.spec.ts b/e2e/dashboard.spec.ts index 4b69093d9..0aa90fe5d 100644 --- a/e2e/dashboard.spec.ts +++ b/e2e/dashboard.spec.ts @@ -98,7 +98,7 @@ test.describe("authenticated dashboard render", () => { }), ); - // S2 — the Today hero reads the unified daily digest from + // The Today hero reads the unified daily digest from // `/api/daily/digest`. Mock it with a deterministic digest (a score + // one worth-a-look item) so the hero paints without reaching the real // route, and so a slow/absent digest can never hang the render or @@ -276,7 +276,7 @@ test.describe("authenticated dashboard render", () => { const tileCount = Number(await strip.getAttribute("data-tile-count")); expect(tileCount).toBeGreaterThan(0); - // S2 — the Today hero paints above the tile strip from the mocked + // The Today hero paints above the tile strip from the mocked // digest: its score ring and its worth-a-look rail render. const todayHero = page.locator('[data-slot="today-hero"]'); await expect(todayHero).toBeVisible({ timeout: 10_000 }); diff --git a/e2e/insights-scroll-restoration.spec.ts b/e2e/insights-scroll-restoration.spec.ts index 8dd6e160f..2868c66fe 100644 --- a/e2e/insights-scroll-restoration.spec.ts +++ b/e2e/insights-scroll-restoration.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from "./setup/test"; import { STORAGE_STATE_PATH } from "./setup/global-setup"; /** - * v1.4.28 R3a FB-D3 — `/insights` scroll restoration + tab-strip + * v1.4.28 — `/insights` scroll restoration + tab-strip * gesture handling. * * Two regressions the maintainer hit walking through v1.4.27: diff --git a/e2e/measurement-flow.spec.ts b/e2e/measurement-flow.spec.ts index 62e4d2137..414dba6f6 100644 --- a/e2e/measurement-flow.spec.ts +++ b/e2e/measurement-flow.spec.ts @@ -132,7 +132,7 @@ test.describe("add measurement flow", () => { await openMenu(page, addTrigger); - // v1.5 phase-5: the menu items now have distinct labels — the + // v1.5: the menu items now have distinct labels — the // measurement entry says "Measurement" / "Messung" instead of "Add", // so we can target it directly. // The desktop rail opens a dropdown, the phone shell the capture sheet diff --git a/e2e/mobile-viewport.spec.ts b/e2e/mobile-viewport.spec.ts index 91234b8bd..c32981aba 100644 --- a/e2e/mobile-viewport.spec.ts +++ b/e2e/mobile-viewport.spec.ts @@ -41,7 +41,7 @@ test.describe("mobile-viewport smoke", () => { // mocks below stay for the reversible `=false` path. await mockDashboardSnapshot(page, { summaries: WEIGHT_ONLY_SUMMARIES }); - // v1.4.37 W-CI — match `/api/analytics` AND any sliced variant + // v1.4.37 — match `/api/analytics` AND any sliced variant // (`?slice=summaries`). The previous string glob `**/api/analytics` // didn't match the query-string form so the IW1 slim-slice fetch // landed on the unmocked real route in CI. @@ -149,7 +149,7 @@ test.describe("mobile-viewport smoke", () => { // scroll to interact, which is its own UX problem the design // review handles. if (box.y < 0 || box.y > viewportH) continue; - // v1.4.37 W-CI — enforce the WCAG 2.5.5 height floor only. + // v1.4.37 — enforce the WCAG 2.5.5 height floor only. // The original `width < 44 || height < 44` check flagged // grouped compact controls (chart-range tabs like "7T"/"30T" // /"90T" sitting at 44 px tall but ~30-40 px wide) as diff --git a/e2e/mood-card-mobile.spec.ts b/e2e/mood-card-mobile.spec.ts index e3ffa57fd..94aa354c8 100644 --- a/e2e/mood-card-mobile.spec.ts +++ b/e2e/mood-card-mobile.spec.ts @@ -3,7 +3,7 @@ import { expect, test } from "./setup/test"; import { STORAGE_STATE_PATH } from "./setup/global-setup"; /** - * v1.4.15 phase-A3 fix #2 — mood-list mobile card was rendering the score + * v1.4.15 #2 — mood-list mobile card was rendering the score * twice: once in the left badge (large bold digit) and once in the title * line as "{score} ({label})". On desktop the table view only ever showed * one. The fix collapses the title line down to just the localized label diff --git a/e2e/onboarding-flicker.spec.ts b/e2e/onboarding-flicker.spec.ts index bd0ee08ac..4e0829556 100644 --- a/e2e/onboarding-flicker.spec.ts +++ b/e2e/onboarding-flicker.spec.ts @@ -7,7 +7,7 @@ import { } from "./utils/mock-dashboard-snapshot"; /** - * v1.4.15 phase-A3 fix #3 — onboarding card flicker on dashboard load. + * v1.4.15 #3 — onboarding card flicker on dashboard load. * * The previous version of `` rendered against a * default-true `shouldShowChecklist` decision while `measurementCount` @@ -64,7 +64,7 @@ test.describe("dashboard onboarding card flicker guard", () => { // works by NOT rendering until this resolves, so even with the // delay the card stays invisible the whole time. // - // v1.4.37 W-CI — match `/api/analytics` AND any sliced variant + // v1.4.37 — match `/api/analytics` AND any sliced variant // (`?slice=summaries`). The previous string glob `**/api/analytics` // missed the IW1 slim-slice URL so the checklist hook's analytics // query fell through to the real route in CI; on a fresh seed user @@ -161,7 +161,7 @@ test.describe("dashboard onboarding card flicker guard", () => { // card mounts collapsed: header + progress meter visible, full row // list hidden until the user clicks the chevron. // - // v1.4.22 C4 — the onboarding redirect now lives in `proxy.ts`, + // v1.4.22 — the onboarding redirect now lives in `proxy.ts`, // not in ``. The proxy reads the `hl_onboarding` cookie // (which the auth routes mirror from the DB). Our storageState // belongs to a fully-onboarded user (no `hl_onboarding` cookie), diff --git a/e2e/settings-export.spec.ts b/e2e/settings-export.spec.ts index f2ca7ee19..8fb99c4b2 100644 --- a/e2e/settings-export.spec.ts +++ b/e2e/settings-export.spec.ts @@ -56,7 +56,7 @@ test.describe("Settings → Export & Import", () => { test("renders all export surfaces with stable testids", async ({ page }) => { await page.goto("/settings/export", { waitUntil: "domcontentloaded" }); - // v1.18.0 (S5) — the full health-record export moved to its own + // v1.18.0 — the full health-record export moved to its own // `/settings/gesundheitsakte` section. The Export & Import page keeps // the four CSV/backup tiles with the `export-card-*` shape. for (const id of [ @@ -72,7 +72,7 @@ test.describe("Settings → Export & Import", () => { test("renders the health-record export panel on its own section", async ({ page, }) => { - // v1.18.0 (S5) — the health-record export is the hero of the + // v1.18.0 — the health-record export is the hero of the // dedicated, module-gated Gesundheitsakte section. await openGesundheitsakte(page); await expect(page.getByTestId("health-record-export-panel")).toBeVisible({ diff --git a/e2e/settings-mobile-consistency.spec.ts b/e2e/settings-mobile-consistency.spec.ts index 7294d69ba..873b869cb 100644 --- a/e2e/settings-mobile-consistency.spec.ts +++ b/e2e/settings-mobile-consistency.spec.ts @@ -6,7 +6,7 @@ import { WEIGHT_ONLY_SUMMARIES, } from "./utils/mock-dashboard-snapshot"; /** - * v1.4.19 A6 — Settings consistency snapshot at Pixel-5 (393 CSS px). + * v1.4.19 — Settings consistency snapshot at Pixel-5 (393 CSS px). * v1.4.27 — input height floor lifted from 36 px (`h-9`) to 40 px * (`h-10`) on `Input`, `Select` trigger, and the native ``; - // L1 — show the VERIFIED origin (or an explicit "unverified" label for DCR) + // Show the VERIFIED origin (or an explicit "unverified" label for DCR) // next to the self-asserted name so the user authorizes who, not just what. const provenance = v.clientSource === "cimd" @@ -469,7 +469,7 @@ async function authorizePost(request: NextRequest): Promise { ); } - // L3 — defence-in-depth same-origin assertion on the consent decision. The + // Defence-in-depth same-origin assertion on the consent decision. The // session cookie is already SameSite=Lax (a cross-site POST carries none), // but reject an explicit cross-origin Origin / Sec-Fetch-Site outright so a // forged "allow" cannot ride a present session. diff --git a/src/app/api/mcp/oauth/register/route.ts b/src/app/api/mcp/oauth/register/route.ts index 7e9e1d13c..1833373fa 100644 --- a/src/app/api/mcp/oauth/register/route.ts +++ b/src/app/api/mcp/oauth/register/route.ts @@ -90,12 +90,12 @@ export async function POST(request: NextRequest): Promise { ); } - // M4 — the operator API kill-switch covers the OAuth surface too. + // The operator API kill-switch covers the OAuth surface too. if (!(await isApiGloballyEnabled())) { return oauthError("temporarily_unavailable", "API is disabled", 503); } - // M3 — enforce the cap WHILE reading the (unauthenticated) registration + // Enforce the cap WHILE reading the (unauthenticated) registration // body so a hostile client cannot buffer an oversized payload first. const read = await readBodyCapped(request, MAX_BODY_BYTES); if (!read.ok) { diff --git a/src/app/api/mcp/oauth/token/route.ts b/src/app/api/mcp/oauth/token/route.ts index fbdaf01af..e0f3dce94 100644 --- a/src/app/api/mcp/oauth/token/route.ts +++ b/src/app/api/mcp/oauth/token/route.ts @@ -206,7 +206,7 @@ export async function POST(request: NextRequest): Promise { const refusal = refuseOAuthOnKeyMismatch(); if (refusal) return refusal; - // M1 — fail closed without a pinned origin; M4 — honour the operator's + // Fail closed without a pinned origin; M4 — honour the operator's // global API kill-switch. Either off and the bridge mints no tokens. if (!isMcpOriginConfigured() || !(await isApiGloballyEnabled())) { return oauthError( diff --git a/src/app/api/mcp/tokens/route.ts b/src/app/api/mcp/tokens/route.ts index e79f93e6c..9b54dbc2f 100644 --- a/src/app/api/mcp/tokens/route.ts +++ b/src/app/api/mcp/tokens/route.ts @@ -48,7 +48,7 @@ export const GET = apiHandler(async () => { return apiError("API is globally disabled", 403); } - // M2 — list only manually-minted connector tokens. OAuth access rows + // List only manually-minted connector tokens. OAuth access rows // (`mcpConnectionId != null`) are transient 60-minute credentials that would // flood this list; they are surfaced (and revoked) as connections instead. const tokens = await prisma.apiToken.findMany({ diff --git a/src/app/api/measurement-reminders/[id]/route.ts b/src/app/api/measurement-reminders/[id]/route.ts index 9922ff88c..7e1b8dc9e 100644 --- a/src/app/api/measurement-reminders/[id]/route.ts +++ b/src/app/api/measurement-reminders/[id]/route.ts @@ -278,7 +278,7 @@ export const PATCH = apiHandler( await auditLog("measurementReminder.update", { userId: user.id, ipAddress: getClientIp(request), - // C4 — what the edit replaced. A cadence change moves when somebody's + // What the edit replaced. A cadence change moves when somebody's // phone rings; the feed has to be able to say what it used to be. details: { reminderId: id, @@ -349,7 +349,7 @@ export const DELETE = apiHandler( await auditLog("measurementReminder.delete", { userId: user.id, ipAddress: getClientIp(request), - // C3 — a hard delete, so the details carry the identity of what went: + // A hard delete, so the details carry the identity of what went: // the label the owner wrote, what it watched, when it was next due. An // id alone would say a preventive-care recall disappeared and never say // what it was for. diff --git a/src/app/api/measurements/[id]/__tests__/route.test.ts b/src/app/api/measurements/[id]/__tests__/route.test.ts index c7dfd55af..01b3cb23b 100644 --- a/src/app/api/measurements/[id]/__tests__/route.test.ts +++ b/src/app/api/measurements/[id]/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.43 W6 — multi-issue 422 envelope on PUT /api/measurements/[id]. + * v1.4.43 — multi-issue 422 envelope on PUT /api/measurements/[id]. */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { NextRequest } from "next/server"; diff --git a/src/app/api/measurements/[id]/route.ts b/src/app/api/measurements/[id]/route.ts index 5b0431332..821017691 100644 --- a/src/app/api/measurements/[id]/route.ts +++ b/src/app/api/measurements/[id]/route.ts @@ -82,7 +82,7 @@ export const PUT = apiHandler( if (jsonError) return jsonError; const parsed = updateMeasurementSchema.safeParse(body); if (!parsed.success) { - // v1.4.43 W6 — measurement edit hot path; multi-issue 422 + + // v1.4.43 — measurement edit hot path; multi-issue 422 + // audit breadcrumb keyed `measurements.update.validation-failed`. const issues = sanitiseZodIssues(parsed.error.issues); annotate({ @@ -192,7 +192,7 @@ export const PUT = apiHandler( }, }); } catch (err) { - // v1.4.28 FB-B1 — re-pointing `measuredAt` onto an existing + // v1.4.28 — re-pointing `measuredAt` onto an existing // `(userId, type, measuredAt, source, sleepStage)` tuple raises // `P2002`. Mirror the POST handler's catch so the row-edit Sheet // surfaces a clean 409 with a translatable `errorCode` instead of @@ -214,7 +214,7 @@ export const PUT = apiHandler( await auditLog("measurement.update", { userId: user.id, ipAddress: getClientIp(request), - // C4 — the overwritten scalars. Without them the owner's feed says a + // The overwritten scalars. Without them the owner's feed says a // reading changed and cannot say from what. The note is named and never // quoted: it is encrypted on the row and copying it here would make the // audit table a second store for it. @@ -239,12 +239,12 @@ export const PUT = apiHandler( }, }); - // v1.4.34 IW-G — bust per-user analytics + achievements + workouts + // v1.4.34 — bust per-user analytics + achievements + workouts // caches so subsequent reads reflect the edited row. Interactive // edit — hard-evict so the SWR readers don't serve the pre-edit body. invalidateUserMeasurements(user.id, { evict: true }); - // v1.37.19 (C2-F1) — shared post-mutation tail. Both the OLD and the + // v1.37.19 — shared post-mutation tail. Both the OLD and the // NEW identity ride the list: an edit can move the row across day // boundaries or re-type it, and the helper collapses identical // (type, day) pairs so the common in-place edit fires once. @@ -308,13 +308,13 @@ export const DELETE = apiHandler( }, }); - // v1.4.34 IW-G — bust per-user analytics + achievements + workouts + // v1.4.34 — bust per-user analytics + achievements + workouts // caches so subsequent reads reflect the deletion. Interactive // delete — hard-evict so the SWR readers don't serve the pre-delete // body. invalidateUserMeasurements(user.id, { evict: true }); - // v1.37.19 (C2-F1) — shared post-mutation tail (the recompute drops + // v1.37.19 — shared post-mutation tail (the recompute drops // the rollup row when the day's measurement count goes to zero). await afterMeasurementMutation( user.id, diff --git a/src/app/api/measurements/__tests__/put-duplicate-timestamp.test.ts b/src/app/api/measurements/__tests__/put-duplicate-timestamp.test.ts index 076466f92..2424881af 100644 --- a/src/app/api/measurements/__tests__/put-duplicate-timestamp.test.ts +++ b/src/app/api/measurements/__tests__/put-duplicate-timestamp.test.ts @@ -122,7 +122,7 @@ describe("PUT /api/measurements/[id] — duplicate-timestamp handling", () => { }); it("returns 409 on a sport-style measurement edit (FB-B1)", async () => { - // v1.4.28 FB-B1 — the maintainer's "Sport edit + save → error" + // v1.4.28 — the maintainer's "Sport edit + save → error" // report maps onto measurement-typed sport rows (Apple Health // ingests workouts as ACTIVE_ENERGY_BURNED / FLIGHTS_CLIMBED / // WALKING_RUNNING_DISTANCE samples, all of which route through diff --git a/src/app/api/measurements/__tests__/range-aggregation-route.test.ts b/src/app/api/measurements/__tests__/range-aggregation-route.test.ts index 968963986..d5f0db0d6 100644 --- a/src/app/api/measurements/__tests__/range-aggregation-route.test.ts +++ b/src/app/api/measurements/__tests__/range-aggregation-route.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { NextRequest } from "next/server"; /** - * v1.4.28 R4-CODE-C1 / C2 — the `/api/measurements` route must + * v1.4.28 — the `/api/measurements` route must * aggregate via Postgres `date_trunc` BEFORE applying the bucket cap. * The legacy code path `findMany({ take: limit })` then in-memory * bucketised — a 1-year `aggregate=daily` window would silently truncate @@ -144,7 +144,7 @@ describe("GET /api/measurements — aggregation gate (C2)", () => { }; expect(prisma.$queryRaw).toHaveBeenCalledTimes(1); expect(prisma.measurement.findMany).not.toHaveBeenCalled(); - // C1 — the response carries one bucket per calendar day, NOT + // The response carries one bucket per calendar day, NOT // `min(limit, count)/grain`. The raw-data density (1440 rows/day) // is summarised in the `count` field. expect(json.data.measurements.length).toBe(365); diff --git a/src/app/api/measurements/__tests__/zod-multi-issue.test.ts b/src/app/api/measurements/__tests__/zod-multi-issue.test.ts index 34c4b295e..894a71648 100644 --- a/src/app/api/measurements/__tests__/zod-multi-issue.test.ts +++ b/src/app/api/measurements/__tests__/zod-multi-issue.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.43 W6 — multi-issue 422 envelope on the measurements route. + * v1.4.43 — multi-issue 422 envelope on the measurements route. * * GET, batch POST and single POST now return every Zod issue under * `details.issues` and write an audit-ledger breadcrumb so the diff --git a/src/app/api/measurements/batch/__tests__/pr-detection-hook.test.ts b/src/app/api/measurements/batch/__tests__/pr-detection-hook.test.ts index a36974a0c..e7c8f58f2 100644 --- a/src/app/api/measurements/batch/__tests__/pr-detection-hook.test.ts +++ b/src/app/api/measurements/batch/__tests__/pr-detection-hook.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.25 W16c — PR detection enqueue coverage for + * v1.4.25 — PR detection enqueue coverage for * `POST /api/measurements/batch`. The full per-entry envelope is * exercised by the integration suite; this file checks the hook fires * with the right `silent` flag for the small / large batch cases and diff --git a/src/app/api/measurements/batch/route.ts b/src/app/api/measurements/batch/route.ts index bbc465868..f6e04823f 100644 --- a/src/app/api/measurements/batch/route.ts +++ b/src/app/api/measurements/batch/route.ts @@ -116,7 +116,7 @@ import { } from "@/generated/prisma/client"; import { isoDateTime } from "@/lib/validations/datetime"; -// v1.4.25 W16c — historical-backfill threshold for PR push +// v1.4.25 — historical-backfill threshold for PR push // suppression. A batch larger than this fires the detection job with // `silent: true` so a multi-year Apple Health backfill writes records // without spamming the user with hundreds of pushes. Tuned generously @@ -134,7 +134,7 @@ const MAX_BATCH_ENTRIES = 500; // The cap is a DoS ceiling, not a tight bound. const MAX_BODY_BYTES = 4 * 1024 * 1024; -// v1.4.25 W10 reconcile (security H-2): cap batch ingest at 60 +// v1.4.25 (security H-2): cap batch ingest at 60 // batches per user per minute. Healthy iOS sync drains its // HealthKit observer queue in well under one batch per minute (the // observer pattern coalesces), so 60/min × 500 entries/batch = @@ -144,7 +144,7 @@ const MAX_BODY_BYTES = 4 * 1024 * 1024; const BATCH_RATE_LIMIT_MAX = 60; const BATCH_RATE_LIMIT_WINDOW_MS = 60 * 1000; -// v1.8.6 W6 — the set of `source` values this client-facing ingest +// v1.8.6 — the set of `source` values this client-facing ingest // route accepts. Deliberately narrower than the full `MeasurementSource` // enum: `WITHINGS` and `IMPORT` are server-owned (the Withings webhook // and the CSV importer mint those rows respectively), so letting an iOS @@ -182,7 +182,7 @@ const batchEntrySchema = z.object({ // @external-id-checked-per-entry: src/app/api/measurements/batch/route.ts externalId: z.string().min(1).max(120), externalSourceVersion: z.string().min(1).max(120).optional(), - // v1.8.6 W6 — optional per-entry source tag. Defaults to + // v1.8.6 — optional per-entry source tag. Defaults to // `APPLE_HEALTH` so every pre-W6 caller (web + current iOS) stays // byte-for-byte unchanged. The iOS standalone adopt-on-pair backfill // sends `MANUAL` for rows the user entered by hand on-device so they @@ -190,7 +190,7 @@ const batchEntrySchema = z.object({ // `(userId, type, source, externalId)` dedup key, so a `MANUAL` row // and an `APPLE_HEALTH` row sharing an externalId are distinct rows. source: batchSourceEnum.optional(), - // v1.4.25 W8c — optional device-type tag. The iOS client maps the + // v1.4.25 — optional device-type tag. The iOS client maps the // `HKDevice.model` of each sample to one of the canonical device // classes (watch | band | ring | phone | scale | other | unknown). // Backward-compatible: every pre-W8c iOS build skips the field and @@ -301,7 +301,7 @@ async function postBatch(request: NextRequest): Promise { // its rows may carry, and whether it may move the phone's sync checkpoint. const scoped = isScopedCredential(auth); - // v1.4.25 W10 reconcile (security H-2): per-user rate limit. Without + // v1.4.25 (security H-2): per-user rate limit. Without // it, a leaked wildcard iOS token can sustain unbounded writes // (500 rows/batch × N batches/sec) and degrade the database for // every other user on the host. 60 batches/min/user is generous @@ -593,7 +593,7 @@ async function postBatch(request: NextRequest): Promise { valueMin, valueMax, unit: mapped.unit, - // v1.8.6 W6 — honour the per-entry source tag, defaulting to + // v1.8.6 — honour the per-entry source tag, defaulting to // `APPLE_HEALTH` when absent so legacy callers are unchanged. // // v1.38.x — a scoped credential gets `EXTERNAL`, and can hold no other @@ -611,7 +611,7 @@ async function postBatch(request: NextRequest): Promise { // v1.10.0 — categorical events (WX-B). The device's own verdict / // severity for an EVENT row; null for every continuous reading. rhythmClassification: mapped.rhythmClassification ?? null, - // v1.4.25 W8c — pass the iOS-supplied device-type through to + // v1.4.25 — pass the iOS-supplied device-type through to // the row. Stays null for pre-W8c iOS builds; the canonical // picker treats null as `unknown` so legacy ingest keeps // working without a server-side default. @@ -1281,7 +1281,7 @@ async function postBatch(request: NextRequest): Promise { }); } - // v1.4.25 W16c — kick off PR detection for this user when at least one + // v1.4.25 — kick off PR detection for this user when at least one // row was written, so a single off-day reading still gets evaluated; the // warm-up gate inside the detector decides whether it's a record. Suppress // push notifications for historical backfills above the silent threshold. @@ -1340,7 +1340,7 @@ async function postBatch(request: NextRequest): Promise { }, }); - // v1.4.34 IW-G — bust per-user analytics + achievements + workouts + // v1.4.34 — bust per-user analytics + achievements + workouts // caches when at least one row landed so the next read picks up the // ingested batch. Skipped-only ingests don't change state, so we // gate the invalidation on `insertedCount > 0 || updatedCount > 0` @@ -1369,7 +1369,7 @@ async function postBatch(request: NextRequest): Promise { wroteManualRow; invalidateUserMeasurements(user.id, evict ? { evict: true } : undefined); - // v1.37.19 (C2-F1) — shared post-mutation tail for every distinct + // v1.37.19 — shared post-mutation tail for every distinct // (type, day) the batch touched. Updates participate as well as // inserts, so this deliberately retains the prepared-row scope. // A batch is the phone's sync, held to the sync gap, unless it wrote one @@ -1379,7 +1379,7 @@ async function postBatch(request: NextRequest): Promise { source: wroteManualRow ? "manual" : "sync", }); - // S4 — Apple-Health sleep is the third sleep transport. If any SLEEP_DURATION + // Apple-Health sleep is the third sleep transport. If any SLEEP_DURATION // row for last night just landed, kick the debounced morning refresh so the // digest/score finalise with the current sleep. The trigger judges "last // night" in the user's profile tz, so a historical Apple export replaying diff --git a/src/app/api/measurements/bulk-delete/route.ts b/src/app/api/measurements/bulk-delete/route.ts index fd378ea9d..ae87d1361 100644 --- a/src/app/api/measurements/bulk-delete/route.ts +++ b/src/app/api/measurements/bulk-delete/route.ts @@ -109,13 +109,13 @@ async function postBulkDelete(request: NextRequest): Promise { }); if (count > 0) { - // v1.4.34 IW-G — bust per-user analytics + achievements + workouts + // v1.4.34 — bust per-user analytics + achievements + workouts // caches so subsequent reads reflect the deletions. Interactive // multi-select delete from the management list — hard-evict so the // SWR readers don't serve the pre-delete body. invalidateUserMeasurements(user.id, { evict: true }); - // v1.37.19 (C2-F1) — shared post-mutation tail (identities were + // v1.37.19 — shared post-mutation tail (identities were // collected BEFORE the destructive statement, while readable). await afterMeasurementMutation( user.id, diff --git a/src/app/api/measurements/by-external-ids/route.ts b/src/app/api/measurements/by-external-ids/route.ts index 3a1caf88d..13df87de2 100644 --- a/src/app/api/measurements/by-external-ids/route.ts +++ b/src/app/api/measurements/by-external-ids/route.ts @@ -161,13 +161,13 @@ async function deleteByExternalIds(request: NextRequest): Promise { meta: { processed: externalIds.length, deleted: result.count }, }); - // v1.4.34 IW-G — bust per-user analytics + achievements + workouts + // v1.4.34 — bust per-user analytics + achievements + workouts // caches when at least one row deleted so the next read reflects the // reconciliation. A 0-delete batch is a no-op and skips the eviction. if (result.count > 0) { invalidateUserMeasurements(user.id); - // v1.37.19 (C2-F1) — shared post-mutation tail (identities collected + // v1.37.19 — shared post-mutation tail (identities collected // BEFORE the destructive statement, while readable). await afterMeasurementMutation(user.id, affectedRows, { source: "sync" }); } diff --git a/src/app/api/measurements/restore/route.ts b/src/app/api/measurements/restore/route.ts index be8a4321a..5f3433411 100644 --- a/src/app/api/measurements/restore/route.ts +++ b/src/app/api/measurements/restore/route.ts @@ -114,7 +114,7 @@ async function postRestore(request: NextRequest): Promise { // hard-evict so the SWR readers don't serve the pre-restore body. invalidateUserMeasurements(user.id, { evict: true }); - // v1.37.19 (C2-F1) — shared post-mutation tail; mirrors the + // v1.37.19 — shared post-mutation tail; mirrors the // bulk-delete path. await afterMeasurementMutation( user.id, diff --git a/src/app/api/measurements/route.ts b/src/app/api/measurements/route.ts index 7d8467d88..e707e63d9 100644 --- a/src/app/api/measurements/route.ts +++ b/src/app/api/measurements/route.ts @@ -456,7 +456,7 @@ async function postMeasurement(request: NextRequest) { measurements: body, }); if (!parsed.success) { - // v1.4.43 W6 — iOS batch ingest (BP + Pulse, combined BG) needs + // v1.4.43 — iOS batch ingest (BP + Pulse, combined BG) needs // every issue echoed back so a one-shot retry can fix the full // payload rather than walk it field-by-field. const issues = sanitiseZodIssues(parsed.error.issues); @@ -516,7 +516,7 @@ async function postMeasurement(request: NextRequest) { notesEncrypted: encryptNote(m.notes ?? null), glucoseContext: (m.glucoseContext as GlucoseContext | undefined) ?? null, - // v1.4.25 W10 reconcile (code-review M4): mirror the + // v1.4.25 (code-review M4): mirror the // single-entry path so the multi-entry batch persists // `deviceType` instead of silently dropping it. deviceType: m.deviceType ?? null, @@ -552,7 +552,7 @@ async function postMeasurement(request: NextRequest) { }, }); - // v1.4.34 IW-G — flush every cache that reflects this user's + // v1.4.34 — flush every cache that reflects this user's // measurement set so the next read paints the new rows. Interactive // multi-entry create (combined BP + pulse form) — hard-evict so the // SWR readers don't serve the pre-write body back to the user. @@ -603,7 +603,7 @@ async function postMeasurement(request: NextRequest) { { action: "measurement.safety_floor.check" }, ); - // v1.37.19 (C2-F1) — the shared post-mutation tail: rollup recompute + // v1.37.19 — the shared post-mutation tail: rollup recompute // (collapsed per (type, day)) + status-insight re-warm, both // best-effort. One helper so a future write surface cannot remember // one leg and forget the other. @@ -619,7 +619,7 @@ async function postMeasurement(request: NextRequest) { // Single mode (existing behavior) const parsed = createMeasurementSchema.safeParse(body); if (!parsed.success) { - // v1.4.43 W6 — single-row POST is the iOS fallback path; one + // v1.4.43 — single-row POST is the iOS fallback path; one // round-trip per stale-field bug was the v1.4.41 iOS-contract // pain point. Multi-issue envelope + audit breadcrumb. const issues = sanitiseZodIssues(parsed.error.issues); @@ -676,7 +676,7 @@ async function postMeasurement(request: NextRequest) { notes: null, notesEncrypted: encryptNote(notes ?? null), glucoseContext: (glucoseContext as GlucoseContext | undefined) ?? null, - // v1.4.25 W10 reconcile (code-review M4): the deviceType column + // v1.4.25 (code-review M4): the deviceType column // already accepts client metadata from the batch route. Mirror // the behaviour here so single-entry POST persists the tag // instead of silently dropping it. @@ -712,7 +712,7 @@ async function postMeasurement(request: NextRequest) { }, }); - // v1.4.34 IW-G — flush every cache that reflects this user's + // v1.4.34 — flush every cache that reflects this user's // measurement set so the next read paints the new row. Interactive // single-entry create — hard-evict so the SWR readers don't serve the // pre-write body back to the user. diff --git a/src/app/api/medications/[id]/__tests__/route.test.ts b/src/app/api/medications/[id]/__tests__/route.test.ts index 81104bf40..0713a9094 100644 --- a/src/app/api/medications/[id]/__tests__/route.test.ts +++ b/src/app/api/medications/[id]/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.43 W6 — multi-issue 422 envelope on PUT /api/medications/[id]. + * v1.4.43 — multi-issue 422 envelope on PUT /api/medications/[id]. */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { NextRequest } from "next/server"; diff --git a/src/app/api/medications/[id]/api-endpoint/route.ts b/src/app/api/medications/[id]/api-endpoint/route.ts index d7736af97..3d845251a 100644 --- a/src/app/api/medications/[id]/api-endpoint/route.ts +++ b/src/app/api/medications/[id]/api-endpoint/route.ts @@ -23,7 +23,7 @@ export const GET = apiHandler( } const { id } = await params; - // v1.4.25 W21 Fix-N — privacy gate hoisted to the shared helper. + // v1.4.25 — privacy gate hoisted to the shared helper. const guard = await assertMedicationOwnership(id, user.id); if (guard) return guard; @@ -63,7 +63,7 @@ export const PUT = apiHandler( } const { id } = await params; - // v1.4.25 W21 Fix-N — privacy gate hoisted to the shared helper. + // v1.4.25 — privacy gate hoisted to the shared helper. const guard = await assertMedicationOwnership(id, user.id); if (guard) return guard; diff --git a/src/app/api/medications/[id]/cadence/__tests__/route.test.ts b/src/app/api/medications/[id]/cadence/__tests__/route.test.ts index fefddabde..aa4146750 100644 --- a/src/app/api/medications/[id]/cadence/__tests__/route.test.ts +++ b/src/app/api/medications/[id]/cadence/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.25 W19e — cadence + compliance API route tests. + * v1.4.25 — cadence + compliance API route tests. * * Mirrors the W19d side-effect test fixture pattern: external * dependencies are mocked at module boundaries (Prisma, session, diff --git a/src/app/api/medications/[id]/cadence/route.ts b/src/app/api/medications/[id]/cadence/route.ts index 96daeb9b1..652623365 100644 --- a/src/app/api/medications/[id]/cadence/route.ts +++ b/src/app/api/medications/[id]/cadence/route.ts @@ -1,5 +1,5 @@ /** - * v1.4.25 W19e — GLP-1 cadence + compliance read endpoint. + * v1.4.25 — GLP-1 cadence + compliance read endpoint. * * GET /api/medications/[id]/cadence?days=30 * - returns the schedule's 30-day (configurable) expected-vs-actual @@ -48,7 +48,7 @@ export const GET = apiHandler( const { user } = await requireRecordAuth("read", "medications"); const { id } = await params; - // v1.4.25 W21 Fix-N — privacy gate hoisted to the shared helper. + // v1.4.25 — privacy gate hoisted to the shared helper. const guard = await assertMedicationOwnership(id, user.id); if (guard) return guard; @@ -76,7 +76,7 @@ export const GET = apiHandler( days: url.searchParams.get("days") ?? undefined, }); if (!parsed.success) { - // v1.4.43 W6 — multi-issue 422. + // v1.4.43 — multi-issue 422. return returnAllZodIssues(parsed.error, 422); } const windowDays = parsed.data.days ?? 30; @@ -117,7 +117,7 @@ export const GET = apiHandler( // chart disagree on the bi-weekly grid. const anchor = med.createdAt; - // v1.4.25 W21 Fix-O — resolve the user's IANA zone so the cadence + // v1.4.25 — resolve the user's IANA zone so the cadence // helpers compute every local-day boundary, window-time application, // and streak day-key in the user's clock instead of the host's // system time. A Tokyo user reading the same medication gets the @@ -125,7 +125,7 @@ export const GET = apiHandler( // was a sneaky bug at the 08:00-Berlin / 16:00-Tokyo intersection. const userTz = await resolveUserTimezone(user.id); - // v1.7.0 SB-SCHED-2 — route the detail-page compliance through the + // v1.7.0 — route the detail-page compliance through the // canonical recurrence engine so RRULE / rolling / cyclic / one-shot / // PRN schedules read the same expected-slot grid the dashboard tile + // intake path use. Without this the detail page falls back to the diff --git a/src/app/api/medications/[id]/compliance/route.ts b/src/app/api/medications/[id]/compliance/route.ts index 367f82e11..7d9eb4c0f 100644 --- a/src/app/api/medications/[id]/compliance/route.ts +++ b/src/app/api/medications/[id]/compliance/route.ts @@ -74,7 +74,7 @@ export const GET = apiHandler( schedules: true, // v1.16.3 — archived schedule eras for era-aware compliance. scheduleRevisions: { orderBy: { validFrom: "asc" } }, - // v1.25 H-MED1 — pause eras so paused days drop out of the denominator. + // v1.25 — pause eras so paused days drop out of the denominator. pauseEras: { select: { pausedAt: true, resumedAt: true } }, // v1.40 (#1024) — the courses, so a gap between two expects nothing. courses: COURSES_COMPLIANCE_SELECT, diff --git a/src/app/api/medications/[id]/glp1/__tests__/route.test.ts b/src/app/api/medications/[id]/glp1/__tests__/route.test.ts index 3d52191ee..2b28fec55 100644 --- a/src/app/api/medications/[id]/glp1/__tests__/route.test.ts +++ b/src/app/api/medications/[id]/glp1/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.25 W21 Fix-K — GLP-1 convenience-route POST tests. + * v1.4.25 — GLP-1 convenience-route POST tests. * * Pins the sec-H1 hardening pass: Zod parse, 30/min/user rate-limit, * `medication.glp1.update` audit row, bounded `doseValue` / `note` / @@ -603,7 +603,7 @@ describe("GET /api/medications/[id]/glp1 — v1.16.10 item-backed inventory", () expect(body.data.inventory).toBeNull(); }); - // W1 — the legacy delta ledger the POST below still accepts must stay + // The legacy delta ledger the POST below still accepts must stay // readable: an account that only ever posted `{inventory: {delta}}` // sees its pen count on GET instead of a silent null. it("falls back to the legacy ledger when the user has zero inventory items", async () => { diff --git a/src/app/api/medications/[id]/glp1/route.ts b/src/app/api/medications/[id]/glp1/route.ts index c8db311ee..ffeff1384 100644 --- a/src/app/api/medications/[id]/glp1/route.ts +++ b/src/app/api/medications/[id]/glp1/route.ts @@ -1,5 +1,5 @@ /** - * v1.4.25 W4d — GLP-1 medication details endpoint. + * v1.4.25 — GLP-1 medication details endpoint. * * Returns per-medication extras the GLP-1 card variant + the dashboard * tile + the doctor-report PDF section read: chronological dose-change @@ -8,7 +8,7 @@ * /api/medications/[id] GET is unchanged so v1.4.24 consumers keep * working untouched. * - * v1.4.25 W21 Fix-K — POST hardened with Zod parse, audit-log, 30/min + * v1.4.25 — POST hardened with Zod parse, audit-log, 30/min * rate-limit, finite-number guard on `doseValue`, length cap on `note`, * and sane bounds on `effectiveFrom`. Mirrors the sibling * `/inventory` + `/side-effects` route shape. @@ -68,7 +68,7 @@ export const GET = apiHandler( const { user } = await requireRecordAuth("read", "medications"); const { id } = await params; - // v1.5.5 F-1 C-4 — close the §10 invariant 24 gap. The destructive + // v1.5.5 — close the §10 invariant 24 gap. The destructive // sweep in af224964 lifted every sibling `[id]/**` handler onto the // shared ownership helper; the glp1 GET still hand-rolled the // `findUnique + userId compare` pattern. Routing it through the @@ -290,7 +290,7 @@ async function postGlp1Update( const parsed = glp1PostBodySchema.safeParse(body); if (!parsed.success) { - // v1.4.43 W6 — multi-issue 422. + // v1.4.43 — multi-issue 422. return returnAllZodIssues(parsed.error, 422); } diff --git a/src/app/api/medications/[id]/ingredients/route.ts b/src/app/api/medications/[id]/ingredients/route.ts index 5f24af7c6..6ef260b4a 100644 --- a/src/app/api/medications/[id]/ingredients/route.ts +++ b/src/app/api/medications/[id]/ingredients/route.ts @@ -79,7 +79,7 @@ async function putIngredients( }); } - // C1 — keyed on the ACTOR, so a manager burns their own allowance. + // Keyed on the ACTOR, so a manager burns their own allowance. const rl = await checkRateLimit( `medication-ingredients:put:${actor.id}`, PUT_RATE_LIMIT, @@ -145,7 +145,7 @@ async function putIngredients( await auditLog("medication.ingredients.update", { userId: user.id, ipAddress: getClientIp(request), - // C4 — the composition this replaced, as one comparable line per side. + // The composition this replaced, as one comparable line per side. details: { medicationId: id, source, diff --git a/src/app/api/medications/[id]/intake/[eventId]/__tests__/route.test.ts b/src/app/api/medications/[id]/intake/[eventId]/__tests__/route.test.ts index 2e2806a5e..9e92263a7 100644 --- a/src/app/api/medications/[id]/intake/[eventId]/__tests__/route.test.ts +++ b/src/app/api/medications/[id]/intake/[eventId]/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.43 W6 — multi-issue 422 envelope on PUT + * v1.4.43 — multi-issue 422 envelope on PUT * /api/medications/[id]/intake/[eventId]. */ import { describe, it, expect, vi, beforeEach } from "vitest"; diff --git a/src/app/api/medications/[id]/intake/[eventId]/route.ts b/src/app/api/medications/[id]/intake/[eventId]/route.ts index 7835f65df..615bbac81 100644 --- a/src/app/api/medications/[id]/intake/[eventId]/route.ts +++ b/src/app/api/medications/[id]/intake/[eventId]/route.ts @@ -58,7 +58,7 @@ export const PUT = apiHandler( if (jsonError) return jsonError; const parsed = updateIntakeEventSchema.safeParse(body); if (!parsed.success) { - // v1.4.43 W6 — per-event update hot path; multi-issue 422 + + // v1.4.43 — per-event update hot path; multi-issue 422 + // audit breadcrumb keyed // `medications.intake.event.update.validation-failed`. const issues = sanitiseZodIssues(parsed.error.issues); @@ -315,7 +315,7 @@ export const PUT = apiHandler( isExplicitTaken: !nextSkipped && nextTakenAt !== null, isExplicitSkip: nextSkipped, idempotencyKey: null, - // C8 — the converged row keeps the ORIGINAL's provenance. This used + // The converged row keeps the ORIGINAL's provenance. This used // to be a hardcoded `"WEB"`, which said "somebody typed this in the // browser" about an Apple Health dose whose time was corrected. Under // MANAGE the sentence would be wrong about a second person as well. @@ -351,7 +351,7 @@ export const PUT = apiHandler( await auditLog("medication.intake.update", { userId: user.id, ipAddress: getClientIp(request), - // C4 — what the correction moved. A dose edit changes when a dose is + // What the correction moved. A dose edit changes when a dose is // recorded as taken, which is exactly what a clinician reads back. details: { eventId, @@ -384,11 +384,11 @@ export const PUT = apiHandler( meta: { medication_id: id }, }); - // v1.4.34 IW-G — bust per-user medications + compliance + achievement + // v1.4.34 — bust per-user medications + compliance + achievement // caches so the next read reflects the edited event. invalidateUserMedications(user.id, { evict: true }); - // v1.4.39 W-MED — refresh the rollup row for both the old + new + // v1.4.39 — refresh the rollup row for both the old + new // scheduledFor day-keys when the dose moved across a day boundary // (band re-attribution can move it as well as an explicit edit); if the // slot is unchanged the second call collapses to a no-op upsert. @@ -481,11 +481,11 @@ export const DELETE = apiHandler( meta: { medication_id: id }, }); - // v1.4.34 IW-G — bust per-user medications + compliance + achievement + // v1.4.34 — bust per-user medications + compliance + achievement // caches so the next read reflects the deletion. invalidateUserMedications(user.id, { evict: true }); - // v1.4.39 W-MED — refresh the rollup row for the day the deleted + // v1.4.39 — refresh the rollup row for the day the deleted // event sat on. When that day now holds zero events the helper // drops the rollup row entirely. await recomputeMedicationComplianceForEvent({ diff --git a/src/app/api/medications/[id]/intake/__tests__/route.test.ts b/src/app/api/medications/[id]/intake/__tests__/route.test.ts index 1705cb8a1..354ab4780 100644 --- a/src/app/api/medications/[id]/intake/__tests__/route.test.ts +++ b/src/app/api/medications/[id]/intake/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.37 W3 — GET /api/medications/[id]/intake `status` filter tests. + * v1.4.37 — GET /api/medications/[id]/intake `status` filter tests. * * Pins the contract added to fix the v1.4.36 regression where * `IntakeHistoryListV2` rendered rows with `takenAt:null AND @@ -49,7 +49,7 @@ vi.mock("@/lib/db", () => ({ findFirst: vi.fn(), count: vi.fn(), create: vi.fn(), - // v1.8.2 reconcile — the shared slot upsert updates in place. + // v1.8.2 — the shared slot upsert updates in place. update: vi.fn(), }, auditLog: { create: vi.fn() }, @@ -148,7 +148,7 @@ beforeEach(() => { [] as never, ); vi.mocked(prisma.medicationIntakeEvent.count).mockResolvedValue(0); - // v1.4.43 W6 — default the audit-row write to resolve. + // v1.4.43 — default the audit-row write to resolve. vi.mocked(prisma.auditLog.create).mockResolvedValue({} as never); }); @@ -420,7 +420,7 @@ describe("POST /api/medications/[id]/intake — one-shot lifecycle", () => { }); // ──────────────────────────────────────────────────────────────────── -// v1.8.2 reconcile — slot-snap upsert invariants through the route +// v1.8.2 — slot-snap upsert invariants through the route // ──────────────────────────────────────────────────────────────────── describe("POST /api/medications/[id]/intake — v1.8.2 reconcile (M2 inventory)", () => { diff --git a/src/app/api/medications/[id]/intake/bulk-delete/__tests__/route.test.ts b/src/app/api/medications/[id]/intake/bulk-delete/__tests__/route.test.ts index c087df6f9..eb99f97bc 100644 --- a/src/app/api/medications/[id]/intake/bulk-delete/__tests__/route.test.ts +++ b/src/app/api/medications/[id]/intake/bulk-delete/__tests__/route.test.ts @@ -25,7 +25,7 @@ vi.mock("@/lib/db", () => ({ medication: { findUnique: vi.fn() }, medicationIntakeEvent: { findMany: vi.fn(), - // v1.15.18 LOW-6 — bulk delete is now a soft-delete updateMany. + // v1.15.18 — bulk delete is now a soft-delete updateMany. updateMany: vi.fn(), }, auditLog: { create: vi.fn() }, @@ -219,7 +219,7 @@ describe("POST /api/medications/[id]/intake/bulk-delete", () => { await POST(postWithBody({ eventIds: ["evt-a"] }), ROUTE_PARAMS); - // v1.15.18 LOW-6 — a bulk delete is a soft-delete updateMany: scoped by + // v1.15.18 — a bulk delete is a soft-delete updateMany: scoped by // (id IN, user, medication, deletedAt:null) so a re-post counts zero, and // the data sets deletedAt + bumps syncVersion for the iOS tombstone feed. const call = vi.mocked(prisma.medicationIntakeEvent.updateMany).mock diff --git a/src/app/api/medications/[id]/intake/bulk-delete/route.ts b/src/app/api/medications/[id]/intake/bulk-delete/route.ts index 4596a5ed1..948db196b 100644 --- a/src/app/api/medications/[id]/intake/bulk-delete/route.ts +++ b/src/app/api/medications/[id]/intake/bulk-delete/route.ts @@ -23,7 +23,7 @@ import { NextRequest } from "next/server"; type RouteParams = { params: Promise<{ id: string }> }; -// v1.5.5 F-1 H-6 — match the glp1 POST shape so a caller firing +// v1.5.5 — match the glp1 POST shape so a caller firing // repeated 500-id bulk deletes cannot pin the per-dayKey recompute // path. 30/min/user is generous for a hand-driven multi-select and // tight enough to cut off the spam case. @@ -67,7 +67,7 @@ export const POST = apiHandler( const guard = await assertMedicationOwnership(id, user.id); if (guard) return guard; - // v1.5.5 F-1 H-6 — per-caller cap matches the glp1 POST so a caller + // v1.5.5 — per-caller cap matches the glp1 POST so a caller // firing repeated 500-id bulk deletes cannot pin the per-dayKey // recompute path. // @@ -138,7 +138,7 @@ export const POST = apiHandler( } } - // v1.15.18 LOW-6 — soft-delete to match the single-event DELETE handler + // v1.15.18 — soft-delete to match the single-event DELETE handler // and the iOS tombstone contract: an intake is an immutable fact, so a // bulk "delete" sets `deletedAt` (+ bumps `syncVersion`) so the // `/api/sync/changes` feed surfaces each removal as a tombstone keyed on diff --git a/src/app/api/medications/[id]/intake/import/route.ts b/src/app/api/medications/[id]/intake/import/route.ts index 8ef9f25ad..b57b4b41f 100644 --- a/src/app/api/medications/[id]/intake/import/route.ts +++ b/src/app/api/medications/[id]/intake/import/route.ts @@ -105,7 +105,7 @@ export const POST = apiHandler( const { user, actor } = await requireRecordAuth("manage", "medications"); const { id } = await params; - // v1.5.5 C-E3-3 — route ownership through the shared helper so the + // v1.5.5 — route ownership through the shared helper so the // 404 leak shape stays identical across every `[id]/**` handler. const guard = await assertMedicationOwnership(id, user.id); if (guard) return guard; @@ -136,7 +136,7 @@ export const POST = apiHandler( const parsed = validatedImportSchema.safeParse(payload); if (!parsed.success) { - // v1.4.43 W6 — CSV import; preserve the `Invalid format:` prefix + // v1.4.43 — CSV import; preserve the `Invalid format:` prefix // semantics for the existing client-side branch by setting the // `errorCode: "medication.intake.import.invalid_format"` meta. // The client UI can now branch on `meta.errorCode` while every diff --git a/src/app/api/medications/[id]/intake/purge/__tests__/route.test.ts b/src/app/api/medications/[id]/intake/purge/__tests__/route.test.ts index 6a8e8c12e..3af9531b4 100644 --- a/src/app/api/medications/[id]/intake/purge/__tests__/route.test.ts +++ b/src/app/api/medications/[id]/intake/purge/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.5.5 F-1 C-3 — purge route fires the medication cache bundle. + * v1.5.5 — purge route fires the medication cache bundle. * * Pins the contract the detail-page Verlauf-löschen cascade depends * on: dropping the rollup rows is necessary but not sufficient. The diff --git a/src/app/api/medications/[id]/intake/purge/route.ts b/src/app/api/medications/[id]/intake/purge/route.ts index 4f9e4b87b..2885701d7 100644 --- a/src/app/api/medications/[id]/intake/purge/route.ts +++ b/src/app/api/medications/[id]/intake/purge/route.ts @@ -22,7 +22,7 @@ export const DELETE = apiHandler( const { user } = await requireRecordAuth("manage", "medications"); const { id } = await params; - // v1.5.5 C-E3-3 — route purge through the same ownership predicate + // v1.5.5 — route purge through the same ownership predicate // every other `src/app/api/medications/[id]/**` handler uses. The // hand-rolled `findUnique + userId !== id ? 404` block returned the // medication row only to read its name for the audit details. The @@ -60,7 +60,7 @@ export const DELETE = apiHandler( data: { deletedAt: new Date(), syncVersion: { increment: 1 } }, }); - // v1.4.39 W-MED — drop every rollup row for this medication so the + // v1.4.39 — drop every rollup row for this medication so the // next compliance read returns zero-filled buckets rather than the // last-known scheduled / taken totals for now-deleted events. await prisma.medicationComplianceRollup.deleteMany({ @@ -91,7 +91,7 @@ export const DELETE = apiHandler( meta: { purged_count: count }, }); - // v1.5.5 F-1 C-3 — drop the per-user medication caches so the + // v1.5.5 — drop the per-user medication caches so the // analytics + iOS today-tally + dashboard tiles converge on the // post-purge counts rather than reading stale values for the // TTL of each cache. Matches the sibling routes (POST intake, diff --git a/src/app/api/medications/[id]/intake/route.ts b/src/app/api/medications/[id]/intake/route.ts index 2cf36546a..0ecd6c9d1 100644 --- a/src/app/api/medications/[id]/intake/route.ts +++ b/src/app/api/medications/[id]/intake/route.ts @@ -72,7 +72,7 @@ async function postIntake(request: NextRequest, { params }: RouteParams) { }); } - // v1.36.1 follow-up — the sibling route `POST /api/medications/intake` + // v1.36.1 — the sibling route `POST /api/medications/intake` // refuses a delegate changing a dose the owner already recorded. This route // reaches the same rows through the slot upsert, so without the same rule it // is simply the way round the other one: an explicit skip posted onto a @@ -114,7 +114,7 @@ async function postIntake(request: NextRequest, { params }: RouteParams) { const intakeSource = authMethod === "bearer" ? "API" : "WEB"; const { id } = await params; - // v1.4.25 W21 Fix-N — privacy gate hoisted to the shared helper. This is a + // v1.4.25 — privacy gate hoisted to the shared helper. This is a // dose write, so the guard also refuses a record-only medication with // `medication.intake.notTracked` (iOS #116, item 15): its history stays, // but a new dose would be stored and then counted nowhere. @@ -131,7 +131,7 @@ async function postIntake(request: NextRequest, { params }: RouteParams) { medicationId: id, }); if (!parsed.success) { - // v1.4.43 W6 — per-med intake POST hot path; multi-issue 422 + + // v1.4.43 — per-med intake POST hot path; multi-issue 422 + // audit breadcrumb keyed `medications.intake.create.validation-failed`. const issues = sanitiseZodIssues(parsed.error.issues); const shape = unstableExternalIdShape(body, "idempotencyKey"); @@ -224,7 +224,7 @@ async function postIntake(request: NextRequest, { params }: RouteParams) { const resolvedTakenAt = skipped ? null : (takenAt ?? new Date()); const incomingScheduledFor = scheduledFor ?? takenAt ?? new Date(); - // C2 — the per-med route only ever carries an explicit user gesture: + // The per-med route only ever carries an explicit user gesture: // `resolvedTakenAt` is `now()` for any non-skip POST (there is no // "mark pending" write on this route), so a non-skip is an explicit // taken and a `skipped:true` body is an explicit skip. Neither is a @@ -413,7 +413,7 @@ async function postIntake(request: NextRequest, { params }: RouteParams) { } let event; - // v1.8.2 reconcile — whether this write moved the slot pending→taken. + // v1.8.2 — whether this write moved the slot pending→taken. // Only that transition decrements pen inventory (M2). For the // unscheduled/PRN branch a non-skip write always records a fresh dose, // so it consumes when not skipped. @@ -601,11 +601,11 @@ async function postIntake(request: NextRequest, { params }: RouteParams) { }, }); - // v1.4.34 IW-G — bust per-user medications + compliance + achievement + // v1.4.34 — bust per-user medications + compliance + achievement // caches so the next read reflects the dose event. invalidateUserMedications(user.id, { evict: true }); - // v1.4.39 W-MED — refresh the persistent compliance rollup for the + // v1.4.39 — refresh the persistent compliance rollup for the // affected day. The hook is best-effort; failures annotate but never // block the user's POST response. await recomputeMedicationComplianceForEvent({ @@ -676,14 +676,14 @@ export const GET = apiHandler( const { user } = await requireRecordAuth("read", "medications"); const { id } = await params; - // v1.4.25 W21 Fix-N — privacy gate hoisted to the shared helper. + // v1.4.25 — privacy gate hoisted to the shared helper. const guard = await assertMedicationOwnership(id, user.id); if (guard) return guard; const searchParams = Object.fromEntries(request.nextUrl.searchParams); const parsed = listIntakeEventsSchema.safeParse(searchParams); if (!parsed.success) { - // v1.4.43 W6 — multi-issue 422 + audit breadcrumb keyed + // v1.4.43 — multi-issue 422 + audit breadcrumb keyed // `medications.intake.list.validation-failed`. const issues = sanitiseZodIssues(parsed.error.issues); annotate({ @@ -709,7 +709,7 @@ export const GET = apiHandler( const { limit, offset, sortBy, sortDir, status } = parsed.data; - // v1.4.37 W3 — translate the optional `status` filter into a Prisma + // v1.4.37 — translate the optional `status` filter into a Prisma // `where` fragment. Default `status:"all"` keeps the contract // byte-stable for the iOS Swift client and the dashboard tiles that // were on the wire before this knob existed. The detail-page @@ -740,7 +740,7 @@ export const GET = apiHandler( ...statusFilter, }; - // v1.7.0 O-1 — pin NULLS LAST on the `takenAt` sort. Skipped rows + // v1.7.0 — pin NULLS LAST on the `takenAt` sort. Skipped rows // carry `takenAt: null`; under a bare `desc` collation Postgres // emits NULLS FIRST, floating skipped/planned rows to the top of // the history view. Pinning them last keeps the descending order diff --git a/src/app/api/medications/[id]/inventory/[itemId]/__tests__/route.test.ts b/src/app/api/medications/[id]/inventory/[itemId]/__tests__/route.test.ts index ad3f124ce..7e704b957 100644 --- a/src/app/api/medications/[id]/inventory/[itemId]/__tests__/route.test.ts +++ b/src/app/api/medications/[id]/inventory/[itemId]/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.43 W6 — multi-issue 422 envelope on PATCH + * v1.4.43 — multi-issue 422 envelope on PATCH * /api/medications/[id]/inventory/[itemId]. * * v1.16.1 — stock-correction contract: `unitsRemaining` sets the count @@ -10,7 +10,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { NextRequest } from "next/server"; vi.mock("@/lib/db", () => { - // v1.32.22 (M5) — the PATCH / DELETE now run inside `prisma.$transaction` + // v1.32.22 — the PATCH / DELETE now run inside `prisma.$transaction` // that takes the per-medication advisory lock. The mock transaction runs the // callback with the base client as the tx, so the existing model mocks apply // inside the transaction and `$queryRaw` (the advisory lock) is observable. @@ -263,7 +263,7 @@ describe("PATCH /api/medications/[id]/inventory/[itemId] — carton labelling", }); /** - * v1.32.22 (M5) — the stock-correction PATCH and the DELETE route their + * v1.32.22 — the stock-correction PATCH and the DELETE route their * read-compose-write through the SAME per-medication advisory lock that * `consumeForIntake` takes, so an absolute stock write can no longer clobber a * concurrent dose decrement. These pin the routing: the write runs inside a diff --git a/src/app/api/medications/[id]/inventory/[itemId]/route.ts b/src/app/api/medications/[id]/inventory/[itemId]/route.ts index 356e44167..259a058e2 100644 --- a/src/app/api/medications/[id]/inventory/[itemId]/route.ts +++ b/src/app/api/medications/[id]/inventory/[itemId]/route.ts @@ -1,5 +1,5 @@ /** - * v1.4.25 W19b — per-pen inventory item operations. + * v1.4.25 — per-pen inventory item operations. * * PATCH /api/medications/[id]/inventory/[itemId] * - set or clear the opening date (`markAsFirstUseAt`); for a pen @@ -60,7 +60,7 @@ export const PATCH = apiHandler( const parsed = updateInventoryItemSchema.safeParse(body); if (!parsed.success) { - // v1.4.43 W6 — multi-issue 422. + // v1.4.43 — multi-issue 422. return returnAllZodIssues(parsed.error, 422); } @@ -92,7 +92,7 @@ export const PATCH = apiHandler( if (manufacturer !== undefined) labellingData.manufacturer = manufacturer; if (doseStrength !== undefined) labellingData.doseStrength = doseStrength; - // v1.32.22 (M5) — the stock correction writes an ABSOLUTE `unitsRemaining` + // v1.32.22 — the stock correction writes an ABSOLUTE `unitsRemaining` // composed from a prior read. `consumeForIntake` decrements the same rows // under a per-medication advisory lock; without taking that SAME lock the // correction's read-compose-write could clobber a concurrent decrement (or @@ -217,7 +217,7 @@ export const PATCH = apiHandler( await auditLog("medication.inventory.update", { userId: user.id, ipAddress: getClientIp(request), - // C4 — the counts and dates this edit replaced, beside the state + // The counts and dates this edit replaced, beside the state // transition the row already carried. details: { medicationId: id, @@ -267,7 +267,7 @@ export const DELETE = apiHandler( const { user } = await requireRecordAuth("manage", "medications"); const { id, itemId } = await params; - // v1.32.22 (M5) — take the same per-medication advisory lock the intake + // v1.32.22 — take the same per-medication advisory lock the intake // consumption hook takes, so a delete that interleaves a `consumeForIntake` // serializes against it: the consume either fully applies before the row // vanishes or blocks until the delete commits and then finds nothing to @@ -288,7 +288,7 @@ export const DELETE = apiHandler( await auditLog("medication.inventory.delete", { userId: user.id, ipAddress: getClientIp(request), - // C3 — the container's own identity: its label, its expiry, what it + // The container's own identity: its label, its expiry, what it // held. The row is gone; this is what is left of it. details: { medicationId: id, diff --git a/src/app/api/medications/[id]/inventory/__tests__/route.test.ts b/src/app/api/medications/[id]/inventory/__tests__/route.test.ts index 5231ae2af..eb7ea16b9 100644 --- a/src/app/api/medications/[id]/inventory/__tests__/route.test.ts +++ b/src/app/api/medications/[id]/inventory/__tests__/route.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { NextRequest } from "next/server"; vi.mock("@/lib/db", () => { - // v1.32.22 (M5) — the [itemId] PATCH / DELETE now run inside a transaction + // v1.32.22 — the [itemId] PATCH / DELETE now run inside a transaction // that takes the per-medication advisory lock. The mock transaction runs the // callback with the base client as tx, and `$queryRaw` stands in for the // advisory-lock query. diff --git a/src/app/api/medications/[id]/inventory/route.ts b/src/app/api/medications/[id]/inventory/route.ts index 16b880d7b..d9ac6115f 100644 --- a/src/app/api/medications/[id]/inventory/route.ts +++ b/src/app/api/medications/[id]/inventory/route.ts @@ -1,5 +1,5 @@ /** - * v1.4.25 W19b — pen / vial inventory CRUD. + * v1.4.25 — pen / vial inventory CRUD. * * GET /api/medications/[id]/inventory — list (user-scoped, all states) * POST /api/medications/[id]/inventory — register a new pen / vial @@ -137,7 +137,7 @@ async function postInventoryItem( const parsed = createInventoryItemSchema.safeParse(body); if (!parsed.success) { - // v1.4.43 W6 — multi-issue 422. + // v1.4.43 — multi-issue 422. return returnAllZodIssues(parsed.error, 422); } diff --git a/src/app/api/medications/[id]/phase-config/__tests__/route.test.ts b/src/app/api/medications/[id]/phase-config/__tests__/route.test.ts index f33f686b2..b2533196e 100644 --- a/src/app/api/medications/[id]/phase-config/__tests__/route.test.ts +++ b/src/app/api/medications/[id]/phase-config/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.5.5 F-1 H-3 — phase-config PUT now returns the multi-issue 422 + * v1.5.5 — phase-config PUT now returns the multi-issue 422 * envelope rather than the flat `400 Invalid input`. */ diff --git a/src/app/api/medications/[id]/phase-config/route.ts b/src/app/api/medications/[id]/phase-config/route.ts index 6ed2937cf..e3ae932c6 100644 --- a/src/app/api/medications/[id]/phase-config/route.ts +++ b/src/app/api/medications/[id]/phase-config/route.ts @@ -13,7 +13,7 @@ export const GET = apiHandler( const { user } = await requireRecordAuth("read", "medications"); const { id } = await params; - // v1.4.25 W21 Fix-N — privacy gate hoisted to the shared helper. + // v1.4.25 — privacy gate hoisted to the shared helper. const guard = await assertMedicationOwnership(id, user.id); if (guard) return guard; @@ -50,7 +50,7 @@ export const PUT = apiHandler( const { user } = await requireAuth(); const { id } = await params; - // v1.4.25 W21 Fix-N — privacy gate hoisted to the shared helper. + // v1.4.25 — privacy gate hoisted to the shared helper. const guard = await assertMedicationOwnership(id, user.id); if (guard) return guard; @@ -61,14 +61,14 @@ export const PUT = apiHandler( if (jsonError) return jsonError; const parsed = phaseConfigSchema.safeParse(body); if (!parsed.success) { - // v1.5.5 F-1 H-3 — every v1.5.5 route returns the multi-issue + // v1.5.5 — every v1.5.5 route returns the multi-issue // 422 envelope so iOS sees per-field feedback the same way the // bulk-delete and intake routes already do. The old `400 Invalid // input` flat response was the last hold-out in the surface. return returnAllZodIssues(parsed.error, 422); } - // v1.5.5 F-1 H-4 — build the Prisma payload field-by-field rather + // v1.5.5 — build the Prisma payload field-by-field rather // than spreading `parsed.data`. A future schema extension would // otherwise land on the upsert silently; the explicit pick keeps // the surface honest at the call site. @@ -116,7 +116,7 @@ export const DELETE = apiHandler( const { user } = await requireAuth(); const { id } = await params; - // v1.4.25 W21 Fix-N — privacy gate hoisted to the shared helper. + // v1.4.25 — privacy gate hoisted to the shared helper. const guard = await assertMedicationOwnership(id, user.id); if (guard) return guard; diff --git a/src/app/api/medications/[id]/route.ts b/src/app/api/medications/[id]/route.ts index 6808d59d8..a1821e376 100644 --- a/src/app/api/medications/[id]/route.ts +++ b/src/app/api/medications/[id]/route.ts @@ -131,7 +131,7 @@ export const GET = apiHandler( // Category enrichment is optional } - // v1.7.0 SB-SCHED-3 — server-computed next due instant. Rolling + // v1.7.0 — server-computed next due instant. Rolling // cadences re-anchor on the latest non-skipped intake, so fetch it // once (no-op for calendar cadences but cheap + keeps the value // correct for rolling injections). @@ -338,7 +338,7 @@ export const PUT = apiHandler( ); const { id } = await params; - // v1.5.5 C-E3-3 — route ownership check through the shared helper + // v1.5.5 — route ownership check through the shared helper // so the 404 leak shape stays identical across every // `[id]/**` handler. The PUT branch still needs `existing.active` // for the pausedAt-derivation step below, so re-read the narrow @@ -379,7 +379,7 @@ export const PUT = apiHandler( if (jsonError) return jsonError; const parsed = updateMedicationSchema.safeParse(body); if (!parsed.success) { - // v1.4.43 W6 — multi-issue 422. + // v1.4.43 — multi-issue 422. return returnAllZodIssues(parsed.error, 422); } @@ -752,7 +752,7 @@ export const PUT = apiHandler( where: tombstoneWhere, select: { scheduledFor: true }, }); - // C7 — the count and the day range of the anchors that went. + // The count and the day range of the anchors that went. tombstonedSlotCount = tombstoned.length; for (const row of tombstoned) { if (tombstonedFrom === null || row.scheduledFor < tombstonedFrom) { @@ -1033,7 +1033,7 @@ export const PUT = apiHandler( include: { schedules: true }, }); - // v1.25 H-MED1 — durable pause intervals. The `pausedAt` column is a + // v1.25 — durable pause intervals. The `pausedAt` column is a // single live marker that resume clears, so the paused window is // irrecoverable once resumed and every paused day's slot collapses to // "missed" in the compliance denominator. Mirror the live transition @@ -1205,7 +1205,7 @@ export const PUT = apiHandler( }, }); - // v1.4.34 IW-G — bust per-user medications + compliance + achievement + // v1.4.34 — bust per-user medications + compliance + achievement // caches so the next read reflects the schedule change. invalidateUserMedications(user.id, { evict: true }); @@ -1234,7 +1234,7 @@ export const DELETE = apiHandler( const { user } = await requireAuth(); const { id } = await params; - // v1.5.5 C-E3-3 — ownership via the shared helper. The DELETE + // v1.5.5 — ownership via the shared helper. The DELETE // branch needs the medication name for the audit-row detail; pull // it after the guard passes. const guard = await assertMedicationOwnership(id, user.id); @@ -1279,7 +1279,7 @@ export const DELETE = apiHandler( }, }); - // v1.4.34 IW-G — bust per-user medications + compliance + achievement + // v1.4.34 — bust per-user medications + compliance + achievement // caches so the next read reflects the deletion. invalidateUserMedications(user.id, { evict: true }); diff --git a/src/app/api/medications/[id]/schedule-revisions/[revisionId]/route.ts b/src/app/api/medications/[id]/schedule-revisions/[revisionId]/route.ts index 1a4d83212..e5d2109ff 100644 --- a/src/app/api/medications/[id]/schedule-revisions/[revisionId]/route.ts +++ b/src/app/api/medications/[id]/schedule-revisions/[revisionId]/route.ts @@ -246,7 +246,7 @@ export const PATCH = apiHandler( await auditLog("medication.schedule_revision.updated", { userId: user.id, ipAddress: getClientIp(request), - // C4 — the era boundaries the correction replaced. An era is a window + // The era boundaries the correction replaced. An era is a window // the compliance engine reads months of history through; moving it // silently re-scores days nobody looked at again. details: { @@ -379,7 +379,7 @@ export const DELETE = apiHandler( await auditLog("medication.schedule_revision.deleted", { userId: user.id, ipAddress: getClientIp(request), - // C3 — the era's dates and its dosing summary. Without them the feed + // The era's dates and its dosing summary. Without them the feed // says an era was deleted and the compliance history it explained // becomes unexplainable. details: { diff --git a/src/app/api/medications/[id]/side-effects/[logId]/route.ts b/src/app/api/medications/[id]/side-effects/[logId]/route.ts index 849f28cb6..fce35426e 100644 --- a/src/app/api/medications/[id]/side-effects/[logId]/route.ts +++ b/src/app/api/medications/[id]/side-effects/[logId]/route.ts @@ -1,5 +1,5 @@ /** - * v1.4.25 W19d — per-row side-effect operations. + * v1.4.25 — per-row side-effect operations. * * DELETE /api/medications/[id]/side-effects/[logId] * - hard delete; the row is user-owned, non-clinical, and the diff --git a/src/app/api/medications/[id]/side-effects/__tests__/route.test.ts b/src/app/api/medications/[id]/side-effects/__tests__/route.test.ts index a82b51f01..c0b955928 100644 --- a/src/app/api/medications/[id]/side-effects/__tests__/route.test.ts +++ b/src/app/api/medications/[id]/side-effects/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.25 W19d — side-effect API route tests. + * v1.4.25 — side-effect API route tests. * * Mirrors the inventory route test fixture pattern: every external * dependency is mocked at module boundaries (Prisma, session, audit, diff --git a/src/app/api/medications/[id]/side-effects/route.ts b/src/app/api/medications/[id]/side-effects/route.ts index e3a986e89..0b12feae8 100644 --- a/src/app/api/medications/[id]/side-effects/route.ts +++ b/src/app/api/medications/[id]/side-effects/route.ts @@ -1,5 +1,5 @@ /** - * v1.4.25 W19d — GLP-1 side-effect log CRUD (collection). + * v1.4.25 — GLP-1 side-effect log CRUD (collection). * * GET /api/medications/[id]/side-effects?from=ISO&to=ISO&limit=50 * - returns the user's logs for this medication, newest first, @@ -61,7 +61,7 @@ export const GET = apiHandler( limit: url.searchParams.get("limit") ?? undefined, }); if (!parsed.success) { - // v1.4.43 W6 — multi-issue 422. + // v1.4.43 — multi-issue 422. return returnAllZodIssues(parsed.error, 422); } @@ -154,13 +154,13 @@ async function postSideEffect( const parsed = createSideEffectSchema.safeParse(body); if (!parsed.success) { - // v1.4.43 W6 — multi-issue 422. + // v1.4.43 — multi-issue 422. return returnAllZodIssues(parsed.error, 422); } const { entry, severity, occurredAt, notes } = parsed.data; - // v1.4.25 W21 Fix-N (code-M6) — category is derived server-side + // v1.4.25 (code-M6) — category is derived server-side // from the entry via the authoritative taxonomy mapping. The wire // schema no longer accepts `category`; older clients that still // send it now have it ignored by Zod's strict drop, and the row diff --git a/src/app/api/medications/__tests__/route.test.ts b/src/app/api/medications/__tests__/route.test.ts index a94070d52..d91bb73e9 100644 --- a/src/app/api/medications/__tests__/route.test.ts +++ b/src/app/api/medications/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.43 W6 — multi-issue 422 envelope on POST /api/medications. + * v1.4.43 — multi-issue 422 envelope on POST /api/medications. */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { NextRequest } from "next/server"; diff --git a/src/app/api/medications/compliance/route.ts b/src/app/api/medications/compliance/route.ts index d8e0bd49e..e2698eda4 100644 --- a/src/app/api/medications/compliance/route.ts +++ b/src/app/api/medications/compliance/route.ts @@ -85,7 +85,7 @@ export const GET = apiHandler(async (request: Request) => { schedules: true, // v1.16.3 — archived schedule eras for era-aware compliance. scheduleRevisions: { orderBy: { validFrom: "asc" } }, - // v1.25 H-MED1 — pause eras so paused days drop out of the denominator. + // v1.25 — pause eras so paused days drop out of the denominator. pauseEras: { select: { pausedAt: true, resumedAt: true } }, // v1.40 (#1024) — the courses, so a gap between two expects nothing. courses: COURSES_COMPLIANCE_SELECT, diff --git a/src/app/api/medications/intake/__tests__/route.test.ts b/src/app/api/medications/intake/__tests__/route.test.ts index 9e033bcca..c1ff368b0 100644 --- a/src/app/api/medications/intake/__tests__/route.test.ts +++ b/src/app/api/medications/intake/__tests__/route.test.ts @@ -21,14 +21,14 @@ vi.mock("@/lib/db", () => ({ update: vi.fn(), // v1.16.10 — the slot-move path re-inserts via the shared upsert. create: vi.fn(), - // v1.4.39 W-SERVER-FIX — `scope=today` now backfills missing + // v1.4.39 — `scope=today` now backfills missing // rows for schedules whose window opens today (covers daily // meds with `daysOfWeek: null` whose reminder hasn't fired yet). createMany: vi.fn(), }, medication: { update: vi.fn(), - // v1.4.39 W-SERVER-FIX — today's projection reads active meds + + // v1.4.39 — today's projection reads active meds + // their schedules to know what to backfill. findMany: vi.fn(), // v1.15.18 — the band resolver loads the medication via findFirst. @@ -40,10 +40,10 @@ vi.mock("@/lib/db", () => ({ upsert: vi.fn(), deleteMany: vi.fn(), }, - // v1.4.43 W6 — audit-ledger breadcrumb for validation-failed paths. + // v1.4.43 — audit-ledger breadcrumb for validation-failed paths. auditLog: { create: vi.fn() }, $transaction: vi.fn(), - // v1.4.39 QA F-H-01 — coverage probe + atomic upsert use raw SQL. + // v1.4.39 — coverage probe + atomic upsert use raw SQL. $queryRaw: vi.fn(), $executeRaw: vi.fn().mockResolvedValue(0), }, @@ -106,20 +106,20 @@ beforeEach(() => { // helper, whose promise the route attaches a `.catch` to. `resetAllMocks` // clears the factory's implementation, so it is restored here. vi.mocked(auditLog).mockResolvedValue(undefined); - // v1.4.34 IW-G — reset compliance LRU between tests so each case + // v1.4.34 — reset compliance LRU between tests so each case // observes a cold cache. __resetAllCachesForTests(); vi.mocked(prisma.medicationIntakeEvent.findMany).mockResolvedValue( [] as never, ); - // v1.4.39 W-SERVER-FIX — default the today-projection prisma reads + // v1.4.39 — default the today-projection prisma reads // to "no active medications" so existing compliance / POST tests // exercise their original paths without an unmocked-call failure. vi.mocked(prisma.medication.findMany).mockResolvedValue([] as never); vi.mocked(prisma.medicationIntakeEvent.createMany).mockResolvedValue({ count: 0, } as never); - // v1.4.39 W-MED — default the coverage probe to "uncovered" so the + // v1.4.39 — default the coverage probe to "uncovered" so the // legacy compliance test still exercises the live-fallback branch // and finds the legacy mocked intake events. vi.mocked(prisma.medicationComplianceRollup.findFirst).mockResolvedValue( @@ -128,18 +128,18 @@ beforeEach(() => { vi.mocked(prisma.medicationComplianceRollup.findMany).mockResolvedValue( [] as never, ); - // v1.4.39 QA F-H-01 — the coverage probe is now a single `$queryRaw` + // v1.4.39 — the coverage probe is now a single `$queryRaw` // aggregate returning `{ rolled_days, event_days }`. Default to // "zero rollups, zero events" (covered/trivial-empty) so tests that // don't care about coverage land on the rollup path. vi.mocked(prisma.$queryRaw).mockResolvedValue([ { rolled_days: BigInt(0), event_days: BigInt(0) }, ] as never); - // v1.4.43 W6 — default the audit-row write to resolve. + // v1.4.43 — default the audit-row write to resolve. vi.mocked(prisma.auditLog.create).mockResolvedValue({} as never); }); -// v1.4.48 L13 — restore real timers no matter how a test exits. A failed +// v1.4.48 — restore real timers no matter how a test exits. A failed // assertion inside an `it` that opted into fake timers would otherwise // leak the fake clock into the next test in the file (or, worse, the // next file in the same vitest worker). @@ -257,7 +257,7 @@ describe("GET /api/medications/intake", () => { source: "REMINDER", }, ], - // v1.4.39 W-SERVER-FIX-2 — the createMany must ship + // v1.4.39 — the createMany must ship // `skipDuplicates: true` so a concurrent dashboard-summary hit // can't race a duplicate `(userId, medicationId, scheduledFor, // REMINDER)` row in between this route's existence probe and @@ -322,7 +322,7 @@ describe("GET /api/medications/intake", () => { ); expect(res.status).toBe(200); expect(prisma.medicationIntakeEvent.createMany).not.toHaveBeenCalled(); - // v1.4.48 L13 — suite-level `afterEach` restores real timers. + // v1.4.48 — suite-level `afterEach` restores real timers. }); it("returns compliance buckets for the last N days", async () => { diff --git a/src/app/api/medications/intake/bulk/__tests__/route.test.ts b/src/app/api/medications/intake/bulk/__tests__/route.test.ts index c3ac9d2a3..f72e787a3 100644 --- a/src/app/api/medications/intake/bulk/__tests__/route.test.ts +++ b/src/app/api/medications/intake/bulk/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.43 W6 — multi-issue 422 envelope on POST /api/medications/intake/bulk. + * v1.4.43 — multi-issue 422 envelope on POST /api/medications/intake/bulk. * Preserves the `medications.intake.bulk.invalid` errorCode meta passthrough. */ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; @@ -17,7 +17,7 @@ vi.mock("@/lib/db", () => ({ medicationIntakeEvent: { create: vi.fn(), findUnique: vi.fn(), - // v1.8.2 reconcile — shared slot upsert reads + updates in place. + // v1.8.2 — shared slot upsert reads + updates in place. findMany: vi.fn(), update: vi.fn(), // v1.15.19 — resolver-null convergence probe before standalone insert. @@ -197,7 +197,7 @@ describe("POST /api/medications/intake/bulk — 422 multi-issue (v1.4.43 W6)", ( }); // ──────────────────────────────────────────────────────────────────── -// v1.8.2 reconcile — bulk slot-snap upsert invariants (C2 + C1) +// v1.8.2 — bulk slot-snap upsert invariants (C2 + C1) // ──────────────────────────────────────────────────────────────────── describe("POST /api/medications/intake/bulk — v1.8.2 reconcile", () => { diff --git a/src/app/api/medications/intake/bulk/route.ts b/src/app/api/medications/intake/bulk/route.ts index 9d0916ff1..7b9847940 100644 --- a/src/app/api/medications/intake/bulk/route.ts +++ b/src/app/api/medications/intake/bulk/route.ts @@ -235,7 +235,7 @@ async function postBulk(request: NextRequest): Promise { const parsed = bulkPayloadSchema.safeParse(rawBody); if (!parsed.success) { - // v1.4.43 W6 — bulk intake ingest; preserve the existing + // v1.4.43 — bulk intake ingest; preserve the existing // `medications.intake.bulk.invalid` errorCode meta so the iOS // Sync engine's retry classifier still branches on it. Audit // breadcrumb keyed `medications.intake.bulk.validation-failed`. @@ -365,7 +365,7 @@ async function postBulk(request: NextRequest): Promise { let extraDosesUnscheduled = 0; const skipped: Array<{ index: number; reason: string }> = []; const unstableShapes: UnstableExternalIdShape[] = []; - // v1.4.39 W-MED — collect distinct `(medicationId, dayKey)` pairs + // v1.4.39 — collect distinct `(medicationId, dayKey)` pairs // touched by the batch so one rollup recompute fires per pair after // all inserts complete. Per-row recompute would balloon a 500-entry // batch into 500 sequential rollup hits. @@ -448,7 +448,7 @@ async function postBulk(request: NextRequest): Promise { const incomingScheduledFor = entry.scheduledFor ?? entry.takenAt ?? new Date(); - // C2 — classify the incoming write. An offline-sync replay echoes a + // Classify the incoming write. An offline-sync replay echoes a // PENDING projection (no `takenAt`, `skipped:false`) for a slot the // user may already have actioned; that echo must NEVER clear a // recorded `takenAt`. An explicit `takenAt` or `skipped:true` is a @@ -632,7 +632,7 @@ async function postBulk(request: NextRequest): Promise { externalId: entry.externalId ?? null, }); if (applied.noDowngradeNoOp) { - // C2 — pending echo onto an already-actioned slot. Report it as a + // Pending echo onto an already-actioned slot. Report it as a // duplicate so the iOS cursor advances WITHOUT downgrading the // recorded dose. A pending echo is never a taken write, so it // must never reach the inventory consume below. @@ -936,7 +936,7 @@ async function postBulk(request: NextRequest): Promise { }, }); - // v1.4.34 IW-G — bust per-user medications + compliance + achievement + // v1.4.34 — bust per-user medications + compliance + achievement // caches when at least one row landed so the next read reflects the // ingested batch. v1.16.8 — hard-evict: this bulk endpoint is the iOS // user's INTERACTIVE intake path (a dose taken / skipped on the phone @@ -949,7 +949,7 @@ async function postBulk(request: NextRequest): Promise { invalidateUserMedications(user.id, { evict: true }); } - // v1.4.39 W-MED — refresh one rollup row per distinct + // v1.4.39 — refresh one rollup row per distinct // `(medicationId, dayKey)` touched by the batch. Best-effort: failures // are swallowed by the helper's `recomputeMedicationComplianceForEvent` // wrapper, but bulk callers wrap recompute directly to keep the diff --git a/src/app/api/medications/intake/route.ts b/src/app/api/medications/intake/route.ts index d746249e2..98d92f114 100644 --- a/src/app/api/medications/intake/route.ts +++ b/src/app/api/medications/intake/route.ts @@ -72,7 +72,7 @@ export const GET = apiHandler(async (request: NextRequest) => { Object.fromEntries(request.nextUrl.searchParams), ); if (!parsed.success) { - // v1.4.43 W6 — multi-issue 422 + audit breadcrumb keyed + // v1.4.43 — multi-issue 422 + audit breadcrumb keyed // `medications.intake.list.validation-failed`. const issues = sanitiseZodIssues(parsed.error.issues); annotate({ @@ -99,7 +99,7 @@ export const GET = apiHandler(async (request: NextRequest) => { const { scope, days } = parsed.data; - // v1.4.25 W7b — anchor "today" and per-day compliance buckets to the + // v1.4.25 — anchor "today" and per-day compliance buckets to the // user's display timezone so a 23:30 reading in Pacific/Auckland lands // in today's bucket rather than yesterday's Berlin one. const userTz = user.timezone ?? DEFAULT_TIMEZONE; @@ -108,7 +108,7 @@ export const GET = apiHandler(async (request: NextRequest) => { const todayStart = startOfLocalDayInTz(new Date(), userTz); const todayEnd = new Date(todayStart.getTime() + 86_400_000); - // v1.4.39 W-SERVER-FIX — project pending intake rows for every + // v1.4.39 — project pending intake rows for every // active schedule whose window opens today, then idempotently // backfill any missing rows. Pre-fix the endpoint returned `[]` // for daily meds (`schedule.daysOfWeek = null` in the DB) until @@ -172,7 +172,7 @@ export const GET = apiHandler(async (request: NextRequest) => { ); } - // v1.4.34 IW-G — compliance: per-day scheduled vs taken for the last + // v1.4.34 — compliance: per-day scheduled vs taken for the last // N days. Cached at a 15-minute TTL because daily compliance buckets // are slow-moving (intake events trickle in, yesterday's row doesn't // move). Cache key carries the userTz so a user who changes timezone @@ -229,7 +229,7 @@ export const POST = apiHandler(async (request: NextRequest) => { const parsed = intakeStatusUpdateSchema.safeParse(body); if (!parsed.success) { - // v1.4.43 W6 — intake-event update hot path; multi-issue 422 + + // v1.4.43 — intake-event update hot path; multi-issue 422 + // audit breadcrumb keyed `medications.intake.update.validation-failed`. const issues = sanitiseZodIssues(parsed.error.issues); annotate({ @@ -565,11 +565,11 @@ export const POST = apiHandler(async (request: NextRequest) => { meta: { intakeId, status }, }); - // v1.4.34 IW-G — bust the medications + compliance + achievement + // v1.4.34 — bust the medications + compliance + achievement // caches for this user so the next read reflects the dose change. invalidateUserMedications(user.id, { evict: true }); - // v1.4.39 W-MED — refresh the persistent compliance rollup row for + // v1.4.39 — refresh the persistent compliance rollup row for // the affected day so the next read after the cache miss returns // the up-to-date `(scheduled, taken, skipped)` tuple. Best-effort: // a populator failure annotates ops without blocking the response. diff --git a/src/app/api/medications/layout/__tests__/route.test.ts b/src/app/api/medications/layout/__tests__/route.test.ts index 84d145b7a..3e02ca491 100644 --- a/src/app/api/medications/layout/__tests__/route.test.ts +++ b/src/app/api/medications/layout/__tests__/route.test.ts @@ -280,7 +280,7 @@ describe("DELETE /api/medications/layout", () => { }); /** - * v1.32.21 (R5a) — optimistic concurrency (issue #581 family). A PUT carrying + * v1.32.21 — optimistic concurrency (issue #581 family). A PUT carrying * the `baseUpdatedAt` it was based on writes CONDITIONALLY on the stored row * still carrying that token, so a view toggle can never resurrect an order the * reorder Save just committed (and vice versa). A tokenless PUT keeps the prior diff --git a/src/app/api/medications/layout/route.ts b/src/app/api/medications/layout/route.ts index 99330b11f..5434fa6c0 100644 --- a/src/app/api/medications/layout/route.ts +++ b/src/app/api/medications/layout/route.ts @@ -63,7 +63,7 @@ async function buildMedicationListLayout( ): Promise { const row = await prisma.user.findUnique({ where: { id: userId }, - // v1.32.21 (R5a) — `updatedAt` rides the cached blob so GET hands the + // v1.32.21 — `updatedAt` rides the cached blob so GET hands the // client the optimistic-concurrency token alongside the presentation it // applies to (busted on every write, so the pair stays consistent). select: { medicationListLayoutJson: true, updatedAt: true }, @@ -114,7 +114,7 @@ export const PUT = apiHandler(async (request: NextRequest) => { }); if (jsonError) return jsonError; - // v1.32.21 (R5a) — pull the optimistic-concurrency base token off the body + // v1.32.21 — pull the optimistic-concurrency base token off the body // BEFORE the Zod parse so the domain schema stays pure (issue #581 family). // BOTH writers (view toggle, order save) route through here, so the token // guards either against a stale interleave of the other. @@ -181,7 +181,7 @@ export const PUT = apiHandler(async (request: NextRequest) => { order: mergedOrder, }); - // v1.32.21 (R5a) — guard the write on the client's base token. A stale base + // v1.32.21 — guard the write on the client's base token. A stale base // 409s and writes NOTHING, so a view toggle can't resurrect an order the // reorder Save just committed (or vice versa); a tokenless request keeps the // prior unconditional write. diff --git a/src/app/api/medications/route.ts b/src/app/api/medications/route.ts index e2fd46d76..45ffb2e74 100644 --- a/src/app/api/medications/route.ts +++ b/src/app/api/medications/route.ts @@ -161,7 +161,7 @@ async function postMedication(request: NextRequest): Promise { meta: unstableExternalIdMeta("medication.create", [shape]), }); } - // v1.4.43 W6 — multi-issue 422. + // v1.4.43 — multi-issue 422. return returnAllZodIssues(parsed.error, 422); } @@ -325,7 +325,7 @@ async function postMedication(request: NextRequest): Promise { userId: user.id, name, dose, - // v1.4.25 W4d — treatmentClass + dosesPerUnit are optional in the + // v1.4.25 — treatmentClass + dosesPerUnit are optional in the // wire schema; Prisma fills the default GENERIC when omitted. ...(treatmentClass !== undefined && { treatmentClass }), ...(dosesPerUnit !== undefined && { dosesPerUnit }), @@ -508,7 +508,7 @@ async function postMedication(request: NextRequest): Promise { }, }); - // v1.4.34 IW-G — bust per-user medications + compliance + achievement + // v1.4.34 — bust per-user medications + compliance + achievement // caches so the next read reflects the new schedule. invalidateUserMedications(user.id, { evict: true }); diff --git a/src/app/api/mental-health/assessments/route.ts b/src/app/api/mental-health/assessments/route.ts index a9a503d6c..dd52b4443 100644 --- a/src/app/api/mental-health/assessments/route.ts +++ b/src/app/api/mental-health/assessments/route.ts @@ -277,7 +277,7 @@ async function postAssessment(request: NextRequest): Promise { const total = scoreTotal(id, items); const band = severityBand(id, total); const flagged = isSafetyFlagged(id, items); - // C2 — the locale of the person holding the phone, not of the record. + // The locale of the person holding the phone, not of the record. // `presentedLocale` is used twice: it is stored on the row as what was // shown, and it selects the crisis-resource copy returned on a positive // item 9. Under a switch the reader is the MANAGER, and crisis resources in @@ -384,7 +384,7 @@ async function postAssessment(request: NextRequest): Promise { }); invalidateUserHealthScore(user.id); - // v1.37.19 (A6-13) — the projection row is a Measurement like any other: + // v1.37.19 — the projection row is a Measurement like any other: // route it through the shared post-mutation tail so the (type, day) // rollup bucket and the cached status assessment converge without // waiting for a nightly discovery pass. diff --git a/src/app/api/mood-entries/[id]/__tests__/route.test.ts b/src/app/api/mood-entries/[id]/__tests__/route.test.ts index fc01e1444..1b96a0f40 100644 --- a/src/app/api/mood-entries/[id]/__tests__/route.test.ts +++ b/src/app/api/mood-entries/[id]/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.43 W6 — multi-issue 422 envelope on PUT /api/mood-entries/[id]. + * v1.4.43 — multi-issue 422 envelope on PUT /api/mood-entries/[id]. */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { NextRequest } from "next/server"; diff --git a/src/app/api/mood-entries/[id]/route.ts b/src/app/api/mood-entries/[id]/route.ts index 816293996..bfb537a1e 100644 --- a/src/app/api/mood-entries/[id]/route.ts +++ b/src/app/api/mood-entries/[id]/route.ts @@ -101,7 +101,7 @@ export const PUT = apiHandler( if (jsonError) return jsonError; const parsed = updateMoodEntrySchema.safeParse(body); if (!parsed.success) { - // v1.4.43 W6 — mood edit hot path; multi-issue 422 + audit + // v1.4.43 — mood edit hot path; multi-issue 422 + audit // breadcrumb keyed `mood-entries.update.validation-failed`. const issues = sanitiseZodIssues(parsed.error.issues); annotate({ @@ -161,7 +161,7 @@ export const PUT = apiHandler( if (data.a4 !== undefined) updateData.connectionA4 = data.a4; if (data.a5 !== undefined) updateData.stabilityA5 = data.a5; if (data.moodLoggedAt !== undefined) { - // v1.4.25 W7b — re-anchor the row's `date` to the user's current + // v1.4.25 — re-anchor the row's `date` to the user's current // displayTimezone. Also refresh the `tz` column so the row's // attribution stays consistent with the new `date`. Legacy rows // promoted via this PUT therefore migrate to per-row tz without @@ -294,7 +294,7 @@ export const PUT = apiHandler( await auditLog("moodEntry.update", { userId: user.id, ipAddress: getClientIp(request), - // C4 — the replaced fields. The note and the free-text tags are named + // The replaced fields. The note and the free-text tags are named // and never quoted; the note is encrypted at rest. details: { moodEntryId: id, @@ -330,10 +330,10 @@ export const PUT = apiHandler( }, }); - // v1.4.34 IW-G — bust per-user mood + achievements + analytics caches. + // v1.4.34 — bust per-user mood + achievements + analytics caches. invalidateUserMood(user.id); - // v1.4.39 W-MOOD — refresh the persisted rollup for the new + // v1.4.39 — refresh the persisted rollup for the new // bucket AND the old bucket when the entry's `moodLoggedAt` // changed. The two recomputes are independent (different // (user, day) tuples) so we fan them out in parallel. Best- @@ -420,10 +420,10 @@ export const DELETE = apiHandler( meta: { moodEntryId: id }, }); - // v1.4.34 IW-G — bust per-user mood + achievements + analytics caches. + // v1.4.34 — bust per-user mood + achievements + analytics caches. invalidateUserMood(user.id); - // v1.4.39 W-MOOD — refresh the persisted rollup for the + // v1.4.39 — refresh the persisted rollup for the // deleted entry's bucket; the recompute helper handles the // "now-empty day → drop the rollup row" branch internally. try { diff --git a/src/app/api/mood-entries/__tests__/route.test.ts b/src/app/api/mood-entries/__tests__/route.test.ts index 08f0b7de7..db99a0445 100644 --- a/src/app/api/mood-entries/__tests__/route.test.ts +++ b/src/app/api/mood-entries/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.43 W6 — multi-issue 422 envelope on GET + POST /api/mood-entries. + * v1.4.43 — multi-issue 422 envelope on GET + POST /api/mood-entries. */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { NextRequest } from "next/server"; diff --git a/src/app/api/mood-entries/bulk-delete/route.ts b/src/app/api/mood-entries/bulk-delete/route.ts index 0aa918407..292c2f57c 100644 --- a/src/app/api/mood-entries/bulk-delete/route.ts +++ b/src/app/api/mood-entries/bulk-delete/route.ts @@ -106,7 +106,7 @@ async function postBulkDelete(request: NextRequest): Promise { }); if (count > 0) { - // v1.4.34 IW-G — bust per-user mood + achievements + analytics caches. + // v1.4.34 — bust per-user mood + achievements + analytics caches. invalidateUserMood(user.id); // v1.32.12 — collapse the deleted rows to the unique set of `date` diff --git a/src/app/api/mood-entries/bulk/__tests__/route.test.ts b/src/app/api/mood-entries/bulk/__tests__/route.test.ts index 35907e376..f67b039eb 100644 --- a/src/app/api/mood-entries/bulk/__tests__/route.test.ts +++ b/src/app/api/mood-entries/bulk/__tests__/route.test.ts @@ -1,8 +1,8 @@ /** - * v1.4.43 W6 — multi-issue 422 envelope on POST /api/mood-entries/bulk. + * v1.4.43 — multi-issue 422 envelope on POST /api/mood-entries/bulk. * Preserves the `mood.bulk.invalid` errorCode meta passthrough. * - * v1.29 perf fix — the per-entry `findUnique` probe was replaced by one + * v1.29 — the per-entry `findUnique` probe was replaced by one * batched `findMany` existence read (a single indexed OR query); every * entry still resolves its own "inserted" / "duplicate" state through the * exact key its dedup contract uses (externalId when carried, else the @@ -22,7 +22,7 @@ const { moodEntryMock, txClient } = vi.hoisted(() => { const moodEntryMock = { findMany: vi.fn(), upsert: vi.fn(), - // v1.37.19 (A6-18) — the P2002-race arm resolves the winning row. + // v1.37.19 — the P2002-race arm resolves the winning row. findFirst: vi.fn(), }; return { diff --git a/src/app/api/mood-entries/bulk/route.ts b/src/app/api/mood-entries/bulk/route.ts index dbea11e57..8a606cf7d 100644 --- a/src/app/api/mood-entries/bulk/route.ts +++ b/src/app/api/mood-entries/bulk/route.ts @@ -226,7 +226,7 @@ async function postBulk(request: NextRequest): Promise { const parsed = bulkPayloadSchema.safeParse(rawBody); if (!parsed.success) { - // v1.4.43 W6 — bulk mood ingest; keep the `mood.bulk.invalid` + // v1.4.43 — bulk mood ingest; keep the `mood.bulk.invalid` // errorCode meta intact so the iOS Sync engine's retry classifier // still branches on it. Adds the audit-ledger breadcrumb keyed // `mood.bulk.validation-failed`. @@ -545,7 +545,7 @@ async function postBulk(request: NextRequest): Promise { }); } } catch (err: unknown) { - // v1.37.19 (A6-18) — P2002 = a concurrent batch raced the same + // v1.37.19 — P2002 = a concurrent batch raced the same // NULL-distinct `(userId, source, externalId)` key and won. That is // the plain duplicate the sequential probe would have classified; // reporting it as "skipped" with a raw Prisma message told the @@ -616,14 +616,14 @@ async function postBulk(request: NextRequest): Promise { }, }); - // v1.4.34 IW-G — bust per-user mood + achievements + analytics caches + // v1.4.34 — bust per-user mood + achievements + analytics caches // when at least one row landed so the next read picks up the ingested // batch. Skipped / duplicate-only ingests are no-ops. if (inserted > 0) { invalidateUserMood(user.id); } - // v1.4.39 W-MOOD — refresh the rollup tier for every distinct day + // v1.4.39 — refresh the rollup tier for every distinct day // touched by this batch. The bulk endpoint is an iOS one-shot // backfill so the batch can span many days; we collapse to the // unique `(user, dayStart)` set first to bound the recompute count. diff --git a/src/app/api/mood-entries/route.ts b/src/app/api/mood-entries/route.ts index 9f23a965f..94a8c4a82 100644 --- a/src/app/api/mood-entries/route.ts +++ b/src/app/api/mood-entries/route.ts @@ -53,7 +53,7 @@ export const GET = apiHandler(async (request: NextRequest) => { const params = Object.fromEntries(request.nextUrl.searchParams); const parsed = listMoodEntriesSchema.safeParse(params); if (!parsed.success) { - // v1.4.43 W6 — surface every Zod issue + audit breadcrumb keyed + // v1.4.43 — surface every Zod issue + audit breadcrumb keyed // `mood-entries.list.validation-failed`. const issues = sanitiseZodIssues(parsed.error.issues); annotate({ @@ -126,7 +126,7 @@ async function postMoodEntry(request: NextRequest) { if (jsonError) return jsonError; const parsed = createMoodEntrySchema.safeParse(body); if (!parsed.success) { - // v1.4.43 W6 — iOS mood log hot path; multi-issue 422 + audit + // v1.4.43 — iOS mood log hot path; multi-issue 422 + audit // breadcrumb keyed `mood-entries.create.validation-failed`. const issues = sanitiseZodIssues(parsed.error.issues); const shape = unstableExternalIdShape(body); @@ -180,7 +180,7 @@ async function postMoodEntry(request: NextRequest) { a4: parsed.data.a4, a5: parsed.data.a5, }); - // v1.4.25 W7b (Decision A) — anchor the `date` string to the user's + // v1.4.25 (Decision A) — anchor the `date` string to the user's // current displayTimezone and store the resolved zone on the row. // Legacy rows with `tz IS NULL` continue to read as Europe/Berlin // (see `src/lib/mood/date-key.ts`). @@ -192,7 +192,7 @@ async function postMoodEntry(request: NextRequest) { // and the row write agree on the exact value. const resolvedSource = source ?? "MANUAL"; - // C6 — the delegated arm refuses `externalId`. It is the sync client's dedup + // The delegated arm refuses `externalId`. It is the sync client's dedup // handle: supplying it turns this create into an upsert on // `(userId, source, externalId)`, which is a device's contract with its own // rows and not something a second person should be able to aim at somebody @@ -375,10 +375,10 @@ async function postMoodEntry(request: NextRequest) { }, }); - // v1.4.34 IW-G — bust per-user mood + achievements + analytics caches. + // v1.4.34 — bust per-user mood + achievements + analytics caches. invalidateUserMood(user.id); - // v1.4.39 W-MOOD — refresh the persisted DAY rollup for the + // v1.4.39 — refresh the persisted DAY rollup for the // entry's bucket and enqueue the WEEK / MONTH / YEAR folds. // Best-effort: a failure here must not surface as a 5xx to the // user, the rollup is a cache tier, not a write-path invariant. diff --git a/src/app/api/mood/analytics/__tests__/route.test.ts b/src/app/api/mood/analytics/__tests__/route.test.ts index 8df698045..9e538462f 100644 --- a/src/app/api/mood/analytics/__tests__/route.test.ts +++ b/src/app/api/mood/analytics/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.39 W-MOOD — integration coverage for the rollup-tier read swap + * v1.4.39 — integration coverage for the rollup-tier read swap * in `/api/mood/analytics`. * * Pins the parity contract the audit at diff --git a/src/app/api/mood/tags/__tests__/layout.test.ts b/src/app/api/mood/tags/__tests__/layout.test.ts index 03dab52c0..4400ef564 100644 --- a/src/app/api/mood/tags/__tests__/layout.test.ts +++ b/src/app/api/mood/tags/__tests__/layout.test.ts @@ -103,7 +103,7 @@ describe("PUT /api/mood/tags/layout", () => { expect(res.status).toBe(200); expect(db.user.update).toHaveBeenCalledWith({ where: { id: "user-1" }, - // v1.32.22 (M2) — tokenless write echoes the fresh `updatedAt` token. + // v1.32.22 — tokenless write echoes the fresh `updatedAt` token. select: { updatedAt: true }, data: { moodTagLayoutJson: { diff --git a/src/app/api/mood/tags/layout/__tests__/route.test.ts b/src/app/api/mood/tags/layout/__tests__/route.test.ts index 2417e5b4d..ff1f6bff5 100644 --- a/src/app/api/mood/tags/layout/__tests__/route.test.ts +++ b/src/app/api/mood/tags/layout/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.32.22 (M2) — `PUT /api/mood/tags/layout` optimistic concurrency. + * v1.32.22 — `PUT /api/mood/tags/layout` optimistic concurrency. * * The two manage cards write DIFFERENT fields of the same blob (group order * vs placements) from the same page; a stale merge would resurrect the other diff --git a/src/app/api/mood/tags/layout/route.ts b/src/app/api/mood/tags/layout/route.ts index 3294d78a0..358148608 100644 --- a/src/app/api/mood/tags/layout/route.ts +++ b/src/app/api/mood/tags/layout/route.ts @@ -55,7 +55,7 @@ async function resolveLayoutResponse( layout.groupOrder, ), placements: layout.placements ?? {}, - // v1.32.22 (M2) — optimistic-concurrency token; omitted (dropped from + // v1.32.22 — optimistic-concurrency token; omitted (dropped from // JSON) when absent so a fresh user's response shape is unchanged. ...(updatedAt !== undefined ? { updatedAt } : {}), }; @@ -81,7 +81,7 @@ export const PUT = apiHandler(async (request: NextRequest) => { }); if (jsonError) return jsonError; - // v1.32.22 (M2) — split the optimistic-concurrency base token off before the + // v1.32.22 — split the optimistic-concurrency base token off before the // Zod parse. The two cards on this page write DIFFERENT fields of the same // blob (group order vs placements); a stale merge would resurrect the other // card's overwritten field, which the token now 409s instead. diff --git a/src/app/api/notifications/status/route.ts b/src/app/api/notifications/status/route.ts index 5adf11a2a..235b24353 100644 --- a/src/app/api/notifications/status/route.ts +++ b/src/app/api/notifications/status/route.ts @@ -102,7 +102,7 @@ async function loadEventStatuses( * Notifications page paints into "Active / Auto-disabled / Sending paused" * badges + last-send / last-failure timestamps. * - * SB-6 v1.4.40 — additionally returns a per-event-type `events` map so + * v1.4.40 — additionally returns a per-event-type `events` map so * the iOS NotificationsScreen can render "last delivered X h ago" per * category. `events[category].lastDeliveredAt` is null when the user * has never received that event-type (or no ledger source exists yet). diff --git a/src/app/api/nutrients/batch/__tests__/route.test.ts b/src/app/api/nutrients/batch/__tests__/route.test.ts index 9544efec6..cc6ea246a 100644 --- a/src/app/api/nutrients/batch/__tests__/route.test.ts +++ b/src/app/api/nutrients/batch/__tests__/route.test.ts @@ -6,7 +6,7 @@ * insert-vs-update statuses off a single bulk existence probe, and the * batch envelope errors (`too_large`, multi-issue `invalid`). * - * v1.29 perf fix — the probe-then-upsert-per-entry pattern (up to 1000 + * v1.29 — the probe-then-upsert-per-entry pattern (up to 1000 * sequential round trips) was replaced by one indexed `findMany` existence * read + a bulk `createMany` for new pairs + a `$transaction` of per-row * `update` calls for existing pairs. `$transaction` is mocked to actually @@ -320,7 +320,7 @@ describe("POST /api/nutrients/batch — write semantics", () => { expect(prisma.nutrientIntakeDay.update).not.toHaveBeenCalled(); }); - // v1.30.3 (QA F9) — a landed row must evict the dashboard snapshot so + // v1.30.3 — a landed row must evict the dashboard snapshot so // the water/nutrient tile reflects the new total on the very next read, // matching the manual water POST's own posture (`nutrients/water/route.ts`). it("evicts the dashboard snapshot when a row lands", async () => { diff --git a/src/app/api/nutrients/batch/route.ts b/src/app/api/nutrients/batch/route.ts index fb15fc1a3..04a3c31a2 100644 --- a/src/app/api/nutrients/batch/route.ts +++ b/src/app/api/nutrients/batch/route.ts @@ -343,7 +343,7 @@ async function postBatch(request: NextRequest): Promise { }); if (inserted + updated > 0) { - // v1.30.3 (QA F9) — a landed row changes the dashboard's water/nutrient + // v1.30.3 — a landed row changes the dashboard's water/nutrient // tile; without this the snapshot kept serving the pre-sync figure // until the analytics fresh TTL lapsed (~3 min). Mirrors the manual // water POST's own hard-evict (`nutrients/water/route.ts`) so an diff --git a/src/app/api/nutrients/water/route.ts b/src/app/api/nutrients/water/route.ts index c3a3fc70c..34610a703 100644 --- a/src/app/api/nutrients/water/route.ts +++ b/src/app/api/nutrients/water/route.ts @@ -162,7 +162,7 @@ async function postWater(request: NextRequest): Promise { await auditLog("nutrient.water.write", { userId: user.id, ipAddress: getClientIp(request), - // C4 — the day total that was there before. A `set` replaces it outright + // The day total that was there before. A `set` replaces it outright // and no per-entry ledger exists to reconstruct it from. details: { day, diff --git a/src/app/api/onboarding/answers/route.ts b/src/app/api/onboarding/answers/route.ts index d8cc35898..9a318f4eb 100644 --- a/src/app/api/onboarding/answers/route.ts +++ b/src/app/api/onboarding/answers/route.ts @@ -1,5 +1,5 @@ /** - * v1.39 (C1) — `PATCH /api/onboarding/answers`. + * v1.39 — `PATCH /api/onboarding/answers`. * * One step at a time, saved the moment it is answered, so leaving the flow and * coming back resumes where it stopped (design spec §Principles 4). The body diff --git a/src/app/api/onboarding/complete/__tests__/route.test.ts b/src/app/api/onboarding/complete/__tests__/route.test.ts index ccd333d57..4fbc8c9a8 100644 --- a/src/app/api/onboarding/complete/__tests__/route.test.ts +++ b/src/app/api/onboarding/complete/__tests__/route.test.ts @@ -8,7 +8,7 @@ vi.mock("@/lib/db", async (importOriginal) => { prisma: { user: { update: vi.fn(), findUnique: vi.fn() }, auditLog: { create: vi.fn() }, - // v1.39 (C1) — the needs half of the route looks for a setup row before + // v1.39 — the needs half of the route looks for a setup row before // it does anything. These cases are the LEGACY caller, which has none, // so the lookup answers null and the route falls straight through to the // acknowledgement it always returned. diff --git a/src/app/api/onboarding/complete/route.ts b/src/app/api/onboarding/complete/route.ts index ebb1b82d0..635827dd1 100644 --- a/src/app/api/onboarding/complete/route.ts +++ b/src/app/api/onboarding/complete/route.ts @@ -54,7 +54,7 @@ import { onboardingCompleteSchema } from "@/lib/validations/onboarding"; * /api/onboarding/restart` clears that marker when the person asks for the * questions again. * - * v1.39 (C2) — the dashboard order joins the derivation. The five-step + * v1.39 — the dashboard order joins the derivation. The five-step * wizard's goal slugs used to seed it from their own route; the answers now * carry both halves of Q2 — which modules are on, and what is on top — and * the seed keeps the wizard's one contract: only while the layout column is @@ -129,7 +129,7 @@ export const POST = apiHandler(async (request: NextRequest) => { data, }); - // v1.4.22 C4 — clear the proxy-readable onboarding cookie so the + // v1.4.22 — clear the proxy-readable onboarding cookie so the // next navigation drops the /onboarding redirect immediately. await setOnboardingPendingCookie(false); diff --git a/src/app/api/onboarding/disclaimer/route.ts b/src/app/api/onboarding/disclaimer/route.ts index acb33fff5..ed89cc61c 100644 --- a/src/app/api/onboarding/disclaimer/route.ts +++ b/src/app/api/onboarding/disclaimer/route.ts @@ -12,7 +12,7 @@ import { import { DISCLAIMER_VERSION } from "@/lib/onboarding/disclaimer"; /** - * v1.18.6 (DISC-02) — one-time medical-disclaimer acknowledgment. + * v1.18.6 — one-time medical-disclaimer acknowledgment. * * POST { version: string } * → stamps `users.disclaimer_acknowledged_at = now()` + diff --git a/src/app/api/onboarding/restart/route.ts b/src/app/api/onboarding/restart/route.ts index 1f6e44062..6c17bb117 100644 --- a/src/app/api/onboarding/restart/route.ts +++ b/src/app/api/onboarding/restart/route.ts @@ -1,5 +1,5 @@ /** - * v1.39 (C1) — `POST /api/onboarding/restart`. + * v1.39 — `POST /api/onboarding/restart`. * * "Set up again", from Settings. It puts the nine steps back to `pending`, * clears the flow's completion stamp and clears the derivation marker so the diff --git a/src/app/api/onboarding/tour/route.ts b/src/app/api/onboarding/tour/route.ts index b68d24e7b..dc04d6454 100644 --- a/src/app/api/onboarding/tour/route.ts +++ b/src/app/api/onboarding/tour/route.ts @@ -14,7 +14,7 @@ import { tourProgressSchema } from "@/lib/onboarding/tour-progress"; import { Prisma } from "@/generated/prisma/client"; /** - * v1.4.15 Phase B5 — onboarding tour completion endpoint. + * v1.4.15 — onboarding tour completion endpoint. * v1.18.6 — extended with the resumable module-tour progress point. * * Operations on a single resource: diff --git a/src/app/api/oura/callback/route.ts b/src/app/api/oura/callback/route.ts index b23802541..a68ff917d 100644 --- a/src/app/api/oura/callback/route.ts +++ b/src/app/api/oura/callback/route.ts @@ -18,7 +18,7 @@ import { getGlobalBoss } from "@/lib/jobs/boss-instance"; import { NextRequest, NextResponse } from "next/server"; /** - * v1.17.0 (F4) — OAuth callback from Oura. + * v1.17.0 — OAuth callback from Oura. * * Identity + CSRF mirror the Polar callback: the `state` param is a stateless * HMAC-signed token, enforced as a byte-exact double-submit against the diff --git a/src/app/api/oura/connect/route.ts b/src/app/api/oura/connect/route.ts index ec9fac009..f56807831 100644 --- a/src/app/api/oura/connect/route.ts +++ b/src/app/api/oura/connect/route.ts @@ -12,7 +12,7 @@ import { import { NextResponse } from "next/server"; /** - * v1.17.0 (F4) — redirect the user to the Oura consent screen. + * v1.17.0 — redirect the user to the Oura consent screen. * * Credentials resolve DB-first then env (`getOuraClientCredentials`): a user's * BYO Oura client id/secret wins, falling back to the shared env app diff --git a/src/app/api/oura/disconnect/route.ts b/src/app/api/oura/disconnect/route.ts index 56b4e3e8c..91ae0804b 100644 --- a/src/app/api/oura/disconnect/route.ts +++ b/src/app/api/oura/disconnect/route.ts @@ -8,7 +8,7 @@ import { checkRateLimit, rateLimitHeaders } from "@/lib/rate-limit"; import { markDisconnected } from "@/lib/integrations/status"; /** - * v1.17.0 (F4) — disconnect Oura for the current user. + * v1.17.0 — disconnect Oura for the current user. * * Clears the stored access + refresh token on `User` and parks the integration * status at `disconnected`. Imported `source = OURA` measurements are left diff --git a/src/app/api/oura/status/route.ts b/src/app/api/oura/status/route.ts index 20924f365..a8ed7abd5 100644 --- a/src/app/api/oura/status/route.ts +++ b/src/app/api/oura/status/route.ts @@ -11,7 +11,7 @@ import { } from "@/lib/integrations/sync-verdict"; /** - * v1.17.0 (F4) — Oura connection status for the current user. + * v1.17.0 — Oura connection status for the current user. * * `available` reports whether usable OAuth credentials resolve for this user — * per-user BYO keys (v1.17.1) first, then the shared env app. diff --git a/src/app/api/personal-records/route.ts b/src/app/api/personal-records/route.ts index cc05b352b..dea22ef2a 100644 --- a/src/app/api/personal-records/route.ts +++ b/src/app/api/personal-records/route.ts @@ -1,7 +1,7 @@ /** * GET /api/personal-records * - * v1.4.25 W8d — schema-only release of the PersonalRecord feature. + * v1.4.25 — schema-only release of the PersonalRecord feature. * The detection worker that actually populates rows lands in a later * release (v1.4.26 or v1.5 — TBD). This route exists today so the * v1.5 iOS-Swift app can build its query path against a stable diff --git a/src/app/api/polar/callback/route.ts b/src/app/api/polar/callback/route.ts index 8e89f51b1..bb52eed61 100644 --- a/src/app/api/polar/callback/route.ts +++ b/src/app/api/polar/callback/route.ts @@ -17,7 +17,7 @@ import { getGlobalBoss } from "@/lib/jobs/boss-instance"; import { NextRequest, NextResponse } from "next/server"; /** - * v1.17.0 (F4) — OAuth callback from Polar AccessLink. + * v1.17.0 — OAuth callback from Polar AccessLink. * * Identity + CSRF: the `state` URL param is a stateless HMAC-signed token. The * callback enforces (1) the param equals the httpOnly cookie byte-for-byte diff --git a/src/app/api/polar/connect/route.ts b/src/app/api/polar/connect/route.ts index 824d4cc47..84b205804 100644 --- a/src/app/api/polar/connect/route.ts +++ b/src/app/api/polar/connect/route.ts @@ -12,7 +12,7 @@ import { import { NextResponse } from "next/server"; /** - * v1.17.0 (F4) — redirect the user to the Polar AccessLink consent screen. + * v1.17.0 — redirect the user to the Polar AccessLink consent screen. * * BYO-key OAuth: the client id/secret resolve DB-first then env via * `getPolarClientCredentials` — a user's own AccessLink app when stored, diff --git a/src/app/api/polar/disconnect/route.ts b/src/app/api/polar/disconnect/route.ts index 07727ffa1..76b41e1a9 100644 --- a/src/app/api/polar/disconnect/route.ts +++ b/src/app/api/polar/disconnect/route.ts @@ -7,7 +7,7 @@ import { checkRateLimit, rateLimitHeaders } from "@/lib/rate-limit"; import { markDisconnected } from "@/lib/integrations/status"; /** - * v1.17.0 (F4) — disconnect Polar for the current user. + * v1.17.0 — disconnect Polar for the current user. * * Clears the stored OAuth token + member id on `User` and parks the integration * status at `disconnected`. Imported `source = POLAR` measurements are left diff --git a/src/app/api/polar/status/route.ts b/src/app/api/polar/status/route.ts index 1f11d7f0e..e2ac89f68 100644 --- a/src/app/api/polar/status/route.ts +++ b/src/app/api/polar/status/route.ts @@ -11,7 +11,7 @@ import { } from "@/lib/integrations/sync-verdict"; /** - * v1.17.0 (F4) — Polar connection status for the current user. + * v1.17.0 — Polar connection status for the current user. * * `available` reports whether usable OAuth credentials resolve for this user — * per-user BYO keys (v1.17.1) first, then the shared env app — so the card can diff --git a/src/app/api/settings/__tests__/telegram-test-locale.test.ts b/src/app/api/settings/__tests__/telegram-test-locale.test.ts index 70be88e51..1256cf7d4 100644 --- a/src/app/api/settings/__tests__/telegram-test-locale.test.ts +++ b/src/app/api/settings/__tests__/telegram-test-locale.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.27 F21 — settings/telegram/test locale path. + * v1.4.27 — settings/telegram/test locale path. * * Asserts the per-user Telegram test message renders in the user's * persisted `User.locale` rather than the hardcoded English string diff --git a/src/app/api/settings/telegram/test/route.ts b/src/app/api/settings/telegram/test/route.ts index 2cf0d33bb..b3d7e8dca 100644 --- a/src/app/api/settings/telegram/test/route.ts +++ b/src/app/api/settings/telegram/test/route.ts @@ -41,7 +41,7 @@ export const POST = apiHandler(async () => { return apiError("Bot token and chat ID must be saved first", 422); } - // v1.4.27 F21 — the test body now reads from the user's persisted + // v1.4.27 — the test body now reads from the user's persisted // locale rather than the hardcoded English string. The route talks // to `sendTelegramMessage` directly (it needs the boolean send // result for the UI) so we compose the body via the server diff --git a/src/app/api/share-links/__tests__/route.test.ts b/src/app/api/share-links/__tests__/route.test.ts index f762f5d95..33509c3c9 100644 --- a/src/app/api/share-links/__tests__/route.test.ts +++ b/src/app/api/share-links/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.11.0 (Epic C, C4) — owner clinician-share-link lifecycle. + * v1.11.0 — owner clinician-share-link lifecycle. * * Asserts the security-load-bearing properties: create returns the raw token * exactly once and persists ONLY its hash; the expiry cap is enforced; revoke diff --git a/src/app/api/user/thresholds/__tests__/route.test.ts b/src/app/api/user/thresholds/__tests__/route.test.ts index c8524b19d..573d737d2 100644 --- a/src/app/api/user/thresholds/__tests__/route.test.ts +++ b/src/app/api/user/thresholds/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.43 W6 — multi-issue 422 envelope on PUT /api/user/thresholds. + * v1.4.43 — multi-issue 422 envelope on PUT /api/user/thresholds. */ import { describe, it, expect, vi, beforeEach } from "vitest"; import { NextRequest } from "next/server"; diff --git a/src/app/api/user/thresholds/route.ts b/src/app/api/user/thresholds/route.ts index 22fcce714..bd8ec93aa 100644 --- a/src/app/api/user/thresholds/route.ts +++ b/src/app/api/user/thresholds/route.ts @@ -91,7 +91,7 @@ export const PUT = apiHandler(async (request: NextRequest) => { const parsed = thresholdsUpdateSchema.safeParse(body); if (!parsed.success) { - // v1.4.43 W6 — multi-issue 422. + // v1.4.43 — multi-issue 422. return returnAllZodIssues(parsed.error, 422); } diff --git a/src/app/api/version/route.ts b/src/app/api/version/route.ts index 9c0435b8e..953b653c7 100644 --- a/src/app/api/version/route.ts +++ b/src/app/api/version/route.ts @@ -3,7 +3,7 @@ import { apiSuccess } from "@/lib/api-response"; import { offlineGeoReady, resolveGeoProviderHost } from "@/lib/geo"; import packageJson from "../../../../package.json"; -// v1.4.27 R5 — `offlineGeoEnabled` reads from the same source the geo +// v1.4.27 — `offlineGeoEnabled` reads from the same source the geo // resolver uses for the runtime fallback gate, so the public state and // the runtime behaviour cannot disagree. The check hits the filesystem // (`existsSync` on the `.empty` marker + the City MMDB) which forces @@ -42,7 +42,7 @@ export const dynamic = "force-dynamic"; * `/api/admin/status`, so it adds nothing to tell instances apart by. */ export const GET = apiHandler(async () => { - // v1.4.43 B11 — prefer the build-arg-injected env var so the runtime + // v1.4.43 — prefer the build-arg-injected env var so the runtime // version cannot drift from the CI release tag. The Dockerfile takes // `NEXT_PUBLIC_APP_VERSION` as a build arg from the docker-publish // workflow (`build-args: NEXT_PUBLIC_APP_VERSION=${{ github.ref_name diff --git a/src/app/api/withings/callback/__tests__/route.test.ts b/src/app/api/withings/callback/__tests__/route.test.ts index 832f934d7..f63707762 100644 --- a/src/app/api/withings/callback/__tests__/route.test.ts +++ b/src/app/api/withings/callback/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.48 M3 — unit-level coverage for the atomic nonce-consumption + * v1.4.48 — unit-level coverage for the atomic nonce-consumption * branch. * * The integration tests in `tests/integration/withings-oauth*.test.ts` @@ -181,10 +181,10 @@ describe("withings/callback atomic nonce consumption (M3)", () => { }); it("non-P2025 delete failure (e.g. connection drop) surfaces a Wide-Event warning carrying err.name only, and bounces to the error page", async () => { - // M7 — a real infra failure on the consume-delete must not be + // A real infra failure on the consume-delete must not be // silently swallowed. // - // v1.4.49 QA-2 — the warning template now interpolates `err.name` + // v1.4.49 — the warning template now interpolates `err.name` // rather than `${err}`. A Prisma error whose `message` echoes the // offending value (e.g. the raw nonce) into the wide-event log // would otherwise leak it. diff --git a/src/app/api/withings/callback/route.ts b/src/app/api/withings/callback/route.ts index 122d8b6af..e3c7d169c 100644 --- a/src/app/api/withings/callback/route.ts +++ b/src/app/api/withings/callback/route.ts @@ -15,7 +15,7 @@ import { timingSafeEqual } from "node:crypto"; /** * OAuth callback from Withings. Exchanges code for tokens and stores them. * - * v1.4.47 W6 — `state` is now a fully-random 22-character base64url + * v1.4.47 — `state` is now a fully-random 22-character base64url * nonce keyed against the `WithingsOAuthState` ledger, rather than the * legacy `${user.id}:${random16}` shape. The user identity bound to * the in-flight handshake is resolved via the ledger's `userId` @@ -24,7 +24,7 @@ import { timingSafeEqual } from "node:crypto"; * runs. The row is consumed (deleted) on every exit branch so a * replay of the same nonce fails the second time. * - * v1.4.48 M3 — the row is now consumed via a single delete-first + * v1.4.48 — the row is now consumed via a single delete-first * round-trip. Prisma `delete` returns the row on success, so the * post-delete validity checks (`expiresAt`, `userId`) still run * against the consumed payload. Two concurrent callbacks with the @@ -111,7 +111,7 @@ export const GET = apiHandler(async (request: NextRequest) => { // page. Re-throwing would land on the apiHandler's 500 path and // strand the user without a clean redirect. // - // v1.4.49 QA-2 — the warning interpolates `err.name` rather than + // v1.4.49 — the warning interpolates `err.name` rather than // `${err}` so a Prisma error message echoing the offending value // (e.g. the raw nonce) into the wide-event log cannot leak it. const errName = err instanceof Error ? err.name : "unknown"; @@ -173,7 +173,7 @@ export const GET = apiHandler(async (request: NextRequest) => { const expiresAt = new Date(Date.now() + tokens.expires_in * 1000); const subscriptionRetryAt = new Date(); - // v1.4.25 W5d — persist the OAuth scope we requested. The token + // v1.4.25 — persist the OAuth scope we requested. The token // response doesn't echo the granted scopes back as a top-level // field, but Withings honours the scope param verbatim — there's // no scope downgrade unless the user explicitly revokes one in diff --git a/src/app/api/withings/connect/__tests__/route.test.ts b/src/app/api/withings/connect/__tests__/route.test.ts index 32d1044d7..01e7c1e1f 100644 --- a/src/app/api/withings/connect/__tests__/route.test.ts +++ b/src/app/api/withings/connect/__tests__/route.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.48 L5 + L14 — GET /api/withings/connect + * v1.4.48 — GET /api/withings/connect * * Pins: * 1. Happy path: mints a nonce, creates a `WithingsOAuthState` row, diff --git a/src/app/api/withings/connect/route.ts b/src/app/api/withings/connect/route.ts index 3c120d0b4..689c2184f 100644 --- a/src/app/api/withings/connect/route.ts +++ b/src/app/api/withings/connect/route.ts @@ -16,7 +16,7 @@ import { shouldEmitSecureCookie } from "@/lib/auth/secure-cookie"; /** * Redirects the user to Withings OAuth authorization page. * - * v1.4.47 W6 — state is now a fully-random 22-character base64url + * v1.4.47 — state is now a fully-random 22-character base64url * nonce backed by a 10-minute `WithingsOAuthState` row, rather than * the legacy `${user.id}:${random16}` shape. The cookie carries JUST * the nonce (no user id), the OAuth state URL param carries JUST the @@ -24,7 +24,7 @@ import { shouldEmitSecureCookie } from "@/lib/auth/secure-cookie"; * column. Closes the v1.4.43 security audit L-1 finding where the * user id was recoverable from log entries / network captures. * - * v1.4.48 L5 + L14 — endpoint is now rate-limited per-user (10 calls + * v1.4.48 — endpoint is now rate-limited per-user (10 calls * per 60 s) so a logged-in user with valid creds can't spam ledger * rows for the full 10-min TTL window. The row-create call is wrapped * in try/catch; a transient DB glitch now lands on the existing diff --git a/src/app/api/withings/status/route.ts b/src/app/api/withings/status/route.ts index baeb2d754..79fe1c729 100644 --- a/src/app/api/withings/status/route.ts +++ b/src/app/api/withings/status/route.ts @@ -112,7 +112,7 @@ export const GET = apiHandler(async () => { tokenExpired, tokenRefreshFailed, tokenExpiresAt, - // v1.4.25 W5d — `scope` is the comma-separated OAuth scope string + // v1.4.25 — `scope` is the comma-separated OAuth scope string // returned during the auth flow; `hasActivityScope` is the // pre-computed convenience flag the Settings reconnect banner // reads. Null `scope` means a legacy v1.4.24 connection that's diff --git a/src/app/api/withings/webhook/[token]/__tests__/route.test.ts b/src/app/api/withings/webhook/[token]/__tests__/route.test.ts index 727081148..7980c43e0 100644 --- a/src/app/api/withings/webhook/[token]/__tests__/route.test.ts +++ b/src/app/api/withings/webhook/[token]/__tests__/route.test.ts @@ -17,7 +17,7 @@ vi.mock("@/lib/withings/sync", () => ({ syncUserMeasurements: vi.fn(), })); -// v1.4.25 W17b/c — activity / sleep paths dispatch through pg-boss. +// v1.4.25 — activity / sleep paths dispatch through pg-boss. vi.mock("@/lib/jobs/boss-instance", () => ({ getGlobalBoss: vi.fn(), })); diff --git a/src/app/api/withings/webhook/__tests__/route.test.ts b/src/app/api/withings/webhook/__tests__/route.test.ts index 569544557..7a66495ff 100644 --- a/src/app/api/withings/webhook/__tests__/route.test.ts +++ b/src/app/api/withings/webhook/__tests__/route.test.ts @@ -17,7 +17,7 @@ vi.mock("@/lib/withings/sync", () => ({ syncUserMeasurements: vi.fn(), })); -// v1.4.25 W17b/c — activity / sleep paths dispatch through pg-boss. +// v1.4.25 — activity / sleep paths dispatch through pg-boss. // Mock the boss-instance accessor to return a fake boss for the // appli=16 / 44 tests and `null` otherwise. vi.mock("@/lib/jobs/boss-instance", () => ({ diff --git a/src/app/api/workouts/[id]/route.ts b/src/app/api/workouts/[id]/route.ts index 033cebee8..428c03c59 100644 --- a/src/app/api/workouts/[id]/route.ts +++ b/src/app/api/workouts/[id]/route.ts @@ -71,7 +71,7 @@ export const GET = apiHandler( annotate({ action: { name: "workouts.detail" }, meta: { workoutId: id } }); - // v1.18.0 B1 — gate the detail surface behind the workouts module. + // v1.18.0 — gate the detail surface behind the workouts module. const gate = await requireModuleEnabled(user.id, "workouts"); if (!gate.enabled) return gate.response; // The stored Activity Insight paragraph is model text: it is read and diff --git a/src/app/api/workouts/__tests__/batch-create.test.ts b/src/app/api/workouts/__tests__/batch-create.test.ts index ae164b7e8..0a434f9a9 100644 --- a/src/app/api/workouts/__tests__/batch-create.test.ts +++ b/src/app/api/workouts/__tests__/batch-create.test.ts @@ -200,7 +200,7 @@ beforeEach(() => { (async (args: { data: Array<{ workoutId: string }> }) => args.data.map((row) => ({ workoutId: row.workoutId }))) as never, ); - // v1.4.43 W9 — default to no per-user source-priority override so + // v1.4.43 — default to no per-user source-priority override so // the write-time picker walks the canonical default ladder. Tests // that exercise a custom ladder override this in-line. vi.mocked(prisma.user.findUnique).mockResolvedValue({ diff --git a/src/app/api/workouts/__tests__/canonical-dedup.test.ts b/src/app/api/workouts/__tests__/canonical-dedup.test.ts index 4fcc2c191..b5f0f181f 100644 --- a/src/app/api/workouts/__tests__/canonical-dedup.test.ts +++ b/src/app/api/workouts/__tests__/canonical-dedup.test.ts @@ -1,7 +1,7 @@ /** * Unit suite for `GET /api/workouts` — the canonical-dedup contract. * - * v1.4.27 B7 / BL-P2-3 — original wiring with `pickCanonicalWorkout()`. + * v1.4.27 — original wiring with `pickCanonicalWorkout()`. * v1.4.32 — swap to `pickCanonicalWorkoutRows()` (v1.4.30) so the * per-user source-priority ladder governs the canonical pick. The * test pins: diff --git a/src/app/api/workouts/batch/route.ts b/src/app/api/workouts/batch/route.ts index 69f1979f5..c25beccca 100644 --- a/src/app/api/workouts/batch/route.ts +++ b/src/app/api/workouts/batch/route.ts @@ -97,14 +97,14 @@ import { EXTERNAL_SOURCE } from "@/lib/measurements/external-source"; import { encryptRouteGeometry } from "@/lib/workouts/route-geometry-cipher"; import { Prisma, type MeasurementSource } from "@/generated/prisma/client"; -// v1.4.25 W16c — push-suppression threshold for workout PRs. A batch +// v1.4.25 — push-suppression threshold for workout PRs. A batch // larger than this fires the detection job with `silent: true` so a // multi-year HKWorkout backfill doesn't spam the user with hundreds of // pushes. Matches the measurements-batch threshold so behaviour stays // uniform across both ingest paths. const PR_DETECTION_SILENT_THRESHOLD = 50; -// v1.4.25 W10 reconcile (security H-2 parity): cap batch ingest at 60 +// v1.4.25 (security H-2 parity): cap batch ingest at 60 // batches per user per minute. Healthy iOS sync drains its HealthKit // observer queue in well under one batch per minute (the observer // pattern coalesces); 60/min × 100 workouts/batch = 6 000 workouts/ @@ -246,7 +246,7 @@ async function postBatch(request: NextRequest): Promise { // its rows is resolved from the credential below rather than from the body. const scoped = isScopedCredential(auth); - // v1.4.43 W9 — resolve the per-user source-priority blob once at + // v1.4.43 — resolve the per-user source-priority blob once at // the start of the handler so the write-time canonical-row picker // walks the SAME ladder the read-time picker walks. Without this // lookup a user who customised Settings → Sources (e.g. MANUAL > @@ -283,7 +283,7 @@ async function postBatch(request: NextRequest): Promise { } } - // v1.4.25 W10 reconcile (security H-2 parity): per-user rate limit. + // v1.4.25 (security H-2 parity): per-user rate limit. // Without it, a leaked wildcard iOS token can sustain unbounded // writes (100 workouts/batch × N batches/sec) and degrade the // database for every other user on the host. 60 batches/min/user is @@ -534,7 +534,7 @@ async function postBatch(request: NextRequest): Promise { const overwrittenIdByIndex = new Map(); if (survivors.length > 0) { - // v1.4.42 W5 — only the write-time dedup survivors are probed + // v1.4.42 — only the write-time dedup survivors are probed // against existing rows; the dropped twins already carry a // `duplicate` status and never reach the DB. const dedupCandidates = survivors @@ -871,7 +871,7 @@ async function postBatch(request: NextRequest): Promise { }, }); - // v1.4.25 W16c — kick off PR detection for this user. Suppress push + // v1.4.25 — kick off PR detection for this user. Suppress push // notifications for historical-backfill batches so a multi-year // HKWorkout import doesn't fire hundreds of pushes during initial // sync. The detector also scans Measurement history on the same @@ -906,7 +906,7 @@ async function postBatch(request: NextRequest): Promise { }, }); - // v1.4.34 IW-G — bust per-user analytics + achievements + workouts + // v1.4.34 — bust per-user analytics + achievements + workouts // caches when at least one row landed. Workouts ride on the // measurements bucket because achievements / analytics also touch // workout-derived metrics. diff --git a/src/app/api/workouts/route.ts b/src/app/api/workouts/route.ts index 685a56771..0dcf1da9b 100644 --- a/src/app/api/workouts/route.ts +++ b/src/app/api/workouts/route.ts @@ -73,7 +73,7 @@ export const GET = apiHandler(async (request: NextRequest) => { const { user } = await requireRecordAuth("read", "measurements"); annotate({ action: { name: "workouts.list" } }); - // v1.18.0 B1 — the workouts surface is a toggleable module. A disabled + // v1.18.0 — the workouts surface is a toggleable module. A disabled // account must not be able to read the list even with a valid Bearer // token. The batch INGEST route stays ungated so synced data still // lands while the surface is hidden. diff --git a/src/app/auth/login/page.tsx b/src/app/auth/login/page.tsx index 08915392c..c6df5ae81 100644 --- a/src/app/auth/login/page.tsx +++ b/src/app/auth/login/page.tsx @@ -74,7 +74,7 @@ export default function LoginPage() { } | null>(null); // Guards the conditional-UI passkey autofill so it starts at most once. const autofillStarted = useRef(false); - // v1.4.27 MB3 — explicit error-region id so the email + password + // v1.4.27 — explicit error-region id so the email + password // inputs reference the banner via `aria-describedby`. Screen readers // pair the error with the field instead of announcing it as a // standalone alert detached from the form. @@ -382,7 +382,7 @@ export default function LoginPage() { return (
- {/* v1.4.27 MB7 / CF-61 — drop `p-8` to `p-6 sm:p-8` so the + {/* v1.4.27 — drop `p-8` to `p-6 sm:p-8` so the auth card breathes on Galaxy Fold / Pixel 5 without stealing half the visible viewport with padding. Tablet+ keeps the generous 32 px padding. */} diff --git a/src/app/auth/register/page.tsx b/src/app/auth/register/page.tsx index e8cb2498f..3fe76dc18 100644 --- a/src/app/auth/register/page.tsx +++ b/src/app/auth/register/page.tsx @@ -47,7 +47,7 @@ export default function RegisterPage() { const [error, setError] = useState(null); const [loading, setLoading] = useState(false); const { t } = useTranslations(); - // v1.4.27 MB3 — explicit error-region id wired to every required + // v1.4.27 — explicit error-region id wired to every required // input via `aria-describedby` so screen readers pair the validation // failure with the offending field instead of announcing it as a // standalone alert detached from the form. @@ -60,7 +60,7 @@ export default function RegisterPage() { setLoading(true); try { - // v1.4.25 W7 — ship the browser-detected timezone with the + // v1.4.25 — ship the browser-detected timezone with the // signup payload. Server validates against the IANA list and // falls back to the admin server-default if the value is // bogus, so a tampered or proxied request can never poison @@ -166,7 +166,7 @@ export default function RegisterPage() { return (
- {/* v1.4.27 MB7 / CF-61 — drop `p-8` to `p-6 sm:p-8` so the + {/* v1.4.27 — drop `p-8` to `p-6 sm:p-8` so the auth card breathes on Galaxy Fold / Pixel 5 without stealing half the visible viewport with padding. Tablet+ keeps the generous 32 px padding. */} @@ -266,7 +266,7 @@ export default function RegisterPage() {
)} - {/* v1.4.27 MB6 — lift the submit to the 44 px tap-target + {/* v1.4.27 — lift the submit to the 44 px tap-target floor and the `lg` size so the primary action stays reachable on a narrow viewport without falling under the iOS keyboard's accessory bar. */} diff --git a/src/app/c/[token]/__tests__/page.test.tsx b/src/app/c/[token]/__tests__/page.test.tsx index 35416057d..8bee88cea 100644 --- a/src/app/c/[token]/__tests__/page.test.tsx +++ b/src/app/c/[token]/__tests__/page.test.tsx @@ -1,5 +1,5 @@ /** - * v1.11.0 (Epic C, C5) — public clinician-view page gating. + * v1.11.0 — public clinician-view page gating. * v1.18.7 — passphrase gate: the page checks gate state first, gates a * protected link until the unlock cookie is valid, and never bumps access * counters on a gate-blocked hit. diff --git a/src/app/coach/conversations/page.tsx b/src/app/coach/conversations/page.tsx index 2b6aab91e..ca61706a1 100644 --- a/src/app/coach/conversations/page.tsx +++ b/src/app/coach/conversations/page.tsx @@ -43,7 +43,7 @@ import { cn } from "@/lib/utils"; * deep-link open mechanism. Reuses the SAME `/coach?c=` open * mechanism the rail uses. * - * v1.30.2 (QoL H1) — the page is a thin read over + * v1.30.2 — the page is a thin read over * `useCoachConversationHistory()` (cursor-paginated + server-searched, see * the route's doc comment), which the drawer's `` also * consumes — the two surfaces can no longer drift onto different @@ -62,7 +62,7 @@ function CoachConversationsBody({ readOnly }: { readOnly: boolean }) { const [openId, setOpenId] = useState(null); const router = useRouter(); const [filter, setFilter] = useState(""); - // v1.30.2 (QoL H1) — search now drives the server-side title query; + // v1.30.2 — search now drives the server-side title query; // debounce the keystroke draft instead of re-issuing a request per key. const debouncedFilter = useDebouncedValue(filter, 200); const { @@ -109,7 +109,7 @@ function CoachConversationsBody({ readOnly }: { readOnly: boolean }) { router.push(`/coach?c=${id}`); } - // v1.30.1 M5 — one tap deletes (with an Undo toast); see + // v1.30.1 — one tap deletes (with an Undo toast); see // `use-coach.ts`'s `useDeleteCoachConversationWithUndo` for the // rationale — this mirrors the rail's identical fix. function handleDeleteRequest(id: string) { @@ -282,7 +282,7 @@ function CoachConversationsBody({ readOnly }: { readOnly: boolean }) { )) )} - {/* v1.30.2 (QoL H1) — same IntersectionObserver sentinel as the + {/* v1.30.2 — same IntersectionObserver sentinel as the drawer rail: scrolling this div into view pulls the next cursor page. Empty + aria-hidden; the status row below is the a11y signal. */} diff --git a/src/app/coach/page-client.tsx b/src/app/coach/page-client.tsx index 01b044e6e..0e1d404b1 100644 --- a/src/app/coach/page-client.tsx +++ b/src/app/coach/page-client.tsx @@ -33,14 +33,14 @@ import { coachScopeSourceSchema } from "@/lib/ai/coach/types"; * operator's master flag is off or the user opted out, the page redirects * back to `/insights` rather than painting a dead chat shell. * - * v1.18.10 (W4) — the page is now a single full-bleed conversation + * v1.18.10 — the page is now a single full-bleed conversation * surface: the inner panel chrome (the bordered/rounded card) is gone so * the chat is no longer a window-inside-a-window, and the top "back to the * drawer" minimize control is removed (pointless on the dedicated page — * the separate bottom-right FAB drawer is the compact surface). The chat * claims the full content width and the viewport height below the top bar. * - * v1.18.11 (W11) — ChatGPT-style IA: the page drops its own header bar + * v1.18.11 — ChatGPT-style IA: the page drops its own header bar * AND the top conversation-history strip entirely. The composer is the * single control hub (leading `+` actions menu = new chat + open * conversations, settings deep-link, mic, send); conversation history is a @@ -49,7 +49,7 @@ import { coachScopeSourceSchema } from "@/lib/ai/coach/types"; * the active conversation. All of that lives in ``'s * page branch, so the page stays a thin gating + sizing wrapper. * - * v1.19.1 (C1/C5) — the Coach now DEFAULTS to the new-chat hero, reversing + * v1.19.1 — the Coach now DEFAULTS to the new-chat hero, reversing * the v1.19.0 (W7) "always resume most-recent" default the maintainer * disliked. Resolution order on mount: * - `?c=` → open that specific thread (explicit deep-link, unchanged). diff --git a/src/app/global-error.tsx b/src/app/global-error.tsx index 0bcb37ce8..449ecc679 100644 --- a/src/app/global-error.tsx +++ b/src/app/global-error.tsx @@ -68,7 +68,7 @@ export default function GlobalError({ backgroundColor: "#1e1f29", color: "#f8f8f2", margin: 0, - // v1.4.27 MB6 — `100dvh` follows the dynamic viewport on + // v1.4.27 — `100dvh` follows the dynamic viewport on // iOS Safari (URL-bar collapse) so the wrapper still // anchors centred when the browser chrome animates in or // out. `safe-area-inset-top` keeps the headline out from @@ -82,7 +82,7 @@ export default function GlobalError({ >
{/* - v1.4.43 QoL — bilingual lockup so a German user landing + v1.4.43 — bilingual lockup so a German user landing in this boundary doesn't read pure English in an already- bad-state moment. The root layout has failed by the time this fires, so no i18n provider is available; the static @@ -150,7 +150,7 @@ export default function GlobalError({ color: "#282a36", border: "none", borderRadius: 6, - // v1.4.27 MB6 — 44 px minimum height matches the + // v1.4.27 — 44 px minimum height matches the // app-wide tap-target floor; the larger inline padding // keeps the labels comfortably finger-tap-sized. minHeight: 44, diff --git a/src/app/insights/__tests__/ecg-cross-link-mount.test.tsx b/src/app/insights/__tests__/ecg-cross-link-mount.test.tsx index 13bebd8e8..2f9c1a86f 100644 --- a/src/app/insights/__tests__/ecg-cross-link-mount.test.tsx +++ b/src/app/insights/__tests__/ecg-cross-link-mount.test.tsx @@ -6,7 +6,7 @@ import { I18nProvider } from "@/lib/i18n/context"; import { queryKeys } from "@/lib/query-keys"; /** - * v1.30 (UX/IA audit H1) — `` now mounts on the resting-pulse + * v1.30 — `` now mounts on the resting-pulse * and HRV sub-pages (previously only on `/insights/pulse`), threaded through * ``. The pointer self-gates on * recordings, so the shared `insightsEcgList` cache is pre-seeded with one diff --git a/src/app/insights/__tests__/explainer-metric-keys.test.ts b/src/app/insights/__tests__/explainer-metric-keys.test.ts index b9c867def..d65366d12 100644 --- a/src/app/insights/__tests__/explainer-metric-keys.test.ts +++ b/src/app/insights/__tests__/explainer-metric-keys.test.ts @@ -28,7 +28,7 @@ import { join } from "node:path"; * `i18n-locale-integrity.test.ts` then propagates the EN guarantee across * the other five locales. * - * v1.15.12 B1 — the score-anatomy detail page (`scores/[metric]/page.tsx`) + * v1.15.12 — the score-anatomy detail page (`scores/[metric]/page.tsx`) * picks its explainer key at runtime from a closed `Record<…, string>` * literal (`explainerMetric={EXPLAINER_METRIC[metric]}`), one of five score * suffixes. That single dynamic site is resolved STATICALLY here by parsing @@ -68,7 +68,7 @@ interface PropSite { const LITERAL_PATTERN = /explainerMetric="([a-zA-Z][a-zA-Z0-9]*)"/g; const DYNAMIC_PATTERN = /explainerMetric=\{/; -// v1.15.12 B1 — the one allowed dynamic site: the score-anatomy detail page +// v1.15.12 — the one allowed dynamic site: the score-anatomy detail page // reads its explainer key from a closed `EXPLAINER_METRIC` record literal. // We resolve it statically — parse the record's string-literal values — so // the dynamic site stays verifiable rather than escaping the guard. diff --git a/src/app/insights/__tests__/page-skeletons.test.ts b/src/app/insights/__tests__/page-skeletons.test.ts index 5767f6e05..366d54492 100644 --- a/src/app/insights/__tests__/page-skeletons.test.ts +++ b/src/app/insights/__tests__/page-skeletons.test.ts @@ -3,7 +3,7 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; /** - * v1.4.43 W11 — insights mother-page dynamic-skeleton guards. + * v1.4.43 — insights mother-page dynamic-skeleton guards. * v1.11.3 — rewritten for the shared `BlockSkeleton` loader. * * The `next/dynamic` loading placeholders for the below-the-hero blocks diff --git a/src/app/insights/blood-pressure/page.tsx b/src/app/insights/blood-pressure/page.tsx index 87679fcc8..a6772ce9c 100644 --- a/src/app/insights/blood-pressure/page.tsx +++ b/src/app/insights/blood-pressure/page.tsx @@ -21,7 +21,7 @@ import { SubPageShell } from "@/components/insights/sub-page-shell"; import { useBpTargetBand } from "@/lib/queries/use-bp-target-band"; /** - * v1.4.25 W4 — `/insights/blood-pressure`. + * v1.4.25 — `/insights/blood-pressure`. * * Routed Blood-Pressure sub-page. The mother page hosts the overview * (hero + briefing + advisor); this page concentrates on BP-only depth: @@ -29,15 +29,15 @@ import { useBpTargetBand } from "@/lib/queries/use-bp-target-band"; * assessment, and the chart's target zones — the band the server resolved * for the record on screen (the user's own target, else the age default). * - * v1.4.27 F17 — when the user has zero BP observations, the page + * v1.4.27 — when the user has zero BP observations, the page * short-circuits to an empty-state CTA pointing at * `/measurements?add=BLOOD_PRESSURE`. Apple Health convention: empty * surfaces lead to onboarding hints rather than chart skeletons. - * v1.4.27 MB6 — the previous `/measurements/new` href hit a 404; the + * v1.4.27 — the previous `/measurements/new` href hit a 404; the * measurements page now consumes `?add=` and auto-opens the * dialog with the matching default type. * - * v1.4.28 R3d (BK-F-H1 + BK-F-M1) — the analytics fetch and the + * v1.4.28 — the analytics fetch and the * empty-state render were lifted into `useInsightsAnalytics()` and * ``; the page module is now Apple-Health-lean. */ diff --git a/src/app/insights/bmi/page.tsx b/src/app/insights/bmi/page.tsx index 5ec84f3fa..735c4d7ea 100644 --- a/src/app/insights/bmi/page.tsx +++ b/src/app/insights/bmi/page.tsx @@ -17,7 +17,7 @@ import { MetricTargetSummary } from "@/components/insights/metric-target-summary import { SubPageShell } from "@/components/insights/sub-page-shell"; /** - * v1.4.25 W4 — `/insights/bmi`. + * v1.4.25 — `/insights/bmi`. * * Routed BMI sub-page. BMI is derived from `WEIGHT / (height/100)^2`, * so the chart sets `valueMode="bmi"` on `` and the @@ -25,7 +25,7 @@ import { SubPageShell } from "@/components/insights/sub-page-shell"; * the chart can't compute; we surface the same plain empty-state the * mother page used. * - * v1.4.28 R3d (BK-F-H1 + BK-F-M1) — analytics fetch + the + * v1.4.28 — analytics fetch + the * weight-not-logged-yet branch consume the shared hook + primitive. * The "height not set" branch keeps its bespoke `` because * the CTA targets `/settings/account`, not the measurement onboarding. @@ -45,7 +45,7 @@ export default function InsightsBmiPage() { const { isEmpty } = useInsightsAnalytics("BMI"); - // v1.4.27 F17 — BMI is derived from WEIGHT. When no weight readings + // v1.4.27 — BMI is derived from WEIGHT. When no weight readings // exist yet, the existing "set your height" branch can never compute // anything useful either; surface the empty-state CTA pointing at // `/measurements?add=WEIGHT` instead so the user logs the weight diff --git a/src/app/insights/ecg/__tests__/page.test.tsx b/src/app/insights/ecg/__tests__/page.test.tsx index d516d91f6..2698461ae 100644 --- a/src/app/insights/ecg/__tests__/page.test.tsx +++ b/src/app/insights/ecg/__tests__/page.test.tsx @@ -6,7 +6,7 @@ import { I18nProvider } from "@/lib/i18n/context"; import { queryKeys } from "@/lib/query-keys"; /** - * v1.30 (UX/IA audit H1) — `/insights/ecg` routed sub-page. + * v1.30 — `/insights/ecg` routed sub-page. * * The page reuses `` (verbatim, `hideHeading` set so the shell * owns the heading) and self-gates to an empty state when the account has no diff --git a/src/app/insights/layout.tsx b/src/app/insights/layout.tsx index e1150ee07..2d544fdf8 100644 --- a/src/app/insights/layout.tsx +++ b/src/app/insights/layout.tsx @@ -3,21 +3,21 @@ import type { ReactNode } from "react"; import { InsightsLayoutShell } from "@/components/insights/insights-layout-shell"; /** - * v1.4.25 W4 — shared layout for the routed `/insights` sub-pages. + * v1.4.25 — shared layout for the routed `/insights` sub-pages. * * Hosts the sticky `` so the metric pills + (mother- * page-only) regenerate affordance stay reachable while a user drills * into a sub-page. The actual page body still owns its own data fetches * — the layout is a thin presentational frame. * - * v1.4.27 R3d MB4 — the Coach drawer mounts above the routed children + * v1.4.27 — the Coach drawer mounts above the routed children * inside a `` so navigating into a sub-page no * longer unmounts the drawer. Every sub-page mounts a * `` that calls `askCoach()` on the same context; * the mother `/insights/page.tsx` consumes the same hook for its hero * strip + suggested-prompt chips. * - * v1.4.34 IW-B — `` + `` now + * v1.4.34 — `` + `` now * live on the global `` (`src/components/layout/auth-shell.tsx`) * so every authenticated route (dashboard included) can open the drawer * from the same context. diff --git a/src/app/insights/medications/page.tsx b/src/app/insights/medications/page.tsx index b8e703978..1545dc5e5 100644 --- a/src/app/insights/medications/page.tsx +++ b/src/app/insights/medications/page.tsx @@ -27,7 +27,7 @@ import { apiGet } from "@/lib/api/api-fetch"; import { getMedicationCategoryLabel } from "@/lib/medications/category-label"; /** - * v1.4.25 W4 — `/insights/medications`. + * v1.4.25 — `/insights/medications`. * * Routed Medication-Compliance sub-page. Mirrors the per-medication * grid from the v1.4.24 mother page (compliance bars + heatmap + @@ -189,7 +189,7 @@ export default function InsightsMedikamentePage() { } if (medications.length === 0) { - // v1.4.27 F17 — medication compliance is event-driven so the + // v1.4.27 — medication compliance is event-driven so the // gate reads `medications.length > 0`. CTA targets `/medications` // (the dedicated medication-management surface). return ( @@ -272,7 +272,7 @@ export default function InsightsMedikamentePage() { {t("insights.compliance7d")} {med.compliance7}%
- {/* v1.4.33 IW9 — aria-label so the bar has an + {/* v1.4.33 — aria-label so the bar has an accessible name (Lighthouse a11y 91/94). */} 0`. CTA targets `/mood` (the dedicated // mood-logging surface) — short-circuits the user to the quickest // path to log their first entry. // - // v1.4.28 R3d (BK-F-M1) — empty-state render delegates to the shared + // v1.4.28 — empty-state render delegates to the shared // `` primitive. The mood data path stays on // `/api/insights/comprehensive` because the `moodSummary.count` // signal is event-driven, not sensor-aggregated. diff --git a/src/app/insights/page-client.tsx b/src/app/insights/page-client.tsx index c57503a45..55c1e4812 100644 --- a/src/app/insights/page-client.tsx +++ b/src/app/insights/page-client.tsx @@ -39,13 +39,13 @@ import { useInsightsAdvisorQuery } from "@/components/insights/use-insights-advi import { useAnalyticsQuery } from "@/lib/queries/use-analytics-query"; import { useDashboardSnapshot } from "@/lib/queries/use-dashboard-snapshot"; import { useDashboardDerived } from "@/components/insights/derived/use-dashboard-derived"; -// v1.4.41 W-ORG — shared shape lives in `src/types/analytics.ts` as +// v1.4.41 — shared shape lives in `src/types/analytics.ts` as // `InsightsAnalyticsData`; aliased back to the local name to keep the // rest of this file readable. import type { InsightsAnalyticsData as AnalyticsData } from "@/types/analytics"; /** - * v1.4.33 IW2 — defer the below-the-fold mother-page blocks behind + * v1.4.33 — defer the below-the-fold mother-page blocks behind * `next/dynamic`. `` (the only above-the-fold piece) stays an * eager import so the initial paint shows the greeting and health-score * badge without a flash; the briefing, correlation row and trends row @@ -252,7 +252,7 @@ const CycleRingTile = dynamic( const EAGER_OVERVIEW_SECTIONS = 3; /** - * v1.4.25 W4d — Insights mother page. + * v1.4.25 — Insights mother page. * * The page used to be a single 1.8k-LOC monolith that scroll-anchored * six per-metric sections beneath the hero. W4a/c carved those out @@ -289,7 +289,7 @@ export default function InsightsPageClient() { const { t } = useTranslations(); const queryClient = useQueryClient(); - // v1.30.1 M12 — the overview fans out into ~6 independent queries + // v1.30.1 — the overview fans out into ~6 independent queries // (comprehensive / advisor / analytics / snapshot / derived batch / // layout) mounted by different hooks below, so there's no single // `refetch()` that covers the page. Refetching every currently-mounted @@ -302,7 +302,7 @@ export default function InsightsPageClient() { ); const pull = usePullToRefresh({ onRefresh: refreshInsights }); - // v1.4.33 IW9 — scroll-to-top on route mount centralised in the + // v1.4.33 — scroll-to-top on route mount centralised in the // shared `useScrollResetOnRoute()` hook. The mother page + the // `` both consume the same hook; the legacy duplicate // RAF that lived here pre-v1.4.33 produced a visible double-snap on @@ -316,7 +316,7 @@ export default function InsightsPageClient() { // on this page is data and never waits on it. const briefing = useAiCapability("briefing"); - // v1.4.36 W1 — drop the page-level `isLoading` gate that used to + // v1.4.36 — drop the page-level `isLoading` gate that used to // block the entire shell on `/api/insights/comprehensive`. The // comprehensive payload still feeds the empty-state decision but // every other section now mounts in parallel under its own @@ -342,7 +342,7 @@ export default function InsightsPageClient() { // beyond the React-state subscription. const advisor = useInsightsAdvisorQuery(isAuthenticated); - // v1.4.33 IW2 — the mother page reads `healthScore` (a thick-only + // v1.4.33 — the mother page reads `healthScore` (a thick-only // field) for the hero score card, so it stays on the default thick // slice. The shared hook still centralises the cache settings so the // consumer dedups with the sub-page mounts that ride the slim slice @@ -351,7 +351,7 @@ export default function InsightsPageClient() { const analyticsQuery = useAnalyticsQuery(); const analytics = analyticsQuery.data as AnalyticsData | undefined; - // v1.21.2 (A5 / A6) — the dashboard snapshot is the single server source + // v1.21.2 — the dashboard snapshot is the single server source // for the ambient narrative the hero surfaces: the Tension Verdict and // return-to-baseline ride its `healthScore`. The hero score number itself // stays on the analytics payload (unchanged). Reads the same shared @@ -369,7 +369,7 @@ export default function InsightsPageClient() { // single query instance, so the wellness lift adds no second request. const dashboardDerived = useDashboardDerived(isAuthenticated); - // v1.15.11 W2 — the resolved overview layout (sections + tiles). Drives + // v1.15.11 — the resolved overview layout (sections + tiles). Drives // both the section render order/visibility below and the Vitals grid's // per-tile order/visibility (passed into ). Defaults to // the canonical layout while in-flight so the first paint matches the @@ -438,7 +438,7 @@ export default function InsightsPageClient() { const modules = user?.modules; const heroStripUpdatedAt = advisor.payload?.cachedAt ?? null; - // v1.4.36 QA C2 — no `` wrappers below. The mother page is + // v1.4.36 — no `` wrappers below. The mother page is // `"use client"`, the below-the-fold blocks load via `next/dynamic` // with `{ ssr: false }` (no Promise-throw on hydrate), and the // TanStack Query hooks each return their own loading state without @@ -471,7 +471,7 @@ export default function InsightsPageClient() { // removed in v1.12.4 — the tab-strip regenerate button is the single // affordance. The generic disclaimer lives once in the layout-shell footer. - // v1.15.11 W2 — decouple the section render order from the JSX. Each section + // v1.15.11 — decouple the section render order from the JSX. Each section // id maps to its existing node here; the customizable region below renders // `orderedVisibleSectionIds(layout)` against this registry. Every existing // feature/data gate is preserved INSIDE each entry: a section the layout @@ -570,7 +570,7 @@ export default function InsightsPageClient() { ); return ( - // v1.12.7 (L3) — one consistent vertical rhythm down the overview. The + // v1.12.7 — one consistent vertical rhythm down the overview. The // page used `space-y-8` (32 px) between top-level blocks while the vitals // wrap used `space-y-6` (24 px), so the overview read as two tiers. Unify // to `space-y-6` — it matches the vitals wrap and tightens the overview in @@ -599,7 +599,7 @@ export default function InsightsPageClient() { // `isPending` settles false on success AND error, so a no-score // account drops the reserve exactly once. scorePending={analyticsQuery.isPending && !analytics} - // v1.21.2 (A5 / A6) — server-resolved Tension Verdict + return-to- + // v1.21.2 — server-resolved Tension Verdict + return-to- // baseline off the dashboard snapshot. The panel localises the keys; // null leaves it quiet. tension={heroTension} @@ -639,7 +639,7 @@ export default function InsightsPageClient() { } /> ) : ( - /* v1.15.11 W2 — the customizable region: sections render in the + /* v1.15.11 — the customizable region: sections render in the resolved layout order, skipping any the user has hidden. A layout-visible-but-gate-off section resolves to `null` from the registry, so the `space-y-6` rhythm closes the gap with no hole. diff --git a/src/app/insights/pulse/page.tsx b/src/app/insights/pulse/page.tsx index b07b680f2..81c888314 100644 --- a/src/app/insights/pulse/page.tsx +++ b/src/app/insights/pulse/page.tsx @@ -37,7 +37,7 @@ const IntradayPulseChart = dynamic( ); /** - * v1.4.25 W4 — `/insights/pulse`. + * v1.4.25 — `/insights/pulse`. * * Routed Pulse sub-page. Renders the raw/workout-inclusive pulse chart * plus the per-section AI assessment. Note: `chartKey="pulse"` so the @@ -90,7 +90,7 @@ export default function InsightsPulsPage() { // strip (the VO2 chart-row below keeps its own full-range read). const { statsByType, statsSettled, onVisibleStats } = useChartDomainStats(); - // v1.4.27 F17 — gate the sub-page on at least one pulse observation. + // v1.4.27 — gate the sub-page on at least one pulse observation. // Brand-new accounts (no manual logs, no Apple-Health upload yet) // see a one-line empty-state with a CTA into // `/measurements?add=PULSE` rather than a chart skeleton over a @@ -168,7 +168,7 @@ export default function InsightsPulsPage() { dayLinks /> - {/* S11 — the intraday "shape of the day" layer: 10-minute mean heart + {/* The intraday "shape of the day" layer: 10-minute mean heart rate with the resting baseline and any cautious elevated-at-rest window. Computed on demand from raw for today; non-diagnostic. */} @@ -187,7 +187,7 @@ export default function InsightsPulsPage() { icon={} /> - {/* S10 — device-attributed pointer into the ECG viewer, in the + {/* Device-attributed pointer into the ECG viewer, in the resting-HR / pulse context. Un-mounts when the user has no ECG recordings; surfaces only that recordings exist + the device's own latest result, never a HealthLog interpretation. */} diff --git a/src/app/insights/recovery/page.tsx b/src/app/insights/recovery/page.tsx index 898dc5a78..9112f2545 100644 --- a/src/app/insights/recovery/page.tsx +++ b/src/app/insights/recovery/page.tsx @@ -36,7 +36,7 @@ export default function InsightsRecoveryPage() { explainerMetric="recoveryPage" coachLaunch > - {/* 2026-07-17 UX/IA audit M4 — reverse direction of the cross-link added + {/* Reverse direction of the cross-link added to the score-anatomy page: this composite wearable page shares the "Recovery" nav label with the RECOVERY_SCORE anatomy view at `/insights/scores/recovery`, so a user who arrived here looking for diff --git a/src/app/insights/scores/[metric]/page.tsx b/src/app/insights/scores/[metric]/page.tsx index 4e7fe4355..8bba13854 100644 --- a/src/app/insights/scores/[metric]/page.tsx +++ b/src/app/insights/scores/[metric]/page.tsx @@ -34,7 +34,7 @@ const SLUG_TO_METRIC: Record = { }; /** - * v1.15.12 B1 — the explainer-body key suffix per score, feeding + * v1.15.12 — the explainer-body key suffix per score, feeding * `insights.subPage.explainer.Body` through `SubPageShell`'s * `explainerMetric` prop (mirrors the metric sub-pages). `SLEEP_SCORE` * uses a distinct `sleepScore` suffix so it never collides with the @@ -96,7 +96,7 @@ export default function CompositeScorePage() { } coachLaunch > - {/* 2026-07-17 UX/IA audit M4 — this anatomy view and the `/insights/recovery` + {/* This anatomy view and the `/insights/recovery` composite-wearable page both used to render as plain "Recovery", a same-name/different-content trap: the score ring here promised one thing, the nav pill led somewhere else with no pointer back. The diff --git a/src/app/insights/sleep/page.tsx b/src/app/insights/sleep/page.tsx index 3fc4782ad..d7a1d99b2 100644 --- a/src/app/insights/sleep/page.tsx +++ b/src/app/insights/sleep/page.tsx @@ -21,19 +21,19 @@ import { QueryErrorCard } from "@/components/ui/query-error-card"; import { useSleepRhythm } from "@/components/insights/sleep/use-sleep-rhythm"; /** - * v1.4.25 W4c — `/insights/sleep`. + * v1.4.25 — `/insights/sleep`. * * The Sleep sub-page surfaces the per-stage breakdown + duration trend * the v1.4.23 schema gained but never rendered. All charts live inside * `` so the page-level scaffold stays trivial; data * fetches and empty-state handling are encapsulated in the component. * - * v1.4.27 F17 — when `summaries.SLEEP_DURATION.count === 0` (no + * v1.4.27 — when `summaries.SLEEP_DURATION.count === 0` (no * Apple-Health / Withings sleep rows yet), the page short-circuits * to an empty-state CTA pointing at `/settings/integrations` so the * user can connect a sleep source. * - * v1.4.28 R3d (BK-F-H1 + BK-F-M1) — analytics fetch + empty-state + * v1.4.28 — analytics fetch + empty-state * render consume the shared hook + primitive. */ export default function InsightsSchlafPage() { diff --git a/src/app/insights/weight/page.tsx b/src/app/insights/weight/page.tsx index 8866094ff..c91f9b1b9 100644 --- a/src/app/insights/weight/page.tsx +++ b/src/app/insights/weight/page.tsx @@ -29,7 +29,7 @@ import { } from "@/lib/analytics/value-bands"; /** - * v1.4.25 W4 — `/insights/weight`. + * v1.4.25 — `/insights/weight`. * * Routed Weight sub-page. Renders the weight chart with green/orange/red * bands plus the per-section AI assessment. The chart-cog @@ -39,7 +39,7 @@ import { * v1.34 — the bands follow the user's own weight target when one is set, * and fall back to the height-derived WHO band only when it is not. * - * v1.4.28 R3d (BK-F-H1 + BK-F-M1) — analytics fetch + empty-state + * v1.4.28 — analytics fetch + empty-state * branch now consume `useInsightsAnalytics()` + ``. */ export default function InsightsGewichtPage() { diff --git a/src/app/labs/page.tsx b/src/app/labs/page.tsx index 303e952c1..2fde494b0 100644 --- a/src/app/labs/page.tsx +++ b/src/app/labs/page.tsx @@ -141,7 +141,7 @@ export default function LabsPage() { description={t("labs.subtitle")} actions={ <> - {/* v1.18.6 (MOD-01) — the wrench is the module's customize entry + {/* v1.18.6 — the wrench is the module's customize entry point, left of the primary Add and linking to the Labs settings page (view, sort order, biomarker CRUD + reorder). Mirrors the medication page header's wrench glyph + slot + 44px tap floor. */} @@ -235,7 +235,7 @@ export default function LabsPage() { queryKey: queryKeys.labResults(), }); }} - // v1.30.1 H3 — "Save & add another" keeps the sheet open; the + // v1.30.1 — "Save & add another" keeps the sheet open; the // form clears the reading itself, this callback just refreshes // the list underneath so the just-saved row is there once the // user is done and closes the sheet. diff --git a/src/app/mcp/__tests__/route.test.ts b/src/app/mcp/__tests__/route.test.ts index d5342866c..9a5589b20 100644 --- a/src/app/mcp/__tests__/route.test.ts +++ b/src/app/mcp/__tests__/route.test.ts @@ -91,7 +91,7 @@ const READ_TOOLS = [ "list_metrics", "search", "fetch", - // Phase 4 — deep-value reads. + // Deep-value reads. "get_correlation", "compare_metric", "get_metric_baseline", diff --git a/src/app/measurements/page.tsx b/src/app/measurements/page.tsx index 13a017f4c..ce8a70cd8 100644 --- a/src/app/measurements/page.tsx +++ b/src/app/measurements/page.tsx @@ -45,7 +45,7 @@ export default function MeasurementsPage() { const searchParams = useSearchParams(); const { t } = useTranslations(); - // v1.4.27 MB6 — read the `?add=` query param during render so + // v1.4.27 — read the `?add=` query param during render so // the dialog opens on the first paint (no flash of the page behind // it). The first-load param is consumed in the `useState` lazy // initializers: they run identically on the server and the hydration @@ -79,7 +79,7 @@ export default function MeasurementsPage() { const [consumedAddRequest, setConsumedAddRequest] = useState( () => addRequestParam, ); - // v1.4.27 R4 RC2 — DOM handle the form portals its action row into so + // v1.4.27 — DOM handle the form portals its action row into so // the Sheet branch can sticky-pin Save / Cancel. const [footerEl, setFooterEl] = useState(null); diff --git a/src/app/medications/page-client.tsx b/src/app/medications/page-client.tsx index 2cb33b7bd..5de387002 100644 --- a/src/app/medications/page-client.tsx +++ b/src/app/medications/page-client.tsx @@ -87,9 +87,9 @@ interface Medication { category: string; /** v1.40 — the label of a custom category; null for a built-in one. */ categoryLabel?: string | null; - /** v1.4.25 W4d — Prisma treatment class (GENERIC | GLP1). */ + /** v1.4.25 — Prisma treatment class (GENERIC | GLP1). */ treatmentClass?: string; - /** v1.4.25 W4d — doses per pen/vial for inventory math. */ + /** v1.4.25 — doses per pen/vial for inventory math. */ dosesPerUnit?: number | null; /** v1.6.0 — route of administration (ORAL | INJECTION | OTHER). */ deliveryForm?: string; @@ -152,7 +152,7 @@ interface Medication { } /** - * v1.11.3 C4 — loading placeholder for one medication card. Renders the + * v1.11.3 — loading placeholder for one medication card. Renders the * real `Card`/`CardContent` shell (so padding, radius and gap match the * loaded card exactly) with the shared `Skeleton` primitive standing in * for each populated slot: header title + dose, status pill, next / last @@ -259,7 +259,7 @@ export default function MedicationsPageClient() { // withheld; sharing does not cover it at all. const { canWriteDomain, inSharedRecord } = useRecordCapabilities(); const canAddMedication = canWriteDomain("medications"); - // v1.18.1 (D3) — medications is an opt-out module. When the account has it + // v1.18.1 — medications is an opt-out module. When the account has it // turned off the whole page disappears (the nav entry is hidden by the same // gate) and the list query never fires (the API would 403 anyway). // Default-on: an absent key reads as enabled, so the page only hides on an @@ -354,7 +354,7 @@ export default function MedicationsPageClient() { refetchOnMount: refetchMedicationsOnMount("list"), }); - // v1.30.1 M12 — pull-to-refresh parity with labs/checkups/mood/measurements. + // v1.30.1 — pull-to-refresh parity with labs/checkups/mood/measurements. // Medications is one of the sharpest PWA-resume-staleness surfaces (a // background-refreshed take/skip from a notification, or a cross-device // write, wouldn't otherwise show up until the 5-min staleTime lapses or a @@ -638,7 +638,7 @@ export default function MedicationsPageClient() { )} {isLoading || isLayoutLoading ? ( - // v1.11.3 C4 — skeletons instead of a bare centred spinner so the + // v1.11.3 — skeletons instead of a bare centred spinner so the // page reserves the loaded layout and does not jump when the // medications resolve. v1.16.10 — the skeleton follows the // persisted view (table rows vs card grid) so resolving the data @@ -663,7 +663,7 @@ export default function MedicationsPageClient() { onRetry={() => void refetchMedications()} /> ) : !medications?.length ? ( - // v1.4.15 phase-C5: refactor the inline icon+text+button block + // v1.4.15: refactor the inline icon+text+button block // to the shared EmptyState primitive so the empty path matches // every other list page in the app (role=status, dashed // border, consistent icon-bubble + spacing). diff --git a/src/app/mood/page-client.tsx b/src/app/mood/page-client.tsx index 5891e6a15..c2a24f008 100644 --- a/src/app/mood/page-client.tsx +++ b/src/app/mood/page-client.tsx @@ -70,7 +70,7 @@ export default function MoodPageClient() { // The list's empty state carries the add action while there is nothing // to list, so the header drops its own then. const [listEmpty, setListEmpty] = useState(false); - // v1.4.27 R4 RC2 — DOM-ref handle the form portals its action row + // v1.4.27 — DOM-ref handle the form portals its action row // into. The ref lives on the `` footer slot so the // Sheet branch can sticky-pin Save / Cancel above the keyboard. const [footerEl, setFooterEl] = useState(null); @@ -191,7 +191,7 @@ export default function MoodPageClient() { onEmptyChange={setListEmpty} /> - {/* 2026-07-17 UX/IA audit M9 — mood tracking, the mental-wellbeing + {/* Mood tracking, the mental-wellbeing screeners, and the mood insights page form one mental-health domain but used to be three unconnected islands. A quiet pointer here (module-gated — no nav change) rather than a merge: capture diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx index 0413a3f69..aa184de24 100644 --- a/src/app/not-found.tsx +++ b/src/app/not-found.tsx @@ -5,7 +5,7 @@ import { resolveServerLocale } from "@/lib/i18n/server-locale"; import { getServerTranslator } from "@/lib/i18n/server-translator"; /** - * v1.4.27 MB6 — branded 404 page. Next.js mounts this file for any + * v1.4.27 — branded 404 page. Next.js mounts this file for any * route that doesn't resolve to a server component, so the previous * implicit fallback was the framework's bare "404" text. The shape * here intentionally stays lighter than `` — Decision L @@ -18,7 +18,7 @@ import { getServerTranslator } from "@/lib/i18n/server-translator"; * `safe-area-inset-top` keeps the headline clear of the notch or * Dynamic Island. * - * v1.4.43 QoL — copy lifted into i18n keys (`notFound.title` / + * v1.4.43 — copy lifted into i18n keys (`notFound.title` / * `notFound.backToDashboard`) and tightened. The previous * marketing-flavoured paragraph ("The page you were looking for… * Head back to the dashboard to pick up where you left off.") read diff --git a/src/app/notifications/page.tsx b/src/app/notifications/page.tsx index 2fc4c7585..7ce11570b 100644 --- a/src/app/notifications/page.tsx +++ b/src/app/notifications/page.tsx @@ -109,7 +109,7 @@ export default function NotificationsPage() { (p) => p.channelId === channelId && p.eventType === eventType, ); if (pref) return pref.enabled; - // v1.4.25 W16c — per-event default. Most events stay opt-out + // v1.4.25 — per-event default. Most events stay opt-out // (no row = ON); PERSONAL_RECORD flips to opt-in (no row = OFF) // so a backfill doesn't fire hundreds of pushes on first sync. return EVENT_DEFAULT_ENABLED[eventType as EventType] ?? true; diff --git a/src/app/onboarding/[step]/page.tsx b/src/app/onboarding/[step]/page.tsx index 9a2002b98..8d7cfb782 100644 --- a/src/app/onboarding/[step]/page.tsx +++ b/src/app/onboarding/[step]/page.tsx @@ -23,7 +23,7 @@ import { } from "@/lib/onboarding/wizard-steps"; /** - * v1.39 (C2) — one setup screen per step id. + * v1.39 — one setup screen per step id. * * /onboarding/who … /onboarding/visit the five questions * /onboarding/units Q6, when it applies diff --git a/src/app/onboarding/__tests__/page.test.tsx b/src/app/onboarding/__tests__/page.test.tsx index c9c8606fe..3b9ffb57e 100644 --- a/src/app/onboarding/__tests__/page.test.tsx +++ b/src/app/onboarding/__tests__/page.test.tsx @@ -10,7 +10,7 @@ import { } from "@/lib/onboarding/needs"; /** - * v1.39 (C2) — the setup flow's front door. + * v1.39 — the setup flow's front door. * * Three ways in, decided by the step machine: a record that never entered * the flow sees the welcome screen, a record mid-way is sent to the step it diff --git a/src/app/onboarding/page.tsx b/src/app/onboarding/page.tsx index 821a2029d..c1737b3c2 100644 --- a/src/app/onboarding/page.tsx +++ b/src/app/onboarding/page.tsx @@ -8,7 +8,7 @@ import { isOnboardingSettled } from "@/lib/onboarding/needs"; import { resumeScreen } from "@/lib/onboarding/wizard-steps"; /** - * v1.39 (C2) — the setup flow's front door. + * v1.39 — the setup flow's front door. * * The proxy lands every account still pending its first run here, and * Settings → "Set up again" does too. What is shown depends on where the diff --git a/src/app/page-client.tsx b/src/app/page-client.tsx index b3fa06daf..cb3aab93e 100644 --- a/src/app/page-client.tsx +++ b/src/app/page-client.tsx @@ -78,7 +78,7 @@ import { EnvironmentChip } from "@/components/dashboard/environment-chip"; import { SleepSourceDiscrepancyMarker } from "@/components/insights/sleep-source-discrepancy-marker"; import { VorsorgeDashboardCard } from "@/components/measurement-reminders/vorsorge-dashboard-card"; -// v1.4.40 W-RSC — module-scope so the option object is stable across +// v1.4.40 — module-scope so the option object is stable across // renders (audit-M2). Pre-fix the same literal was declared inside the // component body, so every render created a fresh `{}` reference; // TanStack does a shallow compare and shrugs at the equivalent values, @@ -237,7 +237,7 @@ export default function DashboardPageClient({ const [quickEntryDialog, setQuickEntryDialog] = useState(null); - // v1.30.1 M12 — pull-to-refresh parity for the dashboard, named as one + // v1.30.1 — pull-to-refresh parity for the dashboard, named as one // of the sharpest PWA-resume-staleness surfaces in the audit (it // already opts back into `refetchOnWindowFocus`, but a PWA resumed // from the home screen/app-switcher, not a browser tab focus event, @@ -250,13 +250,13 @@ export default function DashboardPageClient({ [queryClient], ); const pull = usePullToRefresh({ onRefresh: refreshDashboard }); - // v1.4.29 M5 — the three inline dashboard queries default to a + // v1.4.29 — the three inline dashboard queries default to a // 0-ms `staleTime`, so a tab-focus-and-return triggered a refetch // storm. None of these are real-time data; minute-scale staleness // is fine and the dashboard's chart queries already match this // cadence. // - // v1.4.40 W-RSC — `DASHBOARD_QUERY_OPTS` hoisted to module scope + // v1.4.40 — `DASHBOARD_QUERY_OPTS` hoisted to module scope // (audit-M2 — stable reference across renders). // v1.4.39.2 — split the dashboard's analytics consumption so the @@ -288,7 +288,7 @@ export default function DashboardPageClient({ const snapshotEnabled = isDashboardSnapshotEnabled(); const snapshotQuery = useDashboardSnapshot(snapshotEnabled); - // S2 — the Today hero reads the unified daily digest (the S1 DTO) from + // The Today hero reads the unified daily digest (the S1 DTO) from // its own cached route. It is the day's data, so no module owns it: the // `insights` key means AI analysis, and the digest leaves its model-written // lead out on its own when that is off. Gated on auth only. @@ -307,7 +307,7 @@ export default function DashboardPageClient({ enabled: !snapshotEnabled && isAuthenticated, }); const data = useMemo(() => { - // v1.7.0 W6 — snapshot path: assemble the same `AnalyticsData` + // v1.7.0 — snapshot path: assemble the same `AnalyticsData` // shape from the single snapshot cell so every downstream tile // reads unchanged. `extras` is null on a rollup-coverage miss // (two-phase contract) → the BD-Zielbereich + glucose fields stay @@ -425,7 +425,7 @@ export default function DashboardPageClient({ : undefined : moodDataLegacy; - // v1.4.27 B1 — the dashboard's `` retired (it + // v1.4.27 — the dashboard's `` retired (it // duplicated the much-richer `/insights` advisor surface). The advisor // query lives on `/insights` directly; the dashboard no longer needs // its own hook subscription, so the local `useInsightsAdvisorQuery` @@ -441,7 +441,7 @@ export default function DashboardPageClient({ const sys = data?.summaries?.BLOOD_PRESSURE_SYS; const dia = data?.summaries?.BLOOD_PRESSURE_DIA; const p = data?.summaries?.PULSE; - // v1.15.12 A2 — the resting-pulse target band is judged against the + // v1.15.12 — the resting-pulse target band is judged against the // RESTING_HEART_RATE series (Apple's clean daily resting figure), not // raw PULSE which mixes in workout HR. When the user has resting rows, // the pulse tile shows the resting figure + the resting-band colour; @@ -475,7 +475,7 @@ export default function DashboardPageClient({ ? toHoursSummary(sleepSummary) : undefined; const stepsSummary = data?.summaries?.ACTIVITY_STEPS; - // v1.4.25 W8d — VO2 max secondary-metric tile. /api/analytics + // v1.4.25 — VO2 max secondary-metric tile. /api/analytics // auto-populates this summary because the route iterates over the // full measurementTypeEnum.options list; no backend change needed. const vo2Summary = data?.summaries?.VO2_MAX; @@ -513,14 +513,14 @@ export default function DashboardPageClient({ (layout.enabledHeroItemKinds ?? PRIORITY_ITEM_KINDS).length < PRIORITY_ITEM_KINDS.length; /** - * v1.4.16 phase B8 — comparison baseline (Vormonat / Vorjahr) read + * v1.4.16 — comparison baseline (Vormonat / Vorjahr) read * from the resolved layout so every chart + tile on the dashboard * receives the same value. "none" = comparison off (pre-B8 default). */ const compareBaseline = layout.comparisonBaseline ?? "none"; /** - * v1.4.16 phase B8 — derive the tile-delta value (current 30d avg + * v1.4.16 — derive the tile-delta value (current 30d avg * minus prior-period 30d avg) for any DataSummary. Returns null * when comparison is off OR either side is missing data so the * tile can suppress the callout cleanly. @@ -539,7 +539,7 @@ export default function DashboardPageClient({ }; /** - * v1.4.34 IW-B — read the per-type freshness map and surface the + * v1.4.34 — read the per-type freshness map and surface the * `daysAgo` value when the metric is older than a week. The * tile-strip below forwards the result to `` * which picks the bucket-aware copy (Xd / X weeks / X months) and @@ -558,7 +558,7 @@ export default function DashboardPageClient({ const isChartVisible = (id: string) => layout.widgets.find((widget) => widget.id === id)?.visible ?? false; /** - * v1.4.15 Fix 5 — whether the widget's *tile* in the strip shows. + * v1.4.15 — whether the widget's *tile* in the strip shows. * Independent of the chart visibility so the user can hide chart but * keep the tile (or vice versa) from Settings → Dashboard. Falls back * to chart visibility for layouts saved before v1.4.15. @@ -576,7 +576,7 @@ export default function DashboardPageClient({ // Data-floor gates (widget shows iff visible AND has data) const hasWeight = (w?.count ?? 0) > 0; const hasBp = (sys?.count ?? 0) > 0 || (dia?.count ?? 0) > 0; - // v1.15.12 A2 — the pulse tile shows resting HR when available, else + // v1.15.12 — the pulse tile shows resting HR when available, else // raw PULSE; either signal having data shows the tile. const hasPulse = (p?.count ?? 0) > 0 || (rhr?.count ?? 0) > 0; const hasBodyFat = (bf?.count ?? 0) > 0; @@ -595,7 +595,7 @@ export default function DashboardPageClient({ // v1.29 — data floor for the fluid-intake strip tile. const hasWaterIntake = (waterIntakeSummary?.count ?? 0) > 0; /** - * v1.4.33 F4 — gate the BD-Zielbereich tile so a literal "0,0 %" + * v1.4.33 — gate the BD-Zielbereich tile so a literal "0,0 %" * placeholder doesn't ride on the dashboard when none of the user's * paired readings sit inside the target band. The historical * behaviour (`!= null`) painted the tile whenever the analytics @@ -626,7 +626,7 @@ export default function DashboardPageClient({ const showBodyFatTile = isTileVisible("bodyFat") && hasBodyFat; const showMoodTile = isTileVisible("mood") && hasMood; const showSleepTile = isTileVisible("sleep") && hasSleep; - // v1.18.0 B1 — steps are activity data tied to the workouts module + // v1.18.0 — steps are activity data tied to the workouts module // surface; AND the resolved module flag (fail-open: only an explicit // `false` hides it) so a disabled-workouts account drops the tile. const showStepsTile = @@ -669,7 +669,7 @@ export default function DashboardPageClient({ // data floor; the achievements + recent-workouts cards have no floor, // so wait for the real layout before committing them. const layoutResolved = layoutData != null; - // v1.4.15 phase-B4 — recent unlocks dashboard surface. The card itself + // v1.4.15 — recent unlocks dashboard surface. The card itself // self-handles the loading skeleton + empty state (CTA → /achievements), // so we only need the layout-toggle gate here. No data-floor check (the // empty card is intentional — the maintainer wants the user to discover @@ -955,7 +955,7 @@ export default function DashboardPageClient({ dashboard is this account's own day; a permanent block about other people's records sat above it and pushed it down. */} - {/* S2 — the Today hero, promoted above the tile strip (MARC sign-off + {/* The Today hero, promoted above the tile strip (MARC sign-off decision 1). Renders the S1 daily digest: the day's read, the honest score/freshness face, and the worth-a-look rail. Shimmer skeleton while the cached digest loads; a genuinely empty account @@ -1306,7 +1306,7 @@ export default function DashboardPageClient({ ), }); } - // v1.4.25 W8d — VO2 max trend tile. Self-gates on the + // v1.4.25 — VO2 max trend tile. Self-gates on the // `vo2Max` widget being enabled (Settings → Dashboard) AND // the analytics summary carrying at least one sample. Higher // VO2 max is better, so the directionSentiment is up-good and @@ -1573,7 +1573,7 @@ export default function DashboardPageClient({ }); } if (showBpInTargetTile) { - /* v1.4.22 A2 — feature parity with every other tile. + /* v1.4.22 — feature parity with every other tile. Synthesise a slope from the difference between the 7-day and 30-day in-target shares: when the recent week is above the recent month, the metric is improving (up-good @@ -1587,7 +1587,7 @@ export default function DashboardPageClient({ tile (no DataSummary) so the prior-period delta uses `bpInTargetPctAllTime` as the long-arc baseline — when comparison is off the field stays null. */ - // v1.4.28 FB-C1 + FB-C2 — rewrite the BD-Zielbereich tile + // v1.4.28 — rewrite the BD-Zielbereich tile // against the shared `` primitive so it matches // the Weight / BP / Pulse sibling tiles exactly. The // synthetic `bpSlope30 = bpTrendDelta / 30` block produced a @@ -1618,7 +1618,7 @@ export default function DashboardPageClient({ bpComparePrior === null ? null : Math.round((bp30 - bpComparePrior) * 10) / 10; - // v1.17 W1b — thin-data gate + dynamic span label. Below the + // v1.17 — thin-data gate + dynamic span label. Below the // confidence floor the tile narrates "collecting data" instead of // a hard percentage, and the label names the EFFECTIVE span // ("· 23 T" until ~90 days of history exist) rather than a @@ -1639,11 +1639,11 @@ export default function DashboardPageClient({ node: ( a.order - b.order); - // v1.4.43 W11-M5 — tile-strip skeleton during slow slim-analytics + // v1.4.43 — tile-strip skeleton during slow slim-analytics // fetches. // // `trendCards` only fills once the slim `/api/analytics` slice @@ -2056,7 +2056,7 @@ export default function DashboardPageClient({ primaryLoading, }); - // v1.4.15 phase-C5: dashboard fully-empty state. When no tile + // v1.4.15: dashboard fully-empty state. When no tile // and no chart has data the dashboard would otherwise paint a // 0-px tile strip with the welcome banner above it — visually // looked like a half-broken page. Render an EmptyState that @@ -2067,7 +2067,7 @@ export default function DashboardPageClient({ // case where the checklist has been dismissed but no data was // logged afterwards. // - // v1.4.43 W11 — the empty-state only fires once the slim slice + // v1.4.43 — the empty-state only fires once the slim slice // resolves with no tiles to show. While slim is in flight the // skeleton strip below carries the layout footprint. if (showEmptyState) { @@ -2106,7 +2106,7 @@ export default function DashboardPageClient({ * makes the scroll feel deliberate rather than arbitrary on * touch. */} - {/* v1.4.16 Fix A5: hide the strip entirely when the user + {/* v1.4.16: hide the strip entirely when the user turned off every tile. Until v1.4.15 the wrapper rendered an empty grid even with zero tiles — visually a thin gap the maintainer described as "awkward". Charts below still render so @@ -2118,7 +2118,7 @@ export default function DashboardPageClient({ height for any non-zero count. */} {showTileStripSkeleton && (
(
{/* - * v1.4.40 W-RSC — per-tile `` boundary so each + * v1.4.40 — per-tile `` boundary so each * chart paints independently rather than the row blocking * on the slowest fetch (audit-H2 + brief C1). The * `next/dynamic({ ssr: false })` lazy-load contract on diff --git a/src/app/privacy/__tests__/page.test.tsx b/src/app/privacy/__tests__/page.test.tsx index 18805bd10..31c149a67 100644 --- a/src/app/privacy/__tests__/page.test.tsx +++ b/src/app/privacy/__tests__/page.test.tsx @@ -4,7 +4,7 @@ import { renderToStaticMarkup } from "react-dom/server"; import PrivacyPage from "../page"; /** - * v1.4.40 W-PRIVACY (SB-3) — Bilingual public privacy-policy contract. + * v1.4.40 — Bilingual public privacy-policy contract. * * The page is the App-Store-submission blocker (Apple BLOCKER 2 of 5). * These tests pin the structural commitments that any future redesign diff --git a/src/app/privacy/page.tsx b/src/app/privacy/page.tsx index ebd067b4c..e900b7376 100644 --- a/src/app/privacy/page.tsx +++ b/src/app/privacy/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import Link from "next/link"; /** - * v1.4.40 W-PRIVACY (SB-3) — Bilingual public privacy policy. + * v1.4.40 — Bilingual public privacy policy. * * Requirements driving this page: * @@ -213,7 +213,7 @@ export default function PrivacyPage() { {/* - v1.4.33 IW9 — `max-w-3xl` (768 px) is intentional. The legal / + v1.4.33 — `max-w-3xl` (768 px) is intentional. The legal / long-form column reads better at 70-80 chars per line; widening to the app shell's `max-w-screen-xl` would push line lengths to ~120 chars on a 1440 px laptop and degrade legibility. The same diff --git a/src/components/__tests__/motion-reduce-spin-coverage.test.ts b/src/components/__tests__/motion-reduce-spin-coverage.test.ts index 03ffbf391..eeb395ac1 100644 --- a/src/components/__tests__/motion-reduce-spin-coverage.test.ts +++ b/src/components/__tests__/motion-reduce-spin-coverage.test.ts @@ -3,7 +3,7 @@ import { readdirSync, readFileSync, statSync } from "node:fs"; import { join } from "node:path"; /** - * v1.4.43 W11 — motion-reduce sweep guard. + * v1.4.43 — motion-reduce sweep guard. * * Every `animate-spin` site in component / app code must also carry * `motion-reduce:animate-none` so that motion-sensitive users see a diff --git a/src/components/admin/__tests__/ai-quality-section.test.tsx b/src/components/admin/__tests__/ai-quality-section.test.tsx index e8ce2fea5..f89150a1e 100644 --- a/src/components/admin/__tests__/ai-quality-section.test.tsx +++ b/src/components/admin/__tests__/ai-quality-section.test.tsx @@ -3,7 +3,7 @@ import { renderToStaticMarkup } from "react-dom/server"; import { I18nProvider } from "@/lib/i18n/context"; /** - * v1.4.16 phase B5e — coverage for the admin AI quality preview. + * v1.4.16 — coverage for the admin AI quality preview. * * Three branches: * 1. loading state (Loader2 spinner + label) @@ -57,7 +57,7 @@ describe("", () => { mockQueryState.isLoading = false; mockQueryState.isError = false; const html = render(); - // v1.4.33 IW7 — admin section renamed from "AI Quality" to + // v1.4.33 — admin section renamed from "AI Quality" to // "Insights Quality" per the project-voice rule. expect(html).toContain("Insights Quality"); expect(html).toContain("No feedback yet"); diff --git a/src/components/admin/__tests__/api-token-no-collapse.test.tsx b/src/components/admin/__tests__/api-token-no-collapse.test.tsx index 1b262e136..041e969bc 100644 --- a/src/components/admin/__tests__/api-token-no-collapse.test.tsx +++ b/src/components/admin/__tests__/api-token-no-collapse.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.19 phase A7 — `/admin/api-tokens` no longer renders a Collapse + * v1.4.19 — `/admin/api-tokens` no longer renders a Collapse * / Expand toggle. The toggle existed as an escape hatch from the * v1.4 shared-admin page where 13 sections lived on one route; in * v1.5 every section has its own dedicated route, so collapsing the diff --git a/src/components/admin/__tests__/api-token-overview-responsive.test.tsx b/src/components/admin/__tests__/api-token-overview-responsive.test.tsx index 1991a9957..293143ce1 100644 --- a/src/components/admin/__tests__/api-token-overview-responsive.test.tsx +++ b/src/components/admin/__tests__/api-token-overview-responsive.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.16 phase A3 — `/admin/api-tokens` no horizontal overflow on + * v1.4.16 — `/admin/api-tokens` no horizontal overflow on * Pixel-5-class viewports (393 CSS px). * * The v1.4.15 fix tried hiding columns + tightening padding, but a diff --git a/src/components/admin/__tests__/api-token-truncate.test.tsx b/src/components/admin/__tests__/api-token-truncate.test.tsx index 5ad83c3be..aab52dfa4 100644 --- a/src/components/admin/__tests__/api-token-truncate.test.tsx +++ b/src/components/admin/__tests__/api-token-truncate.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.19 A7 — `/admin/api-tokens` 4th-attempt scrollbar fix. + * v1.4.19 — `/admin/api-tokens` 4th-attempt scrollbar fix. * * The maintainer reported a "minimaler Scrollbar" still painting at the * bottom-right of the api-tokens page, even after the v1.4.18 phase diff --git a/src/components/admin/__tests__/app-log-preview-summary-position.test.tsx b/src/components/admin/__tests__/app-log-preview-summary-position.test.tsx index 356dc6068..9968dc244 100644 --- a/src/components/admin/__tests__/app-log-preview-summary-position.test.tsx +++ b/src/components/admin/__tests__/app-log-preview-summary-position.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.27 MB5 — `/admin/app-logs` summary line lifted out of the + * v1.4.27 — `/admin/app-logs` summary line lifted out of the * `overflow-x-auto` table wrapper so admins on narrow viewports can * still see "showing X of Y events" without first scrolling the table * back to its starting offset. The refresh control already lives in diff --git a/src/components/admin/__tests__/backups-section-empty.test.tsx b/src/components/admin/__tests__/backups-section-empty.test.tsx index 14211f4bc..9c57e7eb3 100644 --- a/src/components/admin/__tests__/backups-section-empty.test.tsx +++ b/src/components/admin/__tests__/backups-section-empty.test.tsx @@ -27,7 +27,7 @@ const mocks = vi.hoisted(() => ({ })); /** - * v1.4.15 phase-C5 — `/admin/backups` empty state. + * v1.4.15 — `/admin/backups` empty state. * * The previous build rendered a single-line `

No backups recorded * yet.

` inside the card. Brand-new admins didn't realise the "Run diff --git a/src/components/admin/__tests__/coach-feedback-section.test.tsx b/src/components/admin/__tests__/coach-feedback-section.test.tsx index 2dd8500d1..8886fb700 100644 --- a/src/components/admin/__tests__/coach-feedback-section.test.tsx +++ b/src/components/admin/__tests__/coach-feedback-section.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.23 H7 — admin Coach feedback section. + * v1.4.23 — admin Coach feedback section. * * Three render paths matter: * 1. Loading — spinner + "Loading Coach feedback summary..." diff --git a/src/components/admin/__tests__/login-overview-auth-filter.test.tsx b/src/components/admin/__tests__/login-overview-auth-filter.test.tsx index 115800721..36673a4bd 100644 --- a/src/components/admin/__tests__/login-overview-auth-filter.test.tsx +++ b/src/components/admin/__tests__/login-overview-auth-filter.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.19 A8 / F-02 — `/admin/login-overview` was rendering insights events + * v1.4.19 — `/admin/login-overview` was rendering insights events * (`insights.weight-status.en`, `insights.bmi-status.en`, …) because the * underlying fetch never restricted the `audit-log` API to authentication * actions. The page subtitle promises "authentication and admin events" so diff --git a/src/components/admin/__tests__/login-overview-csv.test.ts b/src/components/admin/__tests__/login-overview-csv.test.ts index d325e61bf..bec8cf53b 100644 --- a/src/components/admin/__tests__/login-overview-csv.test.ts +++ b/src/components/admin/__tests__/login-overview-csv.test.ts @@ -9,7 +9,7 @@ import { import { toCSV } from "@/lib/export"; /** - * v1.4.25 W8b — Login-Übersicht CSV export contract. + * v1.4.25 — Login-Übersicht CSV export contract. * * The previous CSV emitted snake_case English headers and the raw UTC * `createdAt` string, which Excel/LibreOffice misread once the `Z` @@ -31,7 +31,7 @@ const LABELS_EN: AuditCsvLabels = { user: "Users", ip: "IP", location: "Location", - // v1.4.27 B3 — `admin.carrier` is shipped by bucket B6; the test + // v1.4.27 — `admin.carrier` is shipped by bucket B6; the test // pins the English literal so the column order is locked. carrier: "Carrier", provider: "Provider", diff --git a/src/components/admin/__tests__/login-overview-no-collapse.test.tsx b/src/components/admin/__tests__/login-overview-no-collapse.test.tsx index 56311bdc1..5349116e1 100644 --- a/src/components/admin/__tests__/login-overview-no-collapse.test.tsx +++ b/src/components/admin/__tests__/login-overview-no-collapse.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.25 W8b — `/admin/login-overview` no longer renders a Collapse + * v1.4.25 — `/admin/login-overview` no longer renders a Collapse * / Expand toggle. The toggle was a leftover from the v1.4 shared * admin page where 13 sections lived on one route; collapsing the * only card on the dedicated login-overview route hid the entire diff --git a/src/components/admin/__tests__/login-overview-pagination-position.test.tsx b/src/components/admin/__tests__/login-overview-pagination-position.test.tsx index e5e7721d6..c37af0740 100644 --- a/src/components/admin/__tests__/login-overview-pagination-position.test.tsx +++ b/src/components/admin/__tests__/login-overview-pagination-position.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.27 MB5 — `/admin/login-overview` pagination + summary lifted + * v1.4.27 — `/admin/login-overview` pagination + summary lifted * out of the `overflow-x-auto` table wrapper. * * The audit table can be wider than the viewport on phones, so it diff --git a/src/components/admin/__tests__/login-overview-provider.test.tsx b/src/components/admin/__tests__/login-overview-provider.test.tsx index c24af0fbf..2c17891eb 100644 --- a/src/components/admin/__tests__/login-overview-provider.test.tsx +++ b/src/components/admin/__tests__/login-overview-provider.test.tsx @@ -3,7 +3,7 @@ import { Cpu, Fingerprint, Globe, KeyRound } from "lucide-react"; import { iconForAuthProvider, providerForAction } from "../_shared"; /** - * v1.4.25 W8b — Provider column for the admin Login-Übersicht. + * v1.4.25 — Provider column for the admin Login-Übersicht. * * The audit-log entries don't carry a dedicated "provider" field — the * action name itself is the source of truth. These tests pin the diff --git a/src/components/admin/__tests__/sections.test.tsx b/src/components/admin/__tests__/sections.test.tsx index 3d3449673..f78a28ce5 100644 --- a/src/components/admin/__tests__/sections.test.tsx +++ b/src/components/admin/__tests__/sections.test.tsx @@ -132,13 +132,13 @@ describe("admin sections — SSR smoke", () => { expect(html).toContain("Delete all data globally"); }); - // v1.4.16 phase B5e — admin AI quality preview. The mocked useQuery + // v1.4.16 — admin AI quality preview. The mocked useQuery // returns `data: null, isLoading: false` so the empty-state branch // paints; the table-render branch is exercised by the dedicated // ai-quality-section test. it(" renders the empty-state when no summary exists", () => { const html = render(); - // v1.4.33 IW7 — section renamed from "AI Quality" to + // v1.4.33 — section renamed from "AI Quality" to // "Insights Quality" per the project-voice rule (no "KI"/"AI" prefix // in user-facing copy). expect(html).toContain("Insights Quality"); diff --git a/src/components/admin/__tests__/status-card-grid.test.tsx b/src/components/admin/__tests__/status-card-grid.test.tsx index 1b7cc0ad3..9a5a66dbd 100644 --- a/src/components/admin/__tests__/status-card-grid.test.tsx +++ b/src/components/admin/__tests__/status-card-grid.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.15 phase A2: the `/admin` overview no longer renders the + * v1.4.15: the `/admin` overview no longer renders the * `StatusCardGrid` (the section-card grid the maintainer described as redundant * with the sidebar nav). The status-card component itself was removed * from the codebase. This test guards the *new* overview composition: diff --git a/src/components/admin/__tests__/user-management-empty.test.tsx b/src/components/admin/__tests__/user-management-empty.test.tsx index 9fc59977b..521048c63 100644 --- a/src/components/admin/__tests__/user-management-empty.test.tsx +++ b/src/components/admin/__tests__/user-management-empty.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.15 phase-C5 — `/admin/users` empty state. + * v1.4.15 — `/admin/users` empty state. * * Before this phase, when the admin's filter bucket returned zero * users (or the system had only one admin and the "Users" filter was diff --git a/src/components/admin/_shared.tsx b/src/components/admin/_shared.tsx index 7abbebe4b..69f00d639 100644 --- a/src/components/admin/_shared.tsx +++ b/src/components/admin/_shared.tsx @@ -119,7 +119,7 @@ export interface AdminSettings { glitchtipDeliveryWindowHours: number; reminderLateMinutes: number; reminderMissedMinutes: number; - // v1.4.25 W7 — null means "fall back to Europe/Berlin in the resolver". + // v1.4.25 — null means "fall back to Europe/Berlin in the resolver". defaultUserTimezone: string | null; // Document vault — the two admin-tunable limits (bytes on the wire; the // UI edits them as MB / GB). The per-file cap clamps to the hard 100 MiB @@ -133,7 +133,7 @@ export interface AdminAuditEntry { action: string; ipAddress: string | null; location: string | null; - // v1.4.27 B3 — populated from the bundled GeoLite2-ASN MMDB at + // v1.4.27 — populated from the bundled GeoLite2-ASN MMDB at // audit-creation time. Both nullable: older rows, private/loopback // IPs, and offline-miss rows stay valid. asn: number | null; @@ -246,7 +246,7 @@ export function SettingsToggle({ onCheckedChange: (checked: boolean) => void; disabled: boolean; }) { - // v1.4.27 MB7 / CF-56 — stack the toggle row vertically on ` { } /** - * v1.4.25 W8b — Provider column for the Login-Übersicht. + * v1.4.25 — Provider column for the Login-Übersicht. * * The audit log doesn't have a dedicated `provider` field; the action * name itself encodes how the login happened (`auth.login.passkey`, @@ -440,7 +440,7 @@ export function useAuthProviderLabels(): Record { } /** - * v1.4.27 B3 — Fold a GeoLite2 `autonomous_system_organization` + * v1.4.27 — Fold a GeoLite2 `autonomous_system_organization` * string down to a short DACH carrier label. The MMDB returns the * verbose legal-entity name ("Deutsche Telekom AG", "1&1 Telecom * GmbH", "Telefónica Germany GmbH & Co. OHG"); the admin overview @@ -464,7 +464,7 @@ export function carrierShortLabel(rawAsnOrg: string): string { } /** - * v1.4.25 W8b — Build the CSV record set for the audit-log export. + * v1.4.25 — Build the CSV record set for the audit-log export. * * Pulled out of the section component so the column order, header * mapping, and provider/outcome derivation are reachable from a @@ -474,7 +474,7 @@ export function carrierShortLabel(rawAsnOrg: string): string { * require schema/API changes that are out of scope). `action` and * `details` follow so the export keeps the full triage payload. * - * v1.4.27 B3: the `carrier` column lands between `location` and + * v1.4.27: the `carrier` column lands between `location` and * `provider` so the geo-derived fields stay grouped. * * `formatTimestamp` is injected (not imported) so the production @@ -486,7 +486,7 @@ export interface AuditCsvEntry { action: string; ipAddress: string | null; location: string | null; - // v1.4.27 B3 — both optional so legacy fixture data that predates + // v1.4.27 — both optional so legacy fixture data that predates // the column landing still compiles against this shape. carrier?: string | null; asn?: number | null; diff --git a/src/components/admin/admin-shell.tsx b/src/components/admin/admin-shell.tsx index 2961dbb7a..38a63b17f 100644 --- a/src/components/admin/admin-shell.tsx +++ b/src/components/admin/admin-shell.tsx @@ -86,7 +86,7 @@ export const ADMIN_SECTIONS: readonly AdminSection[] = [ icon: Radio, group: "system", }, - // v1.4.36 W4e — About / version / update check folded into Admin. + // v1.4.36 — About / version / update check folded into Admin. { slug: "about", titleKey: "admin.section.about.title", @@ -114,7 +114,7 @@ export const ADMIN_SECTIONS: readonly AdminSection[] = [ icon: Sparkles, group: "ai", }, - // v1.18.6 (W9) — server-wide module availability split out of Coach into + // v1.18.6 — server-wide module availability split out of Coach into // its own entry, sitting next to it. { slug: "module-availability", @@ -210,7 +210,7 @@ export function AdminShell({ active, children }: AdminShellProps) { // never leave a group heading without an allowed destination below it. const visibleSections = isConfirmedAdmin ? ADMIN_SECTIONS : []; - // v1.4.34 IW-G — mirror the settings-shell auto-scroll pattern so + // v1.4.34 — mirror the settings-shell auto-scroll pattern so // the active chip stays in view when the user lands on a deeper // admin route. Without it the 16-chip strip stays scrolled to the // left and the active chip lives off-screen, reading as a broken nav. @@ -265,7 +265,7 @@ export function AdminShell({ active, children }: AdminShellProps) { /> ); - // v1.4.25 W8 — AuthShell wraps the page in `px-4 py-6 md:px-6` + // v1.4.25 — AuthShell wraps the page in `px-4 py-6 md:px-6` // already, so this inner shell only carries the wider max-width. // Previously the duplicate `px-4 py-6 md:px-6 md:py-8` here was // producing visibly more top/bottom whitespace on Settings/Admin @@ -284,7 +284,7 @@ export function AdminShell({ active, children }: AdminShellProps) { would render an always-on scrollbar that the maintainer kept mis-attributing to the api-tokens table below it. - v1.4.34 IW-G — `snap-x snap-mandatory` plus per-chip + v1.4.34 — `snap-x snap-mandatory` plus per-chip `snap-start` lets a swipe-flick land on the next chip's leading edge instead of the in-between dead zone, matching the settings-shell polish. */} @@ -299,7 +299,7 @@ export function AdminShell({ active, children }: AdminShellProps) { href="/admin" aria-current={activeSlug === null ? "page" : undefined} className={cn( - // v1.4.25 W8 — chip strip is the primary mobile-admin + // v1.4.25 — chip strip is the primary mobile-admin // navigation surface. Pad to WCAG 2.5.5 44 px so the // chips can be tapped without zoom on a Pixel-5. "flex min-h-11 items-center gap-2 rounded-full border px-3 py-1.5 text-sm font-medium whitespace-nowrap transition-colors", @@ -333,7 +333,7 @@ export function AdminShell({ active, children }: AdminShellProps) { href={`/admin/${section.slug}`} aria-current={isActive ? "page" : undefined} className={cn( - // v1.4.25 W8 — chip strip is the primary mobile-admin + // v1.4.25 — chip strip is the primary mobile-admin // navigation surface. Pad to WCAG 2.5.5 44 px so the // chips can be tapped without zoom on a Pixel-5. "flex min-h-11 items-center gap-2 rounded-full border px-3 py-1.5 text-sm font-medium whitespace-nowrap transition-colors", diff --git a/src/components/admin/api-token-overview-section.tsx b/src/components/admin/api-token-overview-section.tsx index 892b6d250..4989fcbc4 100644 --- a/src/components/admin/api-token-overview-section.tsx +++ b/src/components/admin/api-token-overview-section.tsx @@ -24,7 +24,7 @@ import { apiGet } from "@/lib/api/api-fetch"; * Wraps a string in `truncate` + tooltip so a long token name / * username / permission cannot push the row past its bounds. * - * v1.4.19 phase A7 (4th attempt) — earlier fixes (column-hide, + * v1.4.19 (4th attempt) — earlier fixes (column-hide, * mobile card-list, mobile-strip `no-scrollbar`) cleared every * scrollbar the maintainer reported except a residual painted bar they kept * seeing at the bottom-right. Production probe confirmed no @@ -62,7 +62,7 @@ function TruncatedCell({ } /** - * v1.4.22 W5 reconcile (S-03) — the revoked / isExpired / active + * v1.4.22 — the revoked / isExpired / active * 3-way ternary is identical between the desktop table and the * mobile card list; the only difference is `text-xs` vs `text-2xs`. * Centralise so the two surfaces stay in lock-step. @@ -107,7 +107,7 @@ function TokenStatusBadge({ * "iOS auto-login · 05.05.2026 19:46" while leaving any non-ISO suffix * (manual names, device fingerprints, etc.) untouched. */ -// v1.4.22 D / D-CR-M-03 — broaden the ISO match to include non-Z +// v1.4.22 — broaden the ISO match to include non-Z // offsets (e.g. `+02:00`, `-05:30`). The previous regex only matched // UTC (`Z`), so a token name carrying a local-tz timestamp slipped // through unformatted and rendered the raw `2026-05-05T19:46:20.603+02:00` @@ -139,7 +139,7 @@ function formatTokenName(name: string, fmt: Formatters): string { export function ApiTokenOverviewSection() { const { t } = useTranslations(); const fmt = useFormatters(); - // v1.4.19 phase A7 — `/admin/api-tokens` is a dedicated single- + // v1.4.19 — `/admin/api-tokens` is a dedicated single- // section route. The previous pattern carried a "Collapse / Expand" // toggle as an escape hatch from the v1.4 shared admin page where // 13 sections lived together, but on a route that only renders this @@ -166,7 +166,7 @@ export function ApiTokenOverviewSection() {
) : !tokens?.length ? ( - // v1.4.15 phase-C5: empty state explains where tokens come + // v1.4.15: empty state explains where tokens come // from (native client sign-in / Settings → Account) so a // fresh admin doesn't think they need to create them here. - {/* v1.4.22 C2 (5th attempt) — drop + {/* v1.4.22 (5th attempt) — drop `whitespace-nowrap` on the date cells. The v1.4.19 A7 probe confirmed `formatDateTime` renders "05.05.2026, 21:46" (~110px) which diff --git a/src/components/admin/app-log-preview-section.tsx b/src/components/admin/app-log-preview-section.tsx index 92453a839..974d3dfbf 100644 --- a/src/components/admin/app-log-preview-section.tsx +++ b/src/components/admin/app-log-preview-section.tsx @@ -273,7 +273,7 @@ export function AppLogPreviewSection() { description={t("admin.section.app-logs.emptyDescription")} /> ) : ( - // v1.4.27 MB5 — keep the table scroll container, but lift the + // v1.4.27 — keep the table scroll container, but lift the // "showing X of Y" summary line out into a sibling `
` so // the meta info stays reachable on narrow viewports even when // the table is scrolled horizontally. The refresh control was diff --git a/src/components/admin/backups-section.tsx b/src/components/admin/backups-section.tsx index 2f097e29b..6adca7386 100644 --- a/src/components/admin/backups-section.tsx +++ b/src/components/admin/backups-section.tsx @@ -1132,7 +1132,7 @@ export function BackupsSection() { return ( {recentProof.dialog} - {/* v1.18.1 E3 — the snapshot count leads (numbers first) and the + {/* v1.18.1 — the snapshot count leads (numbers first) and the "Run now" button follows. */} void refetch()} /> ) : rows.length === 0 ? ( - // v1.4.15 phase-C5: replace bare text with the EmptyState + // v1.4.15: replace bare text with the EmptyState // primitive. The header already exposes "Backup now" but a // brand-new admin lands inside the card and benefits from a // duplicate CTA right next to the explanation. diff --git a/src/components/admin/coach-feedback-section.tsx b/src/components/admin/coach-feedback-section.tsx index dff4dafbe..6c07d5854 100644 --- a/src/components/admin/coach-feedback-section.tsx +++ b/src/components/admin/coach-feedback-section.tsx @@ -63,7 +63,7 @@ export function CoachFeedbackSection() { const buckets = summary?.coachBuckets ?? []; const hasData = !!summary && buckets.length > 0; - // v1.4.25 W8 — keep the section's outer card + header rendered across + // v1.4.25 — keep the section's outer card + header rendered across // every query state (loading / error / empty / data) so the heading // baseline stays at a constant Y-offset. Previously the loading branch // returned a thin flex-row stub and the error branch a p-4 alert with diff --git a/src/components/admin/danger-zone-section.tsx b/src/components/admin/danger-zone-section.tsx index e41f653e9..b14ca7c4b 100644 --- a/src/components/admin/danger-zone-section.tsx +++ b/src/components/admin/danger-zone-section.tsx @@ -87,7 +87,7 @@ export function DangerZoneSection() { }); return ( - // v1.18.10 (W7) — the card SURFACE is neutral like every other admin / + // v1.18.10 — the card SURFACE is neutral like every other admin / // settings section (`SettingsCard`: bg-card + border-border): a red card // edge reads as alarming, not premium. The danger signal lives ONLY in // the destructive delete button + its typed-confirmation dialog, which diff --git a/src/components/admin/general-settings-section.tsx b/src/components/admin/general-settings-section.tsx index e1b86c4bd..4182d6e3b 100644 --- a/src/components/admin/general-settings-section.tsx +++ b/src/components/admin/general-settings-section.tsx @@ -25,7 +25,7 @@ function clamp(value: number, min: number, max: number): number { return Math.min(max, Math.max(min, value)); } -// v1.4.27 MB7 / CF-52 — the in-file `NATIVE_SELECT_CLASS` constant +// v1.4.27 — the in-file `NATIVE_SELECT_CLASS` constant // retired; the shared `` primitive owns the visual // contract now. The admin copy previously diverged on the // `focus-visible:ring-[3px]` weight; the shared primitive pins @@ -125,7 +125,7 @@ export function GeneralSettingsSection() { disabled={updateSettings.isPending} /> - {/* v1.4.27 MB7 / CF-56 — match the SettingsToggle stacking + {/* v1.4.27 — match the SettingsToggle stacking contract: stack on `
@@ -153,7 +153,7 @@ export function GeneralSettingsSection() {
- {/* v1.4.25 W7 — server-wide default timezone for new + {/* v1.4.25 — server-wide default timezone for new signups. Existing accounts are NOT touched; this drives only the registration handler's fallback when the client doesn't send a browser-detected zone. */} diff --git a/src/components/admin/login-overview-section.tsx b/src/components/admin/login-overview-section.tsx index 15a32d1b9..a6893ce0a 100644 --- a/src/components/admin/login-overview-section.tsx +++ b/src/components/admin/login-overview-section.tsx @@ -71,13 +71,13 @@ function rangeToSince(range: DateRangePreset): string | undefined { export function LoginOverviewSection() { const { t } = useTranslations(); const { user } = useAuth(); - // v1.4.25 W7 — every CSV-emitted timestamp uses the admin's display + // v1.4.25 — every CSV-emitted timestamp uses the admin's display // timezone so the resulting `2026-05-11T11:05:00+02:00` cell stays // legible after Excel/LibreOffice strip the `Z` suffix from the // legacy UTC export. const userTz = user?.timezone ?? DEFAULT_TIMEZONE; - // v1.4.25 W8b — the section used to wrap its body in a + // v1.4.25 — the section used to wrap its body in a // collapse/expand toggle. Since v1.5 phase-4b moved this to a // dedicated `/admin/login-overview` route (the only thing on the // page), the toggle was pure clutter: visiting the page already @@ -88,7 +88,7 @@ export function LoginOverviewSection() { // Quick-filter pill (kept from the v1.4.x UI for one-tap "show failed"). const [filter, setFilter] = useState<"all" | "failed">("all"); - // v1.4.16 phase B4 — deeper filter/pagination/export. + // v1.4.16 — deeper filter/pagination/export. const [actor, setActor] = useState(""); const [actionFilter, setActionFilter] = useState(""); const [target, setTarget] = useState(""); @@ -183,7 +183,7 @@ export function LoginOverviewSection() { user: t("admin.userColumn"), ip: t("admin.ip"), location: t("admin.location"), - // v1.4.27 B3 — `admin.carrier` is shipped by bucket B6. The + // v1.4.27 — `admin.carrier` is shipped by bucket B6. The // `t()` helper falls back to the raw key string when the // translation row hasn't landed yet, so the CSV stays valid // through the staggered B3 → B6 release. @@ -404,7 +404,7 @@ export function LoginOverviewSection() { } /> ) : ( - // v1.4.27 MB5 — the audit table itself stays in an + // v1.4.27 — the audit table itself stays in an // `overflow-x-auto` scroll container so wide rows can pan // horizontally on phones, but the pagination + summary line // moved out into a sibling `
` below so the prev/next @@ -432,7 +432,7 @@ export function LoginOverviewSection() { {t("admin.ip")} {/* - v1.4.25 W8b — Standort column. Previously rendered + v1.4.25 — Standort column. Previously rendered `text-right`, which on wide audit tables let the `Berlin, DE` label drift to the table edge and blend into the timestamp gutter. Left-aligning it diff --git a/src/components/admin/recent-audit-preview.tsx b/src/components/admin/recent-audit-preview.tsx index e505ac556..4d4fa4e13 100644 --- a/src/components/admin/recent-audit-preview.tsx +++ b/src/components/admin/recent-audit-preview.tsx @@ -89,7 +89,7 @@ export function RecentAuditPreview() { onRetry={() => void refetch()} /> ) : entries.length === 0 ? ( - // v1.4.15 phase-C5: compact + plain so it nests inside the + // v1.4.15: compact + plain so it nests inside the // overview card without doubling the dashed border. Reuses // the existing audit-empty string. - {/* v1.4.22 D / F-58 — icon swap so the section header matches + {/* v1.4.22 — icon swap so the section header matches the admin-shell nav tile (Bell). Clock now lives on the timing chips inline instead of competing as the page icon. */} 0 && (
- {/* v1.4.33 IW9 — h4 -> h2 so the page outline stays + {/* v1.4.33 — h4 -> h2 so the page outline stays sequential (parent admin SectionFrame ships

). */}

{t("admin.reminderCheckResults")} diff --git a/src/components/admin/section-slugs.ts b/src/components/admin/section-slugs.ts index f273d5063..55ac2e4f7 100644 --- a/src/components/admin/section-slugs.ts +++ b/src/components/admin/section-slugs.ts @@ -21,7 +21,7 @@ export const ADMIN_SECTION_SLUGS = [ // for one coherent coach-quality concern read as clutter; one card opens // the full picture. "coach", - // v1.18.6 (W9) — server-wide module availability is its own section now + // v1.18.6 — server-wide module availability is its own section now // (was stacked under Coach). It gates every module, not just the coach, // so it reads better next to Coach than buried inside it. "module-availability", @@ -41,7 +41,7 @@ export const ADMIN_SECTION_SLUGS = [ // danger zone. "encryption", "danger-zone", - // v1.4.36 W4e — About / version / update check folded into Admin + // v1.4.36 — About / version / update check folded into Admin // Console. Lives at /admin/about; the legacy /settings/about route // is unlinked from the user-card dropdown. "about", diff --git a/src/components/admin/system-status-section.tsx b/src/components/admin/system-status-section.tsx index ee67f9999..6eaa8aecd 100644 --- a/src/components/admin/system-status-section.tsx +++ b/src/components/admin/system-status-section.tsx @@ -30,7 +30,7 @@ import { type FailingQueue, } from "./_shared"; -// v1.4.16 phase B3: Recharts is ~108 KiB Brotli — defer-load the host +// v1.4.16: Recharts is ~108 KiB Brotli — defer-load the host // metrics chart so the system-status page only ships it when an admin // actually opens this view. Splitting at the wrapper boundary (instead // of per-primitive) keeps Recharts' `findAllByType` reconciliation @@ -247,7 +247,7 @@ export function SystemStatusSection() { tone="success" /> )} - {/* v1.4.27 R5 — offline GeoLite2 availability, read from the + {/* v1.4.27 — offline GeoLite2 availability, read from the admin status rather than the public /api/version. */} )} - {/* v1.4.27 R5 — surface the offline-geo state so the maintainer + {/* v1.4.27 — surface the offline-geo state so the maintainer spots the missing GeoLite2 databases without crawling logs. Read from the admin status, not the public /api/version. */} ` so an admin filter that returned // no rows produced a blank rectangle; the icon + filter-aware // copy + "Show all users" CTA make the state explicit. diff --git a/src/components/admin/version-tile-section.tsx b/src/components/admin/version-tile-section.tsx index 36648bf7f..5d0ef80a3 100644 --- a/src/components/admin/version-tile-section.tsx +++ b/src/components/admin/version-tile-section.tsx @@ -3,7 +3,7 @@ /** * `` — admin-overview version card. * - * v1.15.12 H1 — a dedicated tile that surfaces the running build's + * v1.15.12 — a dedicated tile that surfaces the running build's * version, short build SHA, and build timestamp, plus a best-effort * "update available" hint computed against the latest GitHub release * tag. The release comparison reuses the existing diff --git a/src/components/charts/__tests__/bd-tile.test.tsx b/src/components/charts/__tests__/bd-tile.test.tsx index f1c57e119..4a9a9ea4e 100644 --- a/src/components/charts/__tests__/bd-tile.test.tsx +++ b/src/components/charts/__tests__/bd-tile.test.tsx @@ -6,7 +6,7 @@ import { TrendCard } from "../trend-card"; import { I18nProvider } from "@/lib/i18n/context"; /** - * v1.4.28 R3a FB-C1 + FB-C2 — BD-Zielbereich tile shape parity. + * v1.4.28 — BD-Zielbereich tile shape parity. * * Until v1.4.27 the dashboard mounted the BD tile through a special * branch that: diff --git a/src/components/charts/__tests__/chart-overlay-controls.test.tsx b/src/components/charts/__tests__/chart-overlay-controls.test.tsx index 9fa0f9d9c..8d4a325ea 100644 --- a/src/components/charts/__tests__/chart-overlay-controls.test.tsx +++ b/src/components/charts/__tests__/chart-overlay-controls.test.tsx @@ -175,7 +175,7 @@ describe("", () => { }); /** - * v1.4.25 W3f — comparison-baseline grey-out when the parent chart + * v1.4.25 — comparison-baseline grey-out when the parent chart * reports `hasComparisonData=false`. The disabled state communicates * "you selected a comparison but there's no prior-period series to * paint" instead of silently hiding the overlay. diff --git a/src/components/charts/__tests__/chart-skeleton.test.tsx b/src/components/charts/__tests__/chart-skeleton.test.tsx index d9d6447ec..26bccebd4 100644 --- a/src/components/charts/__tests__/chart-skeleton.test.tsx +++ b/src/components/charts/__tests__/chart-skeleton.test.tsx @@ -5,7 +5,7 @@ import { I18nProvider } from "@/lib/i18n/context"; import { ChartSkeleton } from "../chart-skeleton"; /** - * v1.4.36 W2 — `` carries two variants so the loading + * v1.4.36 — `` carries two variants so the loading * shell occupies the same visible footprint as the chart that lands * after `next/dynamic` resolves. * @@ -51,7 +51,7 @@ describe("", () => { expect(html).toMatch(/h-\[140px\]/); }); - // v1.4.43 QoL (L4) — slow-hint caption appears after a 3 s grace. + // v1.4.43 — slow-hint caption appears after a 3 s grace. // SSR markup never paints the caption because `useEffect` does not // run server-side; this also keeps hydration mismatch-free. it("does NOT paint the slow-hint caption in the initial SSR markup", () => { diff --git a/src/components/charts/__tests__/chart-tooltip.test.tsx b/src/components/charts/__tests__/chart-tooltip.test.tsx index d1d78a81a..8a3f25865 100644 --- a/src/components/charts/__tests__/chart-tooltip.test.tsx +++ b/src/components/charts/__tests__/chart-tooltip.test.tsx @@ -4,7 +4,7 @@ import { renderToStaticMarkup } from "react-dom/server"; import { RichChartTooltip } from "../chart-tooltip"; /** - * v1.4.16 B1a — Apple-Health-style tooltip primitive contract. + * v1.4.16 — Apple-Health-style tooltip primitive contract. */ describe("", () => { it("renders nothing when inactive", () => { diff --git a/src/components/charts/__tests__/compliance-heatmap-a11y.test.tsx b/src/components/charts/__tests__/compliance-heatmap-a11y.test.tsx index bf5459336..92d51e243 100644 --- a/src/components/charts/__tests__/compliance-heatmap-a11y.test.tsx +++ b/src/components/charts/__tests__/compliance-heatmap-a11y.test.tsx @@ -5,7 +5,7 @@ import { I18nProvider } from "@/lib/i18n/context"; import { ComplianceHeatmap } from "../compliance-heatmap"; /** - * 2026-07-17 a11y audit (M2) — medication-compliance heatmap day cells are + * Medication-compliance heatmap day cells are * pointer-only SVG ``s. The keyboard/screen-reader path to the * per-day values is the data table under the grid (v1.42: the same * `ChartDataTable` every day-linked chart carries, collapsed until asked diff --git a/src/components/charts/__tests__/health-chart-annotations.test.ts b/src/components/charts/__tests__/health-chart-annotations.test.ts index 3a2d9e38b..70e148ea0 100644 --- a/src/components/charts/__tests__/health-chart-annotations.test.ts +++ b/src/components/charts/__tests__/health-chart-annotations.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"; import { resolveAnnotationPositions } from "../health-chart"; /** - * v1.4.20 phase B4 — storyboard-annotation pinning. + * v1.4.20 — storyboard-annotation pinning. * * The chart maps incoming `{date, label, color}[]` annotations onto * the bucketed series so each annotation paints a vertical reference diff --git a/src/components/charts/__tests__/health-chart-comparison.test.tsx b/src/components/charts/__tests__/health-chart-comparison.test.tsx index e1c34d431..6b2d61cca 100644 --- a/src/components/charts/__tests__/health-chart-comparison.test.tsx +++ b/src/components/charts/__tests__/health-chart-comparison.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.16 phase B8 — HealthChart comparison overlay contract. + * v1.4.16 — HealthChart comparison overlay contract. * * Asserts the SSR-discoverable surface markers when a comparison * baseline is supplied: diff --git a/src/components/charts/__tests__/health-chart-mini.test.tsx b/src/components/charts/__tests__/health-chart-mini.test.tsx index dbb65db6a..baeaf6db7 100644 --- a/src/components/charts/__tests__/health-chart-mini.test.tsx +++ b/src/components/charts/__tests__/health-chart-mini.test.tsx @@ -44,7 +44,7 @@ import { HealthChart } from "../health-chart"; import { resolveMiniRangePoints, type DataWindow } from "../mini-window"; /** - * v1.4.16 phase B5c — chart mini-mode + windowOverride. + * v1.4.16 — chart mini-mode + windowOverride. * * The Oura-style RecommendationCard (B5c task 5) embeds a small * version of the chart for the recommendation's data window. The diff --git a/src/components/charts/__tests__/health-chart-no-data-in-range.test.tsx b/src/components/charts/__tests__/health-chart-no-data-in-range.test.tsx index c0af0a7d3..03dedc95f 100644 --- a/src/components/charts/__tests__/health-chart-no-data-in-range.test.tsx +++ b/src/components/charts/__tests__/health-chart-no-data-in-range.test.tsx @@ -6,7 +6,7 @@ import { renderToStaticMarkup } from "react-dom/server"; import deMessages from "../../../../messages/de.json"; /** - * v1.4.43 W11-M6 — chart empty-window state. + * v1.4.43 — chart empty-window state. * * Pre-fix the chart returned `null` when the loaded data array was * empty in the selected range, erasing the entire card (header + diff --git a/src/components/charts/__tests__/health-chart-polish.test.tsx b/src/components/charts/__tests__/health-chart-polish.test.tsx index 2dc052ed8..9a69d0f85 100644 --- a/src/components/charts/__tests__/health-chart-polish.test.tsx +++ b/src/components/charts/__tests__/health-chart-polish.test.tsx @@ -4,7 +4,7 @@ import { renderToStaticMarkup } from "react-dom/server"; import { computePersonalBaseline } from "../health-chart"; /** - * v1.4.16 B1a — HealthChart Apple-Health-style polish contract. + * v1.4.16 — HealthChart Apple-Health-style polish contract. * * The HealthChart wrapper is the single component that the dashboard * uses for BP, weight, pulse, body-fat, sleep, and steps. We upgrade it diff --git a/src/components/charts/__tests__/health-chart-preloaded-coverage.test.tsx b/src/components/charts/__tests__/health-chart-preloaded-coverage.test.tsx index d207c76fd..108d771ba 100644 --- a/src/components/charts/__tests__/health-chart-preloaded-coverage.test.tsx +++ b/src/components/charts/__tests__/health-chart-preloaded-coverage.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.19.0 W11b — preloaded-coverage guard. + * v1.19.0 — preloaded-coverage guard. * * The dashboard batches a ~30-day series slice and hands each chart its * portion via `preloadedSeries`. Pre-fix the chart consumed that slice for diff --git a/src/components/charts/__tests__/health-chart-range.test.tsx b/src/components/charts/__tests__/health-chart-range.test.tsx index 3db9cb2f6..5edf1af68 100644 --- a/src/components/charts/__tests__/health-chart-range.test.tsx +++ b/src/components/charts/__tests__/health-chart-range.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.28 R3a FB-D2 (R1.2 H0) — assert the HealthChart fetcher passes + * v1.4.28 (R1.2 H0) — assert the HealthChart fetcher passes * a bounded `from` / `to` window to `/api/measurements` instead of * walking the full history with a `while (true)` loop. * @@ -87,7 +87,7 @@ describe(" — bounded range fetch", () => { expect(measurementsUrl!).toMatch(/type=PULSE/); // Guard against the legacy unbounded paginator returning. expect(measurementsUrl!).not.toMatch(/offset=/); - // v1.4.29 C3 — the default 30-day window must request server- + // v1.4.29 — the default 30-day window must request server- // side daily aggregation so the client doesn't pay for ~5000 // raw pulse rows on every range-tab change. expect(measurementsUrl!).toMatch(/aggregate=daily/); diff --git a/src/components/charts/__tests__/health-chart-vertical-markers.test.ts b/src/components/charts/__tests__/health-chart-vertical-markers.test.ts index 427d2813b..8b8a8ef6e 100644 --- a/src/components/charts/__tests__/health-chart-vertical-markers.test.ts +++ b/src/components/charts/__tests__/health-chart-vertical-markers.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"; import { resolveVerticalMarkerPositions } from "../health-chart"; /** - * v1.4.25 W6 — GLP-1 injection-day vertical-marker pinning. + * v1.4.25 — GLP-1 injection-day vertical-marker pinning. * * The helper maps incoming `{date, label?, color?}[]` markers onto the * bucketed chart series, returning the pointIndex of every match plus diff --git a/src/components/charts/__tests__/medication-compliance-chart-empty-state-gate.test.tsx b/src/components/charts/__tests__/medication-compliance-chart-empty-state-gate.test.tsx index 197cf9a01..d2ab37256 100644 --- a/src/components/charts/__tests__/medication-compliance-chart-empty-state-gate.test.tsx +++ b/src/components/charts/__tests__/medication-compliance-chart-empty-state-gate.test.tsx @@ -6,7 +6,7 @@ import { renderToStaticMarkup } from "react-dom/server"; import deMessages from "../../../../messages/de.json"; /** - * v1.4.43 W2-CHART-GATE — medication-compliance empty-state copy split. + * v1.4.43 — medication-compliance empty-state copy split. * * The chart aggregates per-day compliance buckets, so a user with 20 * scheduled doses on 2 calendar days collapses to `chartData.length = diff --git a/src/components/charts/__tests__/medication-compliance-chart.test.tsx b/src/components/charts/__tests__/medication-compliance-chart.test.tsx index 7aae3683f..98c294882 100644 --- a/src/components/charts/__tests__/medication-compliance-chart.test.tsx +++ b/src/components/charts/__tests__/medication-compliance-chart.test.tsx @@ -94,7 +94,7 @@ describe("aggregateMedicationCompliance()", () => { }); /** - * v1.4.16 A6 — 7-day trend computation. + * v1.4.16 — 7-day trend computation. * * The chart header now paints a 7-day trend chip with a signed delta * in percentage points (pp) plus a direction arrow. The helper @@ -191,7 +191,7 @@ describe("", () => { }); /** - * v1.4.16 A6 — the trend chip is suppressed when the dataset is + * v1.4.16 — the trend chip is suppressed when the dataset is * empty (the empty-state placeholder is shown instead). Once data * arrives, the chip + reference lines paint — those are exercised * end-to-end by the e2e suite; a unit-rendered SSR snapshot can't diff --git a/src/components/charts/__tests__/mood-chart-aggregation.test.tsx b/src/components/charts/__tests__/mood-chart-aggregation.test.tsx index 08a878312..45180d000 100644 --- a/src/components/charts/__tests__/mood-chart-aggregation.test.tsx +++ b/src/components/charts/__tests__/mood-chart-aggregation.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.15 Fix 3: the mood chart used to render raw daily points only + * v1.4.15: the mood chart used to render raw daily points only * regardless of window length, while every other dashboard chart * (BP, weight, pulse) auto-aggregated to weekly / monthly when the * visible range got long. The aggregation helpers + chip are diff --git a/src/components/charts/__tests__/mood-chart-empty-state-gate.test.tsx b/src/components/charts/__tests__/mood-chart-empty-state-gate.test.tsx index 70673c9de..48a3c552a 100644 --- a/src/components/charts/__tests__/mood-chart-empty-state-gate.test.tsx +++ b/src/components/charts/__tests__/mood-chart-empty-state-gate.test.tsx @@ -6,7 +6,7 @@ import { renderToStaticMarkup } from "react-dom/server"; import deMessages from "../../../../messages/de.json"; /** - * v1.4.43 W2-CHART-GATE — mood-chart empty-state copy split. + * v1.4.43 — mood-chart empty-state copy split. * * `chartData` collapses entries to one point per calendar day, so a * user with many mood entries on < 3 distinct days saw the legacy diff --git a/src/components/charts/__tests__/mood-chart-polish.test.tsx b/src/components/charts/__tests__/mood-chart-polish.test.tsx index 41cad2fc3..48fdefbb8 100644 --- a/src/components/charts/__tests__/mood-chart-polish.test.tsx +++ b/src/components/charts/__tests__/mood-chart-polish.test.tsx @@ -83,7 +83,7 @@ describe(" v1.4.18 clean-line revert", () => { }); it("paints the rounded-md mini shell so the trends row shares one radius", async () => { - // D-H5 — the mood-chart mini sits next to two HealthChart mini + // The mood-chart mini sits next to two HealthChart mini // tiles on `/insights`. HealthChart mini paints // `bg-card border-border rounded-md border p-2`; mood mini rode // the shadcn `` default `rounded-xl`, so the trends row diff --git a/src/components/charts/__tests__/mood-heatmap-a11y.test.tsx b/src/components/charts/__tests__/mood-heatmap-a11y.test.tsx index 2ae560edd..3598c74e3 100644 --- a/src/components/charts/__tests__/mood-heatmap-a11y.test.tsx +++ b/src/components/charts/__tests__/mood-heatmap-a11y.test.tsx @@ -5,7 +5,7 @@ import { I18nProvider } from "@/lib/i18n/context"; import { MoodHeatmap } from "../mood-heatmap"; /** - * 2026-07-17 a11y audit (M2) — mood heatmap day cells are pointer-only SVG + * Mood heatmap day cells are pointer-only SVG * ``s. The keyboard/screen-reader path to the per-day values is a * the data table under the grid (v1.42: the same `ChartDataTable` every * day-linked chart carries, collapsed until asked for), not focusable rects diff --git a/src/components/charts/__tests__/trend-card-7d-trend.test.tsx b/src/components/charts/__tests__/trend-card-7d-trend.test.tsx index 133332514..7c01a2bf1 100644 --- a/src/components/charts/__tests__/trend-card-7d-trend.test.tsx +++ b/src/components/charts/__tests__/trend-card-7d-trend.test.tsx @@ -9,7 +9,7 @@ import { primeLocales } from "@/__tests__/helpers/prime-locales"; primeLocales("de"); /** - * v1.4.15 Fix 4 — "7-Tage-Schnitt" → "7-Tage-Trend". + * v1.4.15 — "7-Tage-Schnitt" → "7-Tage-Trend". * * The avg7 line on every dashboard tile used to read "7d: 80.5" / * "7T: 80,5" — visually an *average*. the maintainer wanted it to convey diff --git a/src/components/charts/__tests__/trend-card-baseline-alignment.test.tsx b/src/components/charts/__tests__/trend-card-baseline-alignment.test.tsx index bee89b738..3cb247c7b 100644 --- a/src/components/charts/__tests__/trend-card-baseline-alignment.test.tsx +++ b/src/components/charts/__tests__/trend-card-baseline-alignment.test.tsx @@ -6,7 +6,7 @@ import { TrendCard } from "../trend-card"; import { I18nProvider } from "@/lib/i18n/context"; /** - * v1.4.25 W20a — regression coverage for the dashboard top-tile polish. + * v1.4.25 — regression coverage for the dashboard top-tile polish. * * Maintainer directive 2026-05-14 named three issues with the 5-tile top row: * diff --git a/src/components/charts/__tests__/trend-card-comparison.test.tsx b/src/components/charts/__tests__/trend-card-comparison.test.tsx index 03436a920..a21712d4d 100644 --- a/src/components/charts/__tests__/trend-card-comparison.test.tsx +++ b/src/components/charts/__tests__/trend-card-comparison.test.tsx @@ -6,7 +6,7 @@ import { TrendCard } from "../trend-card"; import { I18nProvider } from "@/lib/i18n/context"; /** - * v1.4.16 phase B8 — TrendCard comparison delta callout. + * v1.4.16 — TrendCard comparison delta callout. * * The tile picks up an optional `compareBaseline` + `compareDelta` * pair. When both are set, the second-line callout renders with diff --git a/src/components/charts/__tests__/trend-card-stale-days.test.tsx b/src/components/charts/__tests__/trend-card-stale-days.test.tsx index 2f068072b..d8a846692 100644 --- a/src/components/charts/__tests__/trend-card-stale-days.test.tsx +++ b/src/components/charts/__tests__/trend-card-stale-days.test.tsx @@ -9,7 +9,7 @@ import { primeLocales } from "@/__tests__/helpers/prime-locales"; primeLocales("de"); /** - * v1.4.34 IW-B — pin the bucket-aware stale-hint copy on ``. + * v1.4.34 — pin the bucket-aware stale-hint copy on ``. * * The tile shows a "Letzter Wert vor …" caption when the metric has not * been logged in a while, so the tile stays on the dashboard with an diff --git a/src/components/charts/__tests__/trend-card.test.tsx b/src/components/charts/__tests__/trend-card.test.tsx index 1e1d9b434..aea0c6d87 100644 --- a/src/components/charts/__tests__/trend-card.test.tsx +++ b/src/components/charts/__tests__/trend-card.test.tsx @@ -7,7 +7,7 @@ import { SleepSourceDiscrepancyMarker } from "@/components/insights/sleep-source import { I18nProvider } from "@/lib/i18n/context"; /** - * v1.5 phase-5 — metric-aware trend-arrow colour. Each metric tile gets a + * v1.5 — metric-aware trend-arrow colour. Each metric tile gets a * `directionSentiment` prop (`up-good` | `up-bad` | `neutral`) so the small * up/down arrow next to the latest reading paints semantically: * @@ -284,7 +284,7 @@ describe(" valueAdornment slot", () => { describe(" responsive layout", () => { it("keeps long BP target tile content wrappable inside the card", () => { - // v1.4.28 FB-C2 — the BD-Zielbereich tile dropped `avgAllTime` + // v1.4.28 — the BD-Zielbereich tile dropped `avgAllTime` // and the synthetic slope; assert the surviving shape (overflow // wrap discipline, compare-delta callout) still ships. const html = render( diff --git a/src/components/charts/chart-overlay-controls.tsx b/src/components/charts/chart-overlay-controls.tsx index 398a77355..0f839121a 100644 --- a/src/components/charts/chart-overlay-controls.tsx +++ b/src/components/charts/chart-overlay-controls.tsx @@ -72,7 +72,7 @@ export interface ChartOverlayControlsProps { */ triggerClassName?: string; /** - * v1.4.25 W3f — when the parent chart already knows the prior period + * v1.4.25 — when the parent chart already knows the prior period * carries no overlay-able rows, it threads `hasComparisonData=false` * here so the comparison-baseline buttons render in a visibly * disabled state (reduced opacity + `aria-disabled` + tooltip) @@ -134,7 +134,7 @@ export function ChartOverlayControls({ } /** - * v1.4.25 W3f — the dropdown body is extracted so unit tests can render + * v1.4.25 — the dropdown body is extracted so unit tests can render * it without spinning up Radix's portalled popover (which is invisible * to `renderToStaticMarkup`). Production callers always go through * `` above; the body export is purely a test @@ -156,7 +156,7 @@ export function ChartOverlayControlsBody({ const trendArrowId = useId(); const targetRangeId = useId(); - // v1.4.25 W3f — when the underlying prior-period series is empty for + // v1.4.25 — when the underlying prior-period series is empty for // BOTH lastMonth and lastYear (we only know about the currently // selected baseline), grey out the non-"none" buttons. We can only // verify the active baseline truthfully, so the disabled state is diff --git a/src/components/charts/chart-skeleton.tsx b/src/components/charts/chart-skeleton.tsx index 2d10370cd..f160a07b3 100644 --- a/src/components/charts/chart-skeleton.tsx +++ b/src/components/charts/chart-skeleton.tsx @@ -7,7 +7,7 @@ import { cn } from "@/lib/utils"; import { useTranslations } from "@/lib/i18n/context"; /** - * v1.4.28 R3d (R1.2 H4) — layout-stable loading shell for every + * v1.4.28 (R1.2 H4) — layout-stable loading shell for every * `next/dynamic` chart import on the dashboard and the insights * sub-pages. * @@ -25,7 +25,7 @@ import { useTranslations } from "@/lib/i18n/context"; * The cumulative box matches the loaded chart's height to within a * pixel so the page does not jump on hydration. * - * v1.4.36 W2 — `mini` variant for the trends-row 140 px chart slot. + * v1.4.36 — `mini` variant for the trends-row 140 px chart slot. * The default skeleton's rounded-xl border + p-4/p-6 padding inflated * past the 140 px wrapper and pushed the row taller than the loaded * chart, re-introducing the very layout shift the wrapper now @@ -33,7 +33,7 @@ import { useTranslations } from "@/lib/i18n/context"; * `rounded-md p-2` and pins the inner band at 140 px so the loading * shell occupies the same visible box as the painted chart. * - * v1.4.43 QoL (L4) — when the skeleton is still painted three seconds + * v1.4.43 — when the skeleton is still painted three seconds * in, surface a small caption ("Computing analytics — this can take * a moment") so the user understands the chart is being computed * rather than broken. Today the `/api/analytics` correlations branch @@ -101,7 +101,7 @@ export function ChartSkeleton({ mini?: boolean; }) { const { t } = useTranslations(); - // v1.4.43 QoL (L4) — `showSlowHint` flips true 3 s into the load so + // v1.4.43 — `showSlowHint` flips true 3 s into the load so // the caption only appears for genuinely slow renders. Using a // `useEffect` keeps the SSR markup identical (caption absent) so // hydration never mismatches; the timer kicks in client-side once @@ -139,7 +139,7 @@ export function ChartSkeleton({ className, )} > - {/* v1.4.34 IW-G — sr-only announcement now reads the locale's + {/* v1.4.34 — sr-only announcement now reads the locale's `charts.loadingLabel` key instead of literal English. */} {t("charts.loadingLabel")} diff --git a/src/components/charts/compliance-heatmap.tsx b/src/components/charts/compliance-heatmap.tsx index d25956a3c..225148517 100644 --- a/src/components/charts/compliance-heatmap.tsx +++ b/src/components/charts/compliance-heatmap.tsx @@ -26,7 +26,7 @@ interface ComplianceHeatmapProps { const CELL_SIZE = 18; const GAP = 3; -// v1.4.27 MB7 / CF-10 — cell-floor so the static heatmap never paints +// v1.4.27 — cell-floor so the static heatmap never paints // below the touch-friendly 14 px square on narrow viewports. The // stretch branch computes an adaptive size; both branches clamp here. const CELL_FLOOR_PX = 14; @@ -56,7 +56,7 @@ function getColor(data: DailyData): string { const late = data.late ?? 0; const missed = Math.max(0, data.expected - taken - data.skipped); - // v1.4.34 IW-C — the v1.4.33 `looksClassifierBug` fallthrough is + // v1.4.34 — the v1.4.33 `looksClassifierBug` fallthrough is // gone because the root cause is fixed: `classifyIntakeTiming` now // routes proactive logs into the `early` bucket (counted as // compliant by the API) instead of flushing them to `very_late`. @@ -196,7 +196,7 @@ export function ComplianceHeatmap({ const labelWidth = stretch ? 0 : 76; const headerHeight = 18; - // v1.4.27 MB7 / CF-10 — clamp the stretch-branch adaptive cell to the + // v1.4.27 — clamp the stretch-branch adaptive cell to the // 14 px floor so the heatmap stays tap-friendly when the container // narrows. Static (non-stretch) instances use the canonical 18 px. const cellSize = @@ -241,7 +241,7 @@ export function ComplianceHeatmap({ return (
- {/* v1.4.27 MB7 / CF-10 — `overflow-x-auto` on `=sm`, so the scroll branch @@ -312,7 +312,7 @@ export function ComplianceHeatmap({ height={cellSize} rx={2} fill={cell.color} - // 2026-07-17 a11y audit (M2) — the per-day values reach + // The per-day values reach // assistive tech through the data table below the grid, not // through the rects: a cell subtree nested under the SVG's // `role="img"` is pruned from the a11y tree, and per-cell tab diff --git a/src/components/charts/health-chart-dynamic.tsx b/src/components/charts/health-chart-dynamic.tsx index d77026815..1cf7afaeb 100644 --- a/src/components/charts/health-chart-dynamic.tsx +++ b/src/components/charts/health-chart-dynamic.tsx @@ -8,7 +8,7 @@ import { ChartSkeleton } from "@/components/charts/chart-skeleton"; import { importWithRetry } from "@/lib/retry-import"; /** - * v1.4.28 (BK-F-M2) — single `next/dynamic` boundary for the + * v1.4.28 — single `next/dynamic` boundary for the * canonical `` component. * * Pre-fix, eight call sites (`src/app/page.tsx`, five insights @@ -81,7 +81,7 @@ export function HealthChartDynamic( } /** - * v1.4.36 W2 — mini variant for the trends-row 140 px chart slot. Same + * v1.4.36 — mini variant for the trends-row 140 px chart slot. Same * underlying `` import, but the loading shell renders the * `mini` `` so the loading box matches the painted * chart's footprint instead of inflating past the row contract. diff --git a/src/components/charts/health-chart.tsx b/src/components/charts/health-chart.tsx index 22fb703bf..640c8db11 100644 --- a/src/components/charts/health-chart.tsx +++ b/src/components/charts/health-chart.tsx @@ -120,7 +120,7 @@ interface HealthChartProps { lineOpacity?: number; }>; /** - * v1.4.16 phase B5c — compact chart mode used by the Oura-style + * v1.4.16 — compact chart mode used by the Oura-style * rationale card. Drops the range tabs, the moving-average / trend * / target-band toggle row, and shrinks padding. Tooltip + gradient * + personal-baseline behaviour stay intact so the small chart @@ -128,13 +128,13 @@ interface HealthChartProps { */ mini?: boolean; /** - * v1.4.16 phase B5c — pin the chart to a specific rationale data + * v1.4.16 — pin the chart to a specific rationale data * window regardless of any parent UI state. Used by the rec-card's * mini-chart so it always renders the window the rec is based on. */ windowOverride?: DataWindow; /** - * v1.4.16 phase B8 — when set to "lastMonth" / "lastYear", paint a + * v1.4.16 — when set to "lastMonth" / "lastYear", paint a * dimmed prior-period overlay beneath the current series. The chart * already fetches the full measurement history; the overlay is * computed by `shiftDailySeriesForward` so the prior-period day at @@ -161,7 +161,7 @@ interface HealthChartProps { */ overlayControls?: boolean; /** - * v1.4.20 phase B4 — additive storyboard annotations. + * v1.4.20 — additive storyboard annotations. * * Each entry pins a vertical reference line + label to the data point * whose Berlin-day key matches `date` (`YYYY-MM-DD`). The chart line @@ -175,7 +175,7 @@ interface HealthChartProps { */ annotations?: Array<{ date: string; label: string; color: string }>; /** - * v1.4.25 W6 — vertical injection-day markers. + * v1.4.25 — vertical injection-day markers. * * Each entry pins a thin dashed vertical reference line on the chart * at the data point whose Berlin/user-tz day-key matches `date` @@ -197,7 +197,7 @@ interface HealthChartProps { */ verticalMarkers?: Array<{ date: string; label?: string; color?: string }>; /** - * v1.4.25 W7b — per-user display timezone. When passed (mount sites + * v1.4.25 — per-user display timezone. When passed (mount sites * thread `useAuth().user?.timezone`), x-axis tick labels and the * per-day bucket keys both render in the user's zone. When a mount * passes none, the chart uses the signed-in account's zone (which @@ -332,7 +332,7 @@ interface MeasurementApiRow { // like an absent value. minValue?: number | null; maxValue?: number | null; - // v1.4.43 W2-CHART-GATE — the rollup / daily-aggregate paths return + // v1.4.43 — the rollup / daily-aggregate paths return // the underlying raw-row count per bucket. Used downstream to gate // the "more days needed" empty-state copy on the actual measurement // count rather than the number of distinct calendar days. Optional @@ -456,7 +456,7 @@ function movingAverageByPoints( } /** - * v1.4.16 B1a — personal-baseline helper. + * v1.4.16 — personal-baseline helper. * * Computes the median (50th-percentile) value of a daily-aggregated * series, capped at the most-recent 90 daily points (Apple Health uses @@ -481,7 +481,7 @@ export function computePersonalBaseline( } /** - * v1.4.20 phase B4 — pure helper that maps storyboard annotations to + * v1.4.20 — pure helper that maps storyboard annotations to * visible chart pointIndex positions. Exported so the unit suite can * pin behaviour without rendering Recharts. Returns the subset of * annotations that fall on (or within 7 days of) a visible bucketed @@ -495,7 +495,7 @@ export interface ResolvedAnnotation { } /** - * v1.4.25 W6 — pure helper for the GLP-1 injection-day vertical + * v1.4.25 — pure helper for the GLP-1 injection-day vertical * markers. Exported so the unit suite can pin behaviour without * spinning Recharts. The shape mirrors `resolveAnnotationPositions` * minus the truncated-label slot. @@ -673,14 +673,14 @@ export function HealthChart({ () => makeBucketLabelFormatters(locale, dateFormat), [locale, dateFormat], ); - // v1.4.25 W7b — per-row day-key formatter used to bucket measurement + // v1.4.25 — per-row day-key formatter used to bucket measurement // rows by the user's local calendar day. Memoised on userTimezone so // the inner per-row loop reuses a single Intl.DateTimeFormat. const dayKeyFormatter = useMemo( () => makeDayKeyFormatter(userTimezone), [userTimezone], ); - // v1.4.16 B5c: when a windowOverride is supplied, seed the range + // v1.4.16: when a windowOverride is supplied, seed the range // state from it so the chart pins to that window. Mini mode also // hides the range tabs, so the user can't change it. const initialRangePoints = windowOverride @@ -701,7 +701,7 @@ export function HealthChart({ ? overlayPrefs.prefs.comparisonBaseline : compareBaseline; - // v1.30.1 M2 — hydrate the range tab from the persisted prefs once + // v1.30.1 — hydrate the range tab from the persisted prefs once // they arrive, mirroring the overlay toggles above. A plain // `useState(30)` initializer can't see the persisted value because // the dashboard-layout query resolves after mount; `rangePoints` also @@ -727,7 +727,7 @@ export function HealthChart({ setRangePoints(overlayPrefs.prefs.rangePoints); } - // v1.4.19 A2 — viewport-aware tick density. Recharts' default is one + // v1.4.19 — viewport-aware tick density. Recharts' default is one // tick per data point; the helper caps that at 4-10 visible labels // depending on viewport width so the X-axis reads consistently across // every chart card. @@ -751,7 +751,7 @@ export function HealthChart({ ? (user.heightCm / 100) ** 2 : null; - // v1.4.28 FB-D2 (R1.2 H0) — derive a bounded date window from the + // v1.4.28 (R1.2 H0) — derive a bounded date window from the // active range selector so the chart fetches only what it will // actually render. The legacy `while (true)` paginated walk pulled // tens of thousands of pulse rows on every visit; the bounded @@ -827,7 +827,7 @@ export function HealthChart({ // render below pairs it with a visual dimming while `isPlaceholderData` // is true — the old range must never masquerade as the new one. placeholderData: keepPreviousData, - // v1.4.40 W-RSC — route the chart-data key through `queryKeys.chartData` + // v1.4.40 — route the chart-data key through `queryKeys.chartData` // so the `["chart-data"]` prefix lands in `measurementDependentKeys` // and a fresh measurement evicts every per-chart daily cache in one // pass (audit C2). The factory-packed tuple is byte-identical with @@ -843,11 +843,11 @@ export function HealthChart({ types.join(","), "raw", "no-bmi", - // v1.4.25 W7b — bucket keys + tick labels depend on the active + // v1.4.25 — bucket keys + tick labels depend on the active // user timezone, so re-key the cache when it changes. Without // this, a tz change inside a session would render stale buckets. userTimezone, - // v1.4.28 FB-D2 — bound the cache by the active fetch window so + // v1.4.28 — bound the cache by the active fetch window so // a range-tab change re-fetches the right slice rather than // re-running the unbounded walk. fetchWindow.from, @@ -864,7 +864,7 @@ export function HealthChart({ // a range-tab change drops batched coverage, re-fetching cleanly. preloadedSeries ? (usePreloaded ? "preloaded" : "fetch") : "", ), - // v1.4.28 FB-D2 — cache the bounded window for a minute so tab + // v1.4.28 — cache the bounded window for a minute so tab // navigation between insights sub-pages does not re-fire every // chart's fetch. `gcTime` keeps the inactive cache for five // minutes so a quick back-and-forth doesn't repay the cost. @@ -1288,7 +1288,7 @@ export function HealthChart({ onVisibleStats?.(isError ? null : visibleStatsByType); }, [mini, isLoading, isError, visibleStatsByType, onVisibleStats]); - // v1.4.16 phase B8 — comparison overlay. + // v1.4.16 — comparison overlay. // // When the toggle is active, derive a prior-period series from the // ALREADY-FETCHED daily aggregates (`data`), shift the timestamps @@ -1345,7 +1345,7 @@ export function HealthChart({ }, [chartData, effectiveCompareBaseline, data, types, tzFmt]); /** - * v1.4.16 phase B8 — true when at least one visible day has a prior- + * v1.4.16 — true when at least one visible day has a prior- * period value to overlay. Drives the "Comparison unavailable" caption * fallback in the chart header. Empty input → false → caption shows; * a partial overlay (some days have prior data, some don't) is treated @@ -1381,7 +1381,7 @@ export function HealthChart({ const keys = [...types]; if (showMA) keys.push(...types.map((type) => `${type}_ma`)); if (showTrend) keys.push(...types.map((type) => `${type}_trend`)); - // v1.4.16 phase B8 — extend the y-domain to fit the prior-period + // v1.4.16 — extend the y-domain to fit the prior-period // overlay. Without this the dimmed line can clip outside the // visible range when last-month / last-year had a different scale. if (effectiveCompareBaseline !== "none") { @@ -1484,7 +1484,7 @@ export function HealthChart({ return zones; }, [showBands, targetZones, yDomain]); - // v1.4.33 F10 — preserve one decimal when the visible Y-domain span is + // v1.4.33 — preserve one decimal when the visible Y-domain span is // narrow enough that two distinct gridlines would otherwise round to // the same integer (e.g. weight at 82.8 vs 83.5 both rendering as // "83 kg"). The threshold matches the body-composition cluster: any @@ -1597,7 +1597,7 @@ export function HealthChart({ (point) => point[`${type}_trend`] as number, ); - // v1.4.16 Fix A8b — delegate to the pure helper so the long- + // v1.4.16 — delegate to the pure helper so the long- // window split-half delta gets a unit-test that doesn't depend // on Recharts. See `src/lib/analytics/window-trend.ts`. const computed = computeWindowTrend({ @@ -1615,7 +1615,7 @@ export function HealthChart({ : null; const unitSuffix = unit ? ` ${unit}` : ""; - // v1.4.16 Fix A8b: when the visible window is long (e.g. "All" + // v1.4.16: when the visible window is long (e.g. "All" // on a multi-year account), the per-week delta becomes vanishingly // small and rounds to ±0.0 in the formatter — the maintainer's complaint // "Wenn ich alle anklicke, dann wird einfach Null Veränderung @@ -1654,7 +1654,7 @@ export function HealthChart({ .filter((entry): entry is string => entry !== null); }, [chartData, showTrend, types, unit, valueMode, t, fmt]); - // v1.4.16 B1a — personal baseline (90-day rolling median) per type. + // v1.4.16 — personal baseline (90-day rolling median) per type. // Painted as a faint dashed reference line labelled "Your normal" so // the user can read each point against their own baseline, not an // absolute value the chart axis happens to render. @@ -1671,7 +1671,7 @@ export function HealthChart({ return map; }, [chartData, types]); - // v1.4.43 W11-M6 — keep the card shell mounted even when the loaded + // v1.4.43 — keep the card shell mounted even when the loaded // payload is empty. Pre-fix the early `return null` here erased the // whole widget (header + range tabs + chart) and the user saw an // unexplained gap on the dashboard. The in-card render branch below @@ -1682,7 +1682,7 @@ export function HealthChart({ const maxPointIndex = Math.max(0, (chartData?.length ?? 1) - 1); - // v1.4.20 phase B4 — derive visible annotations via the pure helper + // v1.4.20 — derive visible annotations via the pure helper // (`resolveAnnotationPositions`). Drops anything outside the // visible window so the chart line + tooltip stay untouched. const annotationPositions = resolveAnnotationPositions( @@ -1690,7 +1690,7 @@ export function HealthChart({ chartData, ); - // v1.4.25 W6 — GLP-1 injection-day vertical markers. Pure-helper + // v1.4.25 — GLP-1 injection-day vertical markers. Pure-helper // pattern lets the chart-tests pin the marker resolution without // mounting Recharts. Off-window markers silently drop here so the // below never paints a line for an out-of-range @@ -1741,14 +1741,14 @@ export function HealthChart({ const showContextDetails = showMA || showTrend || showBands; const animationsEnabled = !prefersReducedMotion(); - // v1.4.16 B5c — mini mode tunes padding + margins so the chart fits + // v1.4.16 — mini mode tunes padding + margins so the chart fits // inside the rationale card without overwhelming the surrounding // rows. Range tabs + toggle row are suppressed entirely; the chart // pins to `windowOverride` (or the default 30pt) instead. const containerClass = mini ? "bg-card border-border rounded-md border p-2" : "bg-card border-border rounded-xl border p-4 md:p-6"; - // v1.4.27 MB7 / CF-43 — chart height now reads from a CSS variable + // v1.4.27 — chart height now reads from a CSS variable // (`--chart-height`) so consumers can override the default per-mount // without re-styling the component. The `mini` branch keeps the // 140 px contract; the regular branch defaults to 240 px on mobile @@ -1761,7 +1761,7 @@ export function HealthChart({ return (
{!mini && ( - /* v1.4.19 A2 — mobile-first header layout. + /* v1.4.19 — mobile-first header layout. Pre-fix: title + bucket chip + comparison chip + 4 range tabs + cog all sat on a single justify-between flex row. On Pixel 5 @@ -1811,13 +1811,13 @@ export function HealthChart({ {t(bucketCaption)} )} - {/* v1.4.16 phase B8 — comparison caption. Inline with the + {/* v1.4.16 — comparison caption. Inline with the bucket-aggregation chip so the user reads "what window am I looking at" + "what comparison is overlaid" in one glance. The "Comparison unavailable" fallback uses neutral muted styling so it doesn't read as an error. - v1.4.19 A2 — hidden on mobile to free up the title row. */} + v1.4.19 — hidden on mobile to free up the title row. */} {effectiveCompareBaseline !== "none" && hasComparisonData && ( void refetch()} /> ) : !chartData?.length ? ( - // v1.4.43 W11-M6 — empty-window state. + // v1.4.43 — empty-window state. // // The chart only withholds rendering when the selected range // holds zero daily points. Any real data — even a single day — @@ -1953,7 +1953,7 @@ export function HealthChart({ }`} > {visibleBands.length > 0 ? ( - // v1.4.27 R3d MB2 — band overlay positioning fix. The + // v1.4.27 — band overlay positioning fix. The // overlay used to inset `right: 18px` while the chart // margin is `right: 8`, so the band rectangle drifted left // of the plotted line by 10 px on every viewport. Pin the @@ -2097,7 +2097,7 @@ export function HealthChart({ } /> ))} - {/* v1.4.20 phase B4 — storyboard annotations. + {/* v1.4.20 — storyboard annotations. Vertical reference lines pinned to a specific visible pointIndex; the label sits above with a short truncation on ` ))} - {/* v1.4.25 W6 — GLP-1 injection-day vertical markers. + {/* v1.4.25 — GLP-1 injection-day vertical markers. Thin dashed line + a small filled dot at the x-axis intersection so a row of markers reads as "injection cadence" without crowding the canvas. Optional label @@ -2285,7 +2285,7 @@ export function HealthChart({ continue; const baseline = personalBaselines.get(dataKey); let delta: string | undefined; - // v1.4.16 phase B8 — prefer the comparison delta + // v1.4.16 — prefer the comparison delta // ("Δ −7 vs. last month") over the personal // baseline delta when comparison is active and we // have a prior value for this day. Otherwise fall @@ -2346,7 +2346,7 @@ export function HealthChart({ color: item.color ?? "var(--dracula-purple)", delta, }); - // v1.4.16 phase B8 — also surface the prior-period + // v1.4.16 — also surface the prior-period // value as its own row so the user reads both // numbers (current AND last-month / last-year) // alongside the delta. @@ -2466,7 +2466,7 @@ export function HealthChart({ connectNulls /> ))} - {/* v1.4.16 phase B8 — comparison overlay. + {/* v1.4.16 — comparison overlay. A dimmed dashed line per type for the prior period (lastMonth / lastYear), painted BENEATH the current series via Recharts' source-order layering. Same diff --git a/src/components/charts/medication-compliance-chart.tsx b/src/components/charts/medication-compliance-chart.tsx index 13697a4c7..cc3965bef 100644 --- a/src/components/charts/medication-compliance-chart.tsx +++ b/src/components/charts/medication-compliance-chart.tsx @@ -12,7 +12,7 @@ * range chips in the header, ReferenceLine target at 100 % goal + * 80 % minimum-acceptable threshold). * - * v1.4.16 A6 — feature-parity with the other charts: + * v1.4.16 — feature-parity with the other charts: * - Computes a 7-day trend chip in the header (signed delta, metric- * aware sentiment colour: rising compliance → green, falling → * orange). @@ -110,7 +110,7 @@ type RangeDays = (typeof RANGE_DAYS)[number]; * * Pure & deterministic so the unit test pins exact rates. * - * v1.4.25 W7b — the `dateFormatter` argument lets the chart pass a + * v1.4.25 — the `dateFormatter` argument lets the chart pass a * tz-aware formatter so a Pacific/Auckland user's X-axis tick label * for "2026-05-15" reads as 15.05. in their tz. Default keeps the * legacy `formatDateShort` so the existing unit test (which calls the @@ -131,7 +131,7 @@ export function aggregateMedicationCompliance( const ts = Date.UTC(y, m - 1, d, 12); const rate = Math.min(100, Math.round((p.taken / p.scheduled) * 100)); return { - // v1.4.25 W3b — short-form day label ("10.05." / "10. May") so + // v1.4.25 — short-form day label ("10.05." / "10. May") so // the X-axis ticks read cleanly on mobile even when the tick // density helper steps to every-7th or every-14th day. The // tooltip below still derives its label from `timestamp` with @@ -192,7 +192,7 @@ interface MedicationComplianceChartProps { /** Override the visible label, primarily for tests. */ title?: string; /** - * v1.4.16 phase D reconcile (H4 design) — when the user toggles the + * v1.4.16 (H4 design) — when the user toggles the * dashboard comparison overlay, every other chart paints a dimmed * prior-period line. Compliance is a percentage-of-doses metric whose * "prior month" overlay would need a second window of intake events @@ -203,7 +203,7 @@ interface MedicationComplianceChartProps { */ compareBaseline?: "none" | "lastMonth" | "lastYear"; /** - * v1.4.25 W7b — per-user display timezone for x-axis tick + tooltip + * v1.4.25 — per-user display timezone for x-axis tick + tooltip * date strings. Defaults to "Europe/Berlin" so older callers stay * byte-identical. */ @@ -229,7 +229,7 @@ export function MedicationComplianceChart({ onDataReady, dayLinks = false, }: MedicationComplianceChartProps) { - // v1.4.27 R4 RC3 — the prop is accepted so the dashboard can pass + // v1.4.27 — the prop is accepted so the dashboard can pass // `compareBaseline={compareBaseline}` uniformly across every chart. // The comparison overlay itself is deferred: medication compliance is // a percentage-of-doses metric whose prior-period overlay needs a @@ -240,7 +240,7 @@ export function MedicationComplianceChart({ const { t, locale } = useTranslations(); const fmt = useFormatters(); const [days, setDays] = useState(30); - // v1.4.25 W7b — tz-aware date formatter for the x-axis labels and + // v1.4.25 — tz-aware date formatter for the x-axis labels and // the tooltip's `dateLabel`. Same pattern as health-chart + mood-chart. // Issue #922 — the axis labels and the tooltip date follow the profile's // date order. The hour cycle is AUTO on purpose: this formatter renders @@ -259,14 +259,14 @@ export function MedicationComplianceChart({ const showTargetRange = overlayPrefs.prefs.showTargetRange; const effectiveCompareBaseline = overlayPrefs.prefs.comparisonBaseline; - // v1.4.19 A2 — universal tick-density helper. Pre-fix the medication + // v1.4.19 — universal tick-density helper. Pre-fix the medication // chart drew ONE tick per day, so a 30-day Pixel-5 window painted 30 // overlapping labels into a 393 px gutter. The helper caps visible // ticks at 6 on Pixel 5 / 4 on Fold, matching the visual breathing // room of the weight / BMI charts. const viewportWidth = useViewportWidth(); - // v1.4.40 W-RSC — factory-routed key so the prefix + // v1.4.40 — factory-routed key so the prefix // `["dashboard-medication-compliance"]` lands in // `medicationDependentKeys` and an intake POST refreshes the chart // immediately rather than waiting for `staleTime` (audit L4). @@ -330,7 +330,7 @@ export function MedicationComplianceChart({ [data, tzFmt], ); - // v1.4.43 W2-CHART-GATE — raw scheduled-dose count across every day + // v1.4.43 — raw scheduled-dose count across every day // in the window. The chart bucket aggregates per-day compliance, so // a user with 20 scheduled doses on 2 calendar days collapses to // `chartData.length = 2`. Sum the `scheduled` field across the raw @@ -345,13 +345,13 @@ export function MedicationComplianceChart({ ); }, [data]); - // v1.4.16 A6 — 7-day trend chip. Computed off the *full* range so a + // v1.4.16 — 7-day trend chip. Computed off the *full* range so a // user toggled to "7 days" still sees a trend (which would otherwise // be empty if we only used the visible window). The 14-day cap inside // computeMedicationTrend7d() bounds the comparison window. const trend = useMemo(() => computeMedicationTrend7d(chartData), [chartData]); - // v1.4.16 A6 — metric-aware sentiment for medication compliance: + // v1.4.16 — metric-aware sentiment for medication compliance: // up-good (rising compliance is the desired direction). Mirrors the // `directionSentiment="up-good"` rule used by the trend-card pulse + // mood tiles. @@ -451,7 +451,7 @@ export function MedicationComplianceChart({ data-slot="medication-compliance-chart" data-latest-rate={latestRate ?? undefined} > - {/* v1.4.19 A2 — header layout split into title row + controls + {/* v1.4.19 — header layout split into title row + controls row on small screens. Pre-fix the trend chip + 4 range tabs + cog dropdown all sat on a single justify-between row, which caused the tabs to wrap mid-row on Pixel 5 (header doubled in @@ -508,7 +508,7 @@ export function MedicationComplianceChart({ ))} {/* v1.4.18 — overlay-controls dropdown next to the range tabs. - v1.4.25 W3f — the compliance card never paints a + v1.4.25 — the compliance card never paints a prior-period overlay (the heatmap doesn't support it), so the comparison buttons grey out when a baseline is already selected to signal "this metric has no @@ -521,7 +521,7 @@ export function MedicationComplianceChart({
- {/* v1.4.16 phase D reconcile (H4 design) — explicit "comparison + {/* v1.4.16 (H4 design) — explicit "comparison N/A" caption when the global toggle is on. The compliance heatmap doesn't render a prior-period overlay (deferred to v1.4.17 when the underlying intake-event window comparison @@ -576,10 +576,10 @@ export function MedicationComplianceChart({ {t("charts.noData")}

) : chartData.length < 3 ? ( - // v1.4.16 B1a — sparse-data placeholder consistent with the + // v1.4.16 — sparse-data placeholder consistent with the // BP/weight/pulse/mood charts. // - // v1.4.43 W2-CHART-GATE — split the copy on the raw scheduled- + // v1.4.43 — split the copy on the raw scheduled- // dose count. A user with 20 scheduled doses on 2 calendar // days has `chartData.length = 2`; the legacy "log more" hint // misled — they logged plenty, just on too few days. Surface diff --git a/src/components/charts/mini-window.ts b/src/components/charts/mini-window.ts index 9bc4edfa0..a3162771c 100644 --- a/src/components/charts/mini-window.ts +++ b/src/components/charts/mini-window.ts @@ -1,5 +1,5 @@ /** - * v1.4.16 phase B5c — shared chart-mini window vocabulary. + * v1.4.16 — shared chart-mini window vocabulary. * * The Oura-style RecommendationCard (B5c) embeds a mini-chart that * pins to the recommendation's `rationale.dataWindow`. This helper diff --git a/src/components/charts/mood-chart.tsx b/src/components/charts/mood-chart.tsx index eeb46aec8..2b5641cb7 100644 --- a/src/components/charts/mood-chart.tsx +++ b/src/components/charts/mood-chart.tsx @@ -69,25 +69,25 @@ interface ChartDataPoint { score: number; ma?: number; trend?: number; - /** v1.4.16 B8 — prior-period overlay value. */ + /** v1.4.16 — prior-period overlay value. */ scoreCompare?: number; } interface MoodChartProps { title?: string; /** - * v1.4.16 B5c — compact mode for embedding inside the + * v1.4.16 — compact mode for embedding inside the * RecommendationCard. Hides the range tabs / toggle row and shrinks * padding. Tooltip stays intact. */ mini?: boolean; /** - * v1.4.16 B5c — pin the chart to a specific rationale data + * v1.4.16 — pin the chart to a specific rationale data * window regardless of any parent UI state. */ windowOverride?: "last7days" | "last30days" | "last90days" | "allTime"; /** - * v1.4.16 B8 — when set to "lastMonth" / "lastYear", overlay a + * v1.4.16 — when set to "lastMonth" / "lastYear", overlay a * dimmed prior-period mood line beneath the current series. Same * shift mechanic the BP/weight/pulse chart uses; the mood score is * a single metric so only one comparison line is drawn. @@ -368,7 +368,7 @@ export function MoodChart({ const effectiveCompareBaseline = !mini && chartKey ? overlayPrefs.prefs.comparisonBaseline : compareBaseline; - // v1.30.1 M2 — hydrate the range tab from the persisted prefs once + // v1.30.1 — hydrate the range tab from the persisted prefs once // they arrive; see health-chart.tsx for the full rationale (the // dashboard-layout query resolves after mount, and `rangePoints` // stays local state because several memos depend on it by identity). @@ -387,12 +387,12 @@ export function MoodChart({ setRangePoints(overlayPrefs.prefs.rangePoints); } - // v1.4.19 A2 — viewport-aware tick density helper. + // v1.4.19 — viewport-aware tick density helper. const viewportWidth = useViewportWidth(); const displayTitle = title ?? t("charts.mood"); - // v1.4.40 W-RSC — align the chart's queryKey with the dashboard's + // v1.4.40 — align the chart's queryKey with the dashboard's // `useQuery({ queryKey: queryKeys.moodAnalytics() })` call against the // same `/api/mood/analytics` endpoint. Pre-fix the chart fetched its // own copy under `["mood-chart-data"]` (audit C1) — TanStack treated @@ -444,7 +444,7 @@ export function MoodChart({ onDataReadyRef.current?.(); }, [isLoading]); - // v1.4.43 W2-CHART-GATE — raw mood-entry count across every day in + // v1.4.43 — raw mood-entry count across every day in // the window. `entries[].samples` carries the per-day raw count // straight from the analytics endpoint; summing it produces the // total entries the user logged, independent of how many calendar @@ -479,7 +479,7 @@ export function MoodChart({ ? allPoints.filter((point) => point.timestamp >= cutoff) : allPoints; - // v1.4.15 Fix 3: parity with health-chart's auto-bucketing. Other + // v1.4.15: parity with health-chart's auto-bucketing. Other // dashboard charts (BP, weight, pulse) auto-aggregate to weekly / // monthly when the visible range is long enough that drawing every // daily point would clutter the chart; the mood chart used to show @@ -561,7 +561,7 @@ export function MoodChart({ }, [data, rangePoints, showMA, showTrend, tzFmt, profileTimeZone]); /** - * v1.4.16 B8 — comparison overlay merged into chartData. + * v1.4.16 — comparison overlay merged into chartData. * * Same mechanic as HealthChart: shift the full mood-entries history * forward by 30 / 365 days, key by the existing `date` formatter, and @@ -633,7 +633,7 @@ export function MoodChart({ const maxPointIndex = Math.max(0, (chartData?.length ?? 1) - 1); - // v1.4.27 B6 / BL-P6-11 — the chart axis and the mood-list cards + // v1.4.27 — the chart axis and the mood-list cards // share `MOOD_LABEL_KEYS` so a polishing-pass copy update lands in // both surfaces. The shared resolver maps a numeric score back to // the canonical key set under `mood.level*`. @@ -660,7 +660,7 @@ export function MoodChart({ return label ? `${rounded} (${label})` : String(rounded); }; - // v1.4.16 B1a — personal mood baseline (median over visible window). + // v1.4.16 — personal mood baseline (median over visible window). const personalBaseline = useMemo(() => { if (!chartData?.length || chartData.length < 5) return null; const sorted = chartData.map((d) => d.score).sort((a, b) => a - b); @@ -767,13 +767,13 @@ export function MoodChart({ {t("charts.noData")}
) : chartData.length < 3 ? ( - // v1.4.16 B1a — sparse-data placeholder consistent with the + // v1.4.16 — sparse-data placeholder consistent with the // BP/weight/pulse charts. Height tracks the chart strip's // shared CHART_HEIGHT_PX so the empty state preserves the // trend-row rhythm (v1.4.27 — was 280, now 240 to match every // other dashboard chart card). // - // v1.4.43 W2-CHART-GATE — split the copy on the raw mood-entry + // v1.4.43 — split the copy on the raw mood-entry // count. A user with 50 mood entries across 2 calendar days // sees `chartData.length = 2` (daily-aggregated) but the raw // count (sum of `samples`) is 50. Surface "need more days" @@ -819,7 +819,7 @@ export function MoodChart({ accessibilityLayer onClick={chartDays.onChartClick} > - {/* v1.4.25 W3 — the mood chart's YAxis is pinned to + {/* v1.4.25 — the mood chart's YAxis is pinned to five mood-score ticks ([1,2,3,4,5]), which Recharts syncs with CartesianGrid by default. That left the mini variant painting five horizontal lines while @@ -1064,7 +1064,7 @@ export function MoodChart({ connectNulls /> )} - {/* v1.4.16 B8 — comparison overlay (mood). Same + {/* v1.4.16 — comparison overlay (mood). Same dimmed dashed treatment the BP/weight/pulse chart uses; mood is a single metric so we only render one overlay line. Suppressed when there's no prior @@ -1112,7 +1112,7 @@ export function MoodChart({ return ( - {/* v1.4.19 A2 — mobile-first header: stack title row above + {/* v1.4.19 — mobile-first header: stack title row above controls row on small viewports so the bucket / comparison chips never push the range tabs into a 2nd line. ≥sm goes back to side-by-side. */} @@ -1128,8 +1128,8 @@ export function MoodChart({ )} )} - {/* v1.4.16 B8 — comparison caption (mood). - v1.4.19 A2 — hidden on mobile to free up the title row. */} + {/* v1.4.16 — comparison caption (mood). + v1.4.19 — hidden on mobile to free up the title row. */} {effectiveCompareBaseline !== "none" && hasComparisonData && ( { setRangeHydrated(true); setRangePoints(r.days); - // v1.30.1 M2 — persist the pick per chart, same + // v1.30.1 — persist the pick per chart, same // model as the overlay toggles. if (chartKey && !windowOverride) { overlayPrefs.setPrefs({ diff --git a/src/components/charts/mood-heatmap.tsx b/src/components/charts/mood-heatmap.tsx index d37b233c8..06475692a 100644 --- a/src/components/charts/mood-heatmap.tsx +++ b/src/components/charts/mood-heatmap.tsx @@ -344,7 +344,7 @@ export function MoodHeatmap({ // mood band reads clearly; only the no-entry cells stay the // quiet `--secondary` empty-state tint. fillOpacity={1} - // 2026-07-17 a11y audit (M2) — the per-day values reach + // The per-day values reach // assistive tech through the data table below the grid, not // through the rects: a cell subtree nested under the SVG's // `role="img"` is pruned from the a11y tree, and per-cell tab diff --git a/src/components/charts/scatter-correlation-chart.tsx b/src/components/charts/scatter-correlation-chart.tsx index 033a8ba11..768643cde 100644 --- a/src/components/charts/scatter-correlation-chart.tsx +++ b/src/components/charts/scatter-correlation-chart.tsx @@ -99,7 +99,7 @@ export function ScatterCorrelationChart>({ xLabel: xAxis.name, yLabel: yAxis.name, }); - // v1.4.27 MB7 / CF-37 — drop the fixed pixel height in favour of a + // v1.4.27 — drop the fixed pixel height in favour of a // responsive aspect-ratio on mobile so the chart never compresses // below readable axes. The `aspect-square` ratio at ` fmt.number(value, fractionDigits); - // v1.4.15 Fix 4 — the 7-day delta now shares the arrow's colour so + // v1.4.15 — the 7-day delta now shares the arrow's colour so // both elements communicate the same sentiment for the same signal. const deltaSentiment = getTrendSentiment(trend7Delta, directionSentiment); const deltaColor = sentimentColorClass(deltaSentiment); /** - * v1.4.16 phase B8 — color the comparison delta with the same + * v1.4.16 — color the comparison delta with the same * metric-aware sentiment rules used everywhere else on the tile. * Tiny deltas read as stable and stay muted. */ @@ -205,7 +205,7 @@ export function TrendCard({ return (
- {/* v1.4.25 W20a — single-line discipline + deterministic height for + {/* v1.4.25 — single-line discipline + deterministic height for the heading row so the value row below sits at the same baseline across every tile. The label is locale-abbreviated upstream (see `dashboard.*Short` keys) so `truncate` is a layout safeguard @@ -220,7 +220,7 @@ export function TrendCard({
- {/* v1.4.25 W20a — baseline-aligned value row. The headline value, + {/* v1.4.25 — baseline-aligned value row. The headline value, unit, and trend arrow share a single flex line with `items-baseline` so the digits line up at the same y-coordinate across every tile in the strip (Weight 80 kg vs. BP 122 mmHg used @@ -235,7 +235,7 @@ export function TrendCard({ data-slot="trend-card-value-row" > {latest === null && emptyHint ? ( - // v1.17 W1b — thin-data placeholder. Renders the caller's short + // v1.17 — thin-data placeholder. Renders the caller's short // text (e.g. "collecting data") where the numeric headline would // sit, at a smaller size so the longer phrase fits the tile's // deterministic height. No unit; the arrow slot below still @@ -345,7 +345,7 @@ export function TrendCard({ )}`}
)} - {/* v1.4.33 maintainer-item-1 — stale-data caption. Renders + {/* v1.4.33 — stale-data caption. Renders only when the caller passes `staleDays` (positive integer of days since the metric's last reading). The tile keeps the historical value visible because the maintainer would @@ -355,7 +355,7 @@ export function TrendCard({ caption; on ` )} - {/* v1.4.28 FB-C2 — the all-time third sub-value retired + {/* v1.4.28 — the all-time third sub-value retired alongside the `avgAllTime*` props. The BD-Zielbereich tile was the only consumer; the same number stays visible on the Insights blood-pressure target panel with diff --git a/src/components/clinician/__tests__/clinician-view.test.tsx b/src/components/clinician/__tests__/clinician-view.test.tsx index d109b52b0..2492591bc 100644 --- a/src/components/clinician/__tests__/clinician-view.test.tsx +++ b/src/components/clinician/__tests__/clinician-view.test.tsx @@ -1,5 +1,5 @@ /** - * v1.11.0 (Epic C, C5) — clinician-view presentation. + * v1.11.0 — clinician-view presentation. * * Asserts the load-bearing rendering properties: the fenced wellness card * carries the descriptive "not a clinical assessment / not a diagnosis" diff --git a/src/components/custom-metrics/custom-metric-history-list.tsx b/src/components/custom-metrics/custom-metric-history-list.tsx index aadfe0b95..0c327ef13 100644 --- a/src/components/custom-metrics/custom-metric-history-list.tsx +++ b/src/components/custom-metrics/custom-metric-history-list.tsx @@ -88,7 +88,7 @@ export function CustomMetricHistoryList({ }); } - // v1.37.20 (A3-11) — undo parity with every peer entry surface: the + // v1.37.20 — undo parity with every peer entry surface: the // delete tombstones server-side and the toast offers the restore. async function restoreEntry(id: string) { try { diff --git a/src/components/daily/priority-card.tsx b/src/components/daily/priority-card.tsx index 080d30412..e958ee956 100644 --- a/src/components/daily/priority-card.tsx +++ b/src/components/daily/priority-card.tsx @@ -1,7 +1,7 @@ "use client"; /** - * P1 — `PriorityCard`, the ONE card behind every "worth a look" rail item. + * `PriorityCard`, the ONE card behind every "worth a look" rail item. * * It renders a `PriorityItem` and never fetches. Every later consumer reuses * it verbatim: the Today rail (S2), a coach check-in (S3), a milestone @@ -144,7 +144,7 @@ export function PriorityCard({ .filter((action) => action.href || onAction) .slice(0, 3); const hue = KIND_HUE[item.kind]; - // S12 — the quiet "reached" moment: the milestone card swaps the generic + // The quiet "reached" moment: the milestone card swaps the generic // fade-in for the `.milestone-reached` treatment (a soft `--success` halo // over the theme card + a one-shot spring reveal on `--ring-spring`; // reduced-motion fallback lives beside the keyframes in globals.css). The diff --git a/src/components/daily/today-hero-skeleton.tsx b/src/components/daily/today-hero-skeleton.tsx index 5f1d11527..4bb79559c 100644 --- a/src/components/daily/today-hero-skeleton.tsx +++ b/src/components/daily/today-hero-skeleton.tsx @@ -1,7 +1,7 @@ import { Skeleton } from "@/components/ui/skeleton"; /** - * S2 — loading silhouette for the Today hero. + * Loading silhouette for the Today hero. * * Mirrors the real `` footprint — the same plain `bg-card` * shell (border + radius + `p-4 md:p-6`), the two-column read/ring split diff --git a/src/components/daily/today-hero.tsx b/src/components/daily/today-hero.tsx index 936232eef..d9650202d 100644 --- a/src/components/daily/today-hero.tsx +++ b/src/components/daily/today-hero.tsx @@ -1,7 +1,7 @@ "use client"; /** - * S2 — the Today hero, the promoted day's read on the dashboard. + * The Today hero, the promoted day's read on the dashboard. * * MARC SIGN-OFF (2026-07-16, decision 1): Today is the dashboard hero * promoted to the default read, with the dense tile grid demoted below diff --git a/src/components/dashboard/__tests__/dashboard-header.test.tsx b/src/components/dashboard/__tests__/dashboard-header.test.tsx index 51d4e6297..e44fd4c94 100644 --- a/src/components/dashboard/__tests__/dashboard-header.test.tsx +++ b/src/components/dashboard/__tests__/dashboard-header.test.tsx @@ -72,7 +72,7 @@ describe(" — greeting", () => { expect(html).toContain("Dashboard"); }); - // 2026-07-17 a11y audit (M4) — the dashboard is the app's landing surface + // The dashboard is the app's landing surface // and must expose a page-level `

`. It comes from the shared // `PageHeader` the header renders, so a screen-reader user navigating by // heading lands on a real page anchor (not the Today hero's muted `

` diff --git a/src/components/dashboard/__tests__/medication-intake-quick-add.test.tsx b/src/components/dashboard/__tests__/medication-intake-quick-add.test.tsx index 351fa9641..09599b48c 100644 --- a/src/components/dashboard/__tests__/medication-intake-quick-add.test.tsx +++ b/src/components/dashboard/__tests__/medication-intake-quick-add.test.tsx @@ -1,5 +1,5 @@ /** - * v1.4.37 W7b — `` contract suite. + * v1.4.37 — `` contract suite. * * Project convention is SSR-only tests (vitest runs in `node` * environment; `@testing-library/react` is not installed). The @@ -251,7 +251,7 @@ describe(" — SSR contract", () => { expect(html).not.toContain( 'data-testid="medication-intake-quick-add-form"', ); - // v1.4.37 W10 — the empty-state CTA must clear the 44 px touch + // v1.4.37 — the empty-state CTA must clear the 44 px touch // floor that W-CI raised on the onboarding checklist. The // `size="sm"` default would land at 32 px without an explicit // `min-h-11 sm:min-h-9` override. diff --git a/src/components/dashboard/__tests__/recent-workouts-tile.test.tsx b/src/components/dashboard/__tests__/recent-workouts-tile.test.tsx index 6dabec782..170ee2639 100644 --- a/src/components/dashboard/__tests__/recent-workouts-tile.test.tsx +++ b/src/components/dashboard/__tests__/recent-workouts-tile.test.tsx @@ -108,7 +108,7 @@ describe("", () => { const html = render(); expect(html).toContain('data-slot="recent-workouts-empty"'); expect(html).toContain("No workouts yet"); - // v1.37.19 (A1-7) — the CTA no longer assumes an iPhone with Apple + // v1.37.19 — the CTA no longer assumes an iPhone with Apple // Health; WHOOP/Polar/Oura/Withings users get the same honest cue. expect(html).toContain("Connect a data source in Settings"); expect(html).not.toContain("Apple Health"); diff --git a/src/components/dashboard/dashboard-header.tsx b/src/components/dashboard/dashboard-header.tsx index 7750d2184..de3814700 100644 --- a/src/components/dashboard/dashboard-header.tsx +++ b/src/components/dashboard/dashboard-header.tsx @@ -174,7 +174,7 @@ export function DashboardHeader({ {canAddAnything && ( - {/* v1.4.33 maintainer-item-7 — restore proportional sizing + {/* v1.4.33 — restore proportional sizing across viewports. The v1.4.27 fix pinned a `size="sm" min-h-11` combo to hit WCAG 2.5.5's 44 px touch-target contract on mobile, but `size="sm"` is h-8 (32 px) and @@ -212,7 +212,7 @@ export function DashboardHeader({ {t("dashboard.quickAddMeasurement")} )} - {/* v1.4.37 W7b — third quick-add row: medication intake. + {/* v1.4.37 — third quick-add row: medication intake. Same Sheet-on-mobile / Dialog-on-desktop primitive as the other two; the menu label is a self-contained verb-phrase so it doesn't collide with the trigger or diff --git a/src/components/dashboard/medication-intake-quick-add.tsx b/src/components/dashboard/medication-intake-quick-add.tsx index ce59a7fc7..c67561b12 100644 --- a/src/components/dashboard/medication-intake-quick-add.tsx +++ b/src/components/dashboard/medication-intake-quick-add.tsx @@ -36,7 +36,7 @@ import { DEFAULT_TIMEZONE } from "@/lib/tz/format"; import { useMedicationDueThresholds } from "@/lib/queries/use-reminder-thresholds"; /** - * v1.4.37 W7b — dashboard "Hinzufügen" → "Medikamenteneinnahme" quick-add. + * v1.4.37 — dashboard "Hinzufügen" → "Medikamenteneinnahme" quick-add. * * The maintainer's brief: the dashboard's top-right "Hinzufügen" menu logs * measurements and mood entries; add a medication-intake action so a @@ -222,7 +222,7 @@ export function MedicationIntakeQuickAdd({ const doseDeviates = trimmedDose.length > 0 && trimmedDose !== (selectedMedication?.dose ?? "").trim(); - // v1.30.1 M7 — the route returns the created event + // v1.30.1 — the route returns the created event // (`apiSuccess(event, 201)`), same shape the card's take/skip path // already reads; the id drives the new Undo action below. const created = await apiPost<{ id?: string } | undefined>( @@ -319,7 +319,7 @@ export function MedicationIntakeQuickAdd({ // form itself is suppressed so we don't ship an unusable Save // button. // - // v1.4.38 W-D P2-1 — symmetry fix. The previous shape rendered the + // v1.4.38 — symmetry fix. The previous shape rendered the // CTA in the body and a footer with only the Close button, which // read as the dialog losing a save button. Now the body carries the // hint and the footer carries ` Medikament diff --git a/src/components/dashboard/quick-entry-sheets.tsx b/src/components/dashboard/quick-entry-sheets.tsx index 9ce874a15..074b25e27 100644 --- a/src/components/dashboard/quick-entry-sheets.tsx +++ b/src/components/dashboard/quick-entry-sheets.tsx @@ -88,7 +88,7 @@ export function admittedQuickEntry( } /** - * v1.11.3 F3 — guard the quick-entry sheets against an accidental + * v1.11.3 — guard the quick-entry sheets against an accidental * dismiss (overlay tap, Esc, mobile swipe-down) discarding a partly * filled form. The forms live behind a stable `ResponsiveSheet` body * slot and don't expose their dirty state, so we read it from the DOM @@ -162,12 +162,12 @@ export function QuickEntrySheets({ // Re-derived on every render, never latched: the answer can arrive after the // sheet is already open. See `admittedQuickEntry`. const openSheet = admittedQuickEntry(open, capabilities); - // v1.4.27 R4 RC2 — DOM handles for the form action-row portal target + // v1.4.27 — DOM handles for the form action-row portal target // on each quick-entry sheet. The Sheet branch sticky-pins this slot. const [measurementFooterEl, setMeasurementFooterEl] = useState(null); const [moodFooterEl, setMoodFooterEl] = useState(null); - // v1.4.37 W7b — medication-intake quick-add lives on the same Sheet + // v1.4.37 — medication-intake quick-add lives on the same Sheet // primitive as the other two quick-entries; the form's action row is // portalled into this slot so Save / Cancel stay reachable above the // mobile soft keyboard. @@ -181,7 +181,7 @@ export function QuickEntrySheets({ ); const [lifeEventFooterEl, setLifeEventFooterEl] = useState(null); - // v1.11.3 F3 — when an open quick-entry sheet is dismissed with + // v1.11.3 — when an open quick-entry sheet is dismissed with // unsaved input, hold the close in this flag and surface a confirm // instead of nulling the dialog outright. Cleared once the user // confirms (close) or keeps editing (dismiss the confirm). @@ -224,7 +224,7 @@ export function QuickEntrySheets({ footerSlot={moodFooterEl} /> - {/* v1.4.37 W7b — third Sheet: medication intake. Same + {/* v1.4.37 — third Sheet: medication intake. Same ResponsiveSheet contract as the two above; the form's footer (Cancel + Save) portals into the sticky-pinned slot so it stays reachable above the soft keyboard on mobile. */} @@ -297,7 +297,7 @@ export function QuickEntrySheets({ )} - {/* v1.11.3 F3 — confirm before discarding a partly-filled + {/* v1.11.3 — confirm before discarding a partly-filled quick-entry form when the sheet is dismissed by an overlay tap, Escape, or a mobile swipe-down. Keeps the sheet open until the user decides. */} diff --git a/src/components/dashboard/recent-workouts-tile.tsx b/src/components/dashboard/recent-workouts-tile.tsx index 3534fafdb..0b75c76f6 100644 --- a/src/components/dashboard/recent-workouts-tile.tsx +++ b/src/components/dashboard/recent-workouts-tile.tsx @@ -87,7 +87,7 @@ export function RecentWorkoutsTile() { {showLoading ? ( - // v1.4.43 W11-L6 — reserve roughly the loaded tile height + // v1.4.43 — reserve roughly the loaded tile height // (3 workouts × ~3 rem rows + spacing) so the surrounding // dashboard layout doesn't reflow once the data lands. // v1.29.x — row-shaped skeletons instead of a "loading…" text diff --git a/src/components/data-list/selection-action-bar.tsx b/src/components/data-list/selection-action-bar.tsx index 9fe66f11c..cc8804a35 100644 --- a/src/components/data-list/selection-action-bar.tsx +++ b/src/components/data-list/selection-action-bar.tsx @@ -72,7 +72,7 @@ export function SelectionActionBar({ // `:has()` gate and yields while the bar is mounted, so the // floating launcher never sits over the delete action. data-slot="selection-action-bar" - // v1.15.13 HIGH-1 — on mobile the fixed bottom-nav (64px, `md:hidden`, + // v1.15.13 — on mobile the fixed bottom-nav (64px, `md:hidden`, // z-50) plus the iOS home-indicator inset sit over the page's lower // edge. A `sticky bottom-2` bar lands BEHIND the nav, hiding the // destructive "Delete (N)" button. Lift the bar to clear the nav band diff --git a/src/components/documents/document-detail-sheet.tsx b/src/components/documents/document-detail-sheet.tsx index b11eab8cb..303a41ce4 100644 --- a/src/components/documents/document-detail-sheet.tsx +++ b/src/components/documents/document-detail-sheet.tsx @@ -422,7 +422,7 @@ export function DocumentDetailSheet({ const linkSeedFresh = openedOn !== null && detail.dataUpdatedAt > openedOn.seenAt; - // 2026-07-17 UX/IA audit M5 / F5-5 — the document → lab-values direction + // The document → lab-values direction // had no UI link at all: an auto-staged (PENDING) or OCR-confirmed // (APPROVED, see `linkOcrLabsToVaultDocument`) OBSERVATION fact already // ties this document to specific lab rows, but nothing on the sheet said diff --git a/src/components/documents/documents-view.tsx b/src/components/documents/documents-view.tsx index 5bae07a45..43ab0612d 100644 --- a/src/components/documents/documents-view.tsx +++ b/src/components/documents/documents-view.tsx @@ -417,7 +417,7 @@ export function DocumentsView() { // persisted per user and changed behind the wrench. const { layout, isLayoutLoading } = useDocumentsLayout(moduleEnabled); - // v1.30.1 M12 — pull-to-refresh parity with labs/checkups/mood/ + // v1.30.1 — pull-to-refresh parity with labs/checkups/mood/ // measurements/medications. The vault is one of the sharpest PWA-resume- // staleness surfaces named in the audit: a document indexed server-side // while the tab was backgrounded (auto-index / AI summary) otherwise diff --git a/src/components/error-details.tsx b/src/components/error-details.tsx index 2de077ddb..8ee94ea1b 100644 --- a/src/components/error-details.tsx +++ b/src/components/error-details.tsx @@ -73,7 +73,7 @@ export function ErrorDetails({ } return ( - // v1.4.27 MB6 — `min-h-dvh flex flex-col justify-center` makes + // v1.4.27 — `min-h-dvh flex flex-col justify-center` makes // the error panel anchor in the middle of the available viewport // even when this component is mounted at the root layout. Without // it the panel hugged the top edge under the notch on diff --git a/src/components/gamification/__tests__/recent-achievements-card.test.tsx b/src/components/gamification/__tests__/recent-achievements-card.test.tsx index 66c8f70e2..631e22b67 100644 --- a/src/components/gamification/__tests__/recent-achievements-card.test.tsx +++ b/src/components/gamification/__tests__/recent-achievements-card.test.tsx @@ -5,7 +5,7 @@ import { I18nProvider } from "@/lib/i18n/context"; import type { AchievementProgress } from "@/lib/gamification/achievements"; /** - * v1.4.15 phase-B4 — `` contract. + * v1.4.15 — `` contract. * * 1. `pickRecentUnlocks` — locked entries dropped, unlocked sorted by * `completedAt` descending, capped at the requested limit. Pure. @@ -43,7 +43,7 @@ let mockData: }; let mockIsPending = false; -// v1.4.34 IW-F-Perf — the card now reads through the shared +// v1.4.34 — the card now reads through the shared // `useAchievementsQuery` hook; the test mocks the hook directly so the // network layer + TanStack provider scaffolding stays out of scope. vi.mock("@/lib/queries/use-achievements-query", () => ({ diff --git a/src/components/gamification/achievement-unlock-notifier.tsx b/src/components/gamification/achievement-unlock-notifier.tsx index db21007c7..d4f0df18d 100644 --- a/src/components/gamification/achievement-unlock-notifier.tsx +++ b/src/components/gamification/achievement-unlock-notifier.tsx @@ -70,7 +70,7 @@ export function AchievementUnlockNotifier({ const localStateReadyRef = useRef(false); const hasSnapshotRef = useRef(false); - // v1.4.34 IW-F-Perf — the notifier and `` + // v1.4.34 — the notifier and `` // both ride the shared `["gamification", "achievements"]` cache cell. // Before, the notifier carried a per-user discriminator on its key, // which made TanStack treat it as a fresh cell and fire a second diff --git a/src/components/gamification/recent-achievements-card.tsx b/src/components/gamification/recent-achievements-card.tsx index 3e7214581..2c778df45 100644 --- a/src/components/gamification/recent-achievements-card.tsx +++ b/src/components/gamification/recent-achievements-card.tsx @@ -70,7 +70,7 @@ export function pickRecentUnlocks( const RECENT_LIMIT = 3; /** - * v1.4.15 phase-B4 — small dashboard card surfacing the user's three + * v1.4.15 — small dashboard card surfacing the user's three * most-recently unlocked achievements. Empty-states with a CTA to * /achievements when nothing is unlocked yet so the user discovers the * feature exists. Visibility is controlled from Settings → Dashboard diff --git a/src/components/i18n/__tests__/maintainership-banner.test.tsx b/src/components/i18n/__tests__/maintainership-banner.test.tsx index 243344e24..e21897ad1 100644 --- a/src/components/i18n/__tests__/maintainership-banner.test.tsx +++ b/src/components/i18n/__tests__/maintainership-banner.test.tsx @@ -6,7 +6,7 @@ import { I18nProvider } from "@/lib/i18n/context"; import type { Locale } from "@/lib/i18n/config"; /** - * v1.4.25 W9e — contract: + * v1.4.25 — contract: * 1. Renders nothing on maintained locales (DE / EN). * 2. Renders a notice strip on AI-initial locales (FR / ES / IT / PL). * 3. The notice carries a GitHub-issue CTA so feedback has a clear diff --git a/src/components/i18n/maintainership-banner.tsx b/src/components/i18n/maintainership-banner.tsx index 5c957d42b..66c6eed5f 100644 --- a/src/components/i18n/maintainership-banner.tsx +++ b/src/components/i18n/maintainership-banner.tsx @@ -6,7 +6,7 @@ import { isMaintainedLocale, type Locale } from "@/lib/i18n/config"; import { useTranslations } from "@/lib/i18n/context"; /** - * v1.4.25 W9e — notice strip for AI-initial locales. + * v1.4.25 — notice strip for AI-initial locales. * * HealthLog ships German + English as maintainer-curated translations * (the maintainer owns both bodies). French, Spanish, Italian @@ -34,7 +34,7 @@ import { useTranslations } from "@/lib/i18n/context"; */ const STORAGE_PREFIX = "healthlog-i18n-banner-dismissed:"; -// v1.4.26 P6-8 — `.github/ISSUE_TEMPLATE/translation.yml` now exists. +// v1.4.26 — `.github/ISSUE_TEMPLATE/translation.yml` now exists. // GitHub matches the `?template=` query against the filename; the old // `.md` filename never resolved (the form file was missing), so the // link landed users on a blank issue body. diff --git a/src/components/illness/illness-correlation-card.tsx b/src/components/illness/illness-correlation-card.tsx index ec8fb1432..58c196889 100644 --- a/src/components/illness/illness-correlation-card.tsx +++ b/src/components/illness/illness-correlation-card.tsx @@ -1,7 +1,7 @@ "use client"; /** - * v1.18.1 P3 — the per-episode retrospective correlation card ("How it + * v1.18.1 — the per-episode retrospective correlation card ("How it * unfolded"). Server-authoritative + coverage-gated: it pattern-matches the * `Derived` `status` and renders "still learning" when the signal is thin, * NEVER a fabricated number. Retrospective only — never a prediction or a diff --git a/src/components/illness/illness-insights-card.tsx b/src/components/illness/illness-insights-card.tsx index c40d75624..64f84232b 100644 --- a/src/components/illness/illness-insights-card.tsx +++ b/src/components/illness/illness-insights-card.tsx @@ -1,7 +1,7 @@ "use client"; /** - * v1.18.1 P3 — the cross-episode retrospective summary card ("Your illness + * v1.18.1 — the cross-episode retrospective summary card ("Your illness * history"): how many episodes in the window, and — on demand — the typical * recovery gap. Retrospective only — a count of the past, never a forecast. * diff --git a/src/components/illness/illness-settings.tsx b/src/components/illness/illness-settings.tsx index db3592bb3..13698abb4 100644 --- a/src/components/illness/illness-settings.tsx +++ b/src/components/illness/illness-settings.tsx @@ -1,7 +1,7 @@ "use client"; /** - * v1.18.6 (W8 / MOD-03) — the Illness module's own settings page body. + * v1.18.6 — the Illness module's own settings page body. * * Reached from the wrench beside the page's "hinzufügen" button. Lets the * user reorder conditions/episodes + pick the card-vs-list page view. Order diff --git a/src/components/illness/illness-view.tsx b/src/components/illness/illness-view.tsx index a1a813172..b4ab4a057 100644 --- a/src/components/illness/illness-view.tsx +++ b/src/components/illness/illness-view.tsx @@ -132,7 +132,7 @@ function EpisodeCard({ const active = episode.resolvedAt === null; const isChronic = episode.lifecycle === "CHRONIC_ONGOING"; - // v1.18.6 (MOD-03) — compact list row: a single divided line per episode + // v1.18.6 — compact list row: a single divided line per episode // (label + status chips + onset; tap navigates to the detail surface). if (view === "list") { return ( @@ -291,7 +291,7 @@ function EpisodeGroup({ resolving: boolean; view: "cards" | "list"; /** - * v1.18.6 (MOD-05) — the resolved group collapses by default behind a + * v1.18.6 — the resolved group collapses by default behind a * disclosure trigger so a long recovered history never pushes the active * conditions below the fold. */ @@ -395,7 +395,7 @@ export function IllnessView() { ); return { - // v1.18.6 (MOD-03) — honour the user's persisted manual order within + // v1.18.6 — honour the user's persisted manual order within // each status group (ids absent from the order sort after the block). activeParents: applyOrder(activeParents, prefs.order, (e) => e.id), resolvedParents: applyOrder(resolvedParents, prefs.order, (e) => e.id), @@ -412,7 +412,7 @@ export function IllnessView() { description={t("illness.subtitle")} actions={ <> - {/* v1.18.6 (MOD-01) — wrench left of the primary Add, linking to the + {/* v1.18.6 — wrench left of the primary Add, linking to the Illness settings page (view + reorder). `/settings` is the caller's, not the record's, so no grant reaches it. */} {!inSharedRecord && ( @@ -431,7 +431,7 @@ export function IllnessView() { )} - {/* v1.18.6 (MOD-02) — the add button reads "hinzufügen" like every + {/* v1.18.6 — the add button reads "hinzufügen" like every other module, not the bespoke "neue Episode". */} {/* Hidden while there is no episode: the empty state below carries the add action then. */} @@ -448,7 +448,7 @@ export function IllnessView() { } /> - {/* v1.18.6 (DISC-01) — the per-page medical disclaimer line is removed; + {/* v1.18.6 — the per-page medical disclaimer line is removed; the one-time acknowledgment now lives at onboarding and the legal text stays reachable on the public privacy page. */} diff --git a/src/components/insights/__tests__/coincident-deviation-card.test.tsx b/src/components/insights/__tests__/coincident-deviation-card.test.tsx index ebc2eea90..6633d8f81 100644 --- a/src/components/insights/__tests__/coincident-deviation-card.test.tsx +++ b/src/components/insights/__tests__/coincident-deviation-card.test.tsx @@ -49,7 +49,7 @@ function ok( return { metric: "COINCIDENT_DEVIATION", status: "ok", - // v1.18.1 P4 — default the Rest Mode reframe flag off so the existing + // v1.18.1 — default the Rest Mode reframe flag off so the existing // fixtures stay valid; the reframe test passes `illnessExplained: true`. value: { illnessExplained: false, ...value }, coverage: { diff --git a/src/components/insights/__tests__/correlation-card-skeleton.test.ts b/src/components/insights/__tests__/correlation-card-skeleton.test.ts index 27e5bdee4..5b517eef3 100644 --- a/src/components/insights/__tests__/correlation-card-skeleton.test.ts +++ b/src/components/insights/__tests__/correlation-card-skeleton.test.ts @@ -3,7 +3,7 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; /** - * v1.4.43 W11 — correlation-card scatter skeleton class guard. + * v1.4.43 — correlation-card scatter skeleton class guard. * * The skeleton must mirror the loaded chart's responsive aspect- * ratio classes (`scatter-correlation-chart.tsx:100`) so the diff --git a/src/components/insights/__tests__/correlation-card.test.tsx b/src/components/insights/__tests__/correlation-card.test.tsx index 31010bf90..02689583f 100644 --- a/src/components/insights/__tests__/correlation-card.test.tsx +++ b/src/components/insights/__tests__/correlation-card.test.tsx @@ -4,7 +4,7 @@ import { I18nProvider } from "@/lib/i18n/context"; import type { CorrelationResult } from "@/lib/insights/correlations"; /** - * v1.4.20 phase B3 — `` rendering. + * v1.4.20 — `` rendering. * * Tests cover: * 1. OK status renders title + interpretation + source chip + disabled CTA. diff --git a/src/components/insights/__tests__/daily-briefing.test.tsx b/src/components/insights/__tests__/daily-briefing.test.tsx index 91e00e2a2..a279b4fcc 100644 --- a/src/components/insights/__tests__/daily-briefing.test.tsx +++ b/src/components/insights/__tests__/daily-briefing.test.tsx @@ -18,7 +18,7 @@ vi.mock("@/hooks/use-auth", () => ({ })); /** - * v1.4.20 phase B1 — full-width Daily Briefing card. + * v1.4.20 — full-width Daily Briefing card. * * The card sits below the hero strip on `/insights` and renders the * narrative paragraph + 0-5 key-finding rows produced by the AI @@ -177,7 +177,7 @@ describe("", () => { }); it("renders the 'Updated today,

- {/* v1.18.6 (CCH-01) — `` is itself a `flex h-full + {/* v1.18.6 — `` is itself a `flex h-full flex-col` with its OWN `overflow-y-auto` list, so a single bounded flex track here is all it needs; do not nest a second scroll container. */} @@ -152,7 +152,7 @@ export function CoachDrawerBody({ data-slot="coach-drawer-thread" className="flex h-full min-h-0 min-w-0 flex-col" > - {/* v1.4.27 R3d MB2 — rail-tray triggers lifted out of the + {/* v1.4.27 — rail-tray triggers lifted out of the absolute overlay into a sub-header strip so the buttons sit at a 44 px tap target and never overlay the first message bubble. diff --git a/src/components/insights/coach-panel/coach-drawer.tsx b/src/components/insights/coach-panel/coach-drawer.tsx index c80697cb3..0cb3e4327 100644 --- a/src/components/insights/coach-panel/coach-drawer.tsx +++ b/src/components/insights/coach-panel/coach-drawer.tsx @@ -60,7 +60,7 @@ export function coachMaximizeHref( } /** - * v1.4.20 phase B2b — AI Coach drawer (right-side `` overlay). + * v1.4.20 — AI Coach drawer (right-side `` overlay). * * Mounts above `/insights` so the user keeps the dashboard context * behind it. The drawer is fully-controlled by the parent (`open` / @@ -80,7 +80,7 @@ export interface CoachDrawerProps { /** Optional pre-fill for the input box (suggested-prompt chip click). */ prefill?: string | null; /** - * v1.21.0 (C4 H1/H4) — optional launch scope so a conversation opened + * v1.21.0 — optional launch scope so a conversation opened * from a metric surface or insight card is pre-narrowed to the relevant * source(s) + window. Null → the route's default all-source snapshot. */ @@ -118,7 +118,7 @@ export function CoachDrawer({ }: CoachDrawerProps) { const { t } = useTranslations(); const router = useRouter(); - // v1.4.27 R3d MB1 — below the `sm` breakpoint (640 px) the Coach + // v1.4.27 — below the `sm` breakpoint (640 px) the Coach // drawer slides up from the bottom edge of the viewport instead of // sliding in from the right. Right-side slide makes the back-arrow // / close-X drift far from the user's thumb on a phone; bottom-up @@ -188,11 +188,11 @@ export function CoachDrawer({ return ( ` wrapped in a Dracula-styled card with a send button. * Submit fires on: @@ -41,7 +41,7 @@ import { COACH_COMPOSER_ID } from "./composer-focus"; * the drawer. `disabled` flips during a streamed reply so we never * fire two requests in parallel. * - * v1.4.22 B4: the disclaimer ("Coach replies are generated …") moved + * v1.4.22: the disclaimer ("Coach replies are generated …") moved * out of the composer and into the sources rail footer. DISC-01 * (v1.18.6) later dropped that per-page copy too, in favour of the * one-time onboarding acknowledgment; the only in-surface "not medical @@ -75,7 +75,7 @@ export interface CoachInputProps { /** Optional textarea ref so the parent can focus on drawer open. */ inputId?: string; /** - * v1.4.27 MB3 / CF-30 — when set, focuses the composer textarea once + * v1.4.27 — when set, focuses the composer textarea once * on mount. The drawer flips this to `true` when the drawer becomes * the freshly-opened surface so the user can start typing * immediately. It must stay opt-in (lazy): on every re-render of an @@ -110,7 +110,7 @@ export interface CoachInputProps { } /** - * v1.4.25 W5 — Claude-web-style auto-grow. + * v1.4.25 — Claude-web-style auto-grow. * * The composer textarea starts at 1 line (≈44 px including padding, * matching the disclaimer text height on the sources rail) and grows @@ -162,7 +162,7 @@ export function CoachInput({ const textareaRef = useRef(null); const attachFileInputRef = useRef(null); - // v1.4.27 MB3 / CF-30 — focus the textarea once on mount when the + // v1.4.27 — focus the textarea once on mount when the // parent flags this surface as the freshly-opened one. The empty // dependency array keeps this from re-firing on every value change // (the auto-grow effect handles those). We bail when disabled so we @@ -180,7 +180,7 @@ export function CoachInput({ // eslint-disable-next-line react-hooks/exhaustive-deps -- one-shot on mount }, []); - // v1.4.25 W5 — auto-grow side effect. Runs on every value change + // v1.4.25 — auto-grow side effect. Runs on every value change // (the controlled `value` prop is the source of truth) plus on mount // so the initial paint already shows the 1-line height even when the // value arrives prefilled. We reset `style.height` to "auto" first so diff --git a/src/components/insights/coach-panel/message-thread.tsx b/src/components/insights/coach-panel/message-thread.tsx index 9cfbbe53a..e274a11ef 100644 --- a/src/components/insights/coach-panel/message-thread.tsx +++ b/src/components/insights/coach-panel/message-thread.tsx @@ -54,7 +54,7 @@ export { } from "./chat-bubble"; /** - * v1.4.20 phase B2b — message-thread renderer. + * v1.4.20 — message-thread renderer. * * Mounted inside the centre column of the Coach drawer. Renders the * persisted message history (decrypted server-side and delivered as @@ -78,7 +78,7 @@ export interface MessageThreadProps { /** Optional in-flight bubble from `useSendCoachMessage()`. */ streaming?: CoachStreamingMessage; /** - * v1.4.25 W5 — optimistic user message surfaced by the send hook so + * v1.4.25 — optimistic user message surfaced by the send hook so * the user sees their own bubble before the "Thinking…" placeholder. * Cleared by the hook once the SSE `done` frame fires (the persisted * twin lands via the invalidate-refetch). When the persisted twin is @@ -456,7 +456,7 @@ export function MessageThread({ !streamingPersisted && (!!streaming?.inProgress || !!streaming?.content || !!streaming?.errorCode); - // v1.4.25 W5 — render the optimistic user bubble only when the + // v1.4.25 — render the optimistic user bubble only when the // persisted twin hasn't landed yet. We match on (role=user, content // equality, no later persisted user message). The server is the // source of truth — once the persisted user message lands (via the @@ -583,7 +583,7 @@ export function MessageThread({ const el = scrollerRef.current; if (!el) return; if (wasPinnedRef.current) { - // v1.4.43 W5-H5 — respect `prefers-reduced-motion`. + // v1.4.43 — respect `prefers-reduced-motion`. el.scrollTo({ top: el.scrollHeight, behavior: scrollBehaviorForUser() }); } }, [ @@ -593,7 +593,7 @@ export function MessageThread({ interleavedItems.length, ]); - // v1.4.27 R3d MB4 / CF-74 — re-pin to the bottom when the + // v1.4.27 — re-pin to the bottom when the // visual viewport shrinks (typically the soft keyboard opening on // a phone). Without this the last bubble drifts behind the keyboard // because the scroller's `scrollHeight` references the layout @@ -656,7 +656,7 @@ export function MessageThread({ // bottom breathing room so the conversation reads like a document, // not a chat log packed against the chrome. "flex min-h-0 flex-1 flex-col gap-6 overflow-y-auto px-4 py-6 sm:px-6", - // v1.18.6 (CCH-01) / v1.18.7 — on the wide page surface an edge-to- + // v1.18.6 v1.18.7 — on the wide page surface an edge-to- // edge thread sprawled the prose past a readable measure. Centre the // content on a narrower, calmer Claude/ChatGPT-like column // (`max-w-2xl`) via an `mx-auto` inner gutter; the scrollbar still @@ -743,7 +743,7 @@ export function MessageThread({ ...placement.beforeOptimistic.map((i) => ( {i.node} )), - // v1.4.25 W5 — the optimistic user bubble sits between the + // v1.4.25 — the optimistic user bubble sits between the // persisted history and the streaming assistant placeholder, so // the visible order matches the person's mental model. The send // hook drops it once the persisted twin lands. diff --git a/src/components/insights/coach-panel/mobile-rail-tray.tsx b/src/components/insights/coach-panel/mobile-rail-tray.tsx index e88dba14e..2dcf10075 100644 --- a/src/components/insights/coach-panel/mobile-rail-tray.tsx +++ b/src/components/insights/coach-panel/mobile-rail-tray.tsx @@ -11,14 +11,14 @@ import { import { useTranslations } from "@/lib/i18n/context"; /** - * v1.4.20 phase B4 — mobile-only rail trays for the Coach drawer. + * v1.4.20 — mobile-only rail trays for the Coach drawer. * * The desktop layout pins the history rail (lg+) and the sources rail * (xl+) alongside the message thread. Below those breakpoints the * rails are reachable via chevron triggers in the thread's sub-header * strip; each trigger opens the matching side-sheet here. * - * v1.4.28 R3c (BK-F-M6) — carved out of `` (~560 LOC). + * v1.4.28 — carved out of `` (~560 LOC). * The parent still owns the rail content + the open/closed state for * each tray so this component stays a thin presentational shell that * can be unit-tested in isolation without standing up the drawer's diff --git a/src/components/insights/coach-panel/read-aloud.tsx b/src/components/insights/coach-panel/read-aloud.tsx index 948bd91b0..b27a5b485 100644 --- a/src/components/insights/coach-panel/read-aloud.tsx +++ b/src/components/insights/coach-panel/read-aloud.tsx @@ -23,7 +23,7 @@ import { stripChartTokens } from "@/lib/insights/chart-tokens"; */ /** - * v1.22 (W5) — feature-detect the browser Speech Synthesis API SSR-safe. + * v1.22 — feature-detect the browser Speech Synthesis API SSR-safe. * `useSyncExternalStore` returns false on the server + first client paint * (so the read-aloud button is absent in markup) and resolves to the real * capability after hydration, mirroring the mic button's detection pattern @@ -126,7 +126,7 @@ export function pickSpeechVoice( } /** - * v1.22 (W5) — read the assistant prose aloud via the browser's + * v1.22 — read the assistant prose aloud via the browser's * `SpeechSynthesis`. Client-only, no dependency, no egress, CSP-clean. A * single in-flight utterance: a new `speak` cancels any prior one; `toggle` * stops while speaking. @@ -238,7 +238,7 @@ export const COACH_ICON_BUTTON = cn( ); /** - * v1.22 (W5) — read-aloud toggle for a settled assistant turn. Hidden when + * v1.22 — read-aloud toggle for a settled assistant turn. Hidden when * the browser has no Speech Synthesis support. Speaks `stripChartTokens` * (the same text the prose shows), so stray tokens are never voiced. */ diff --git a/src/components/insights/coach-panel/reminder-suggestion-card.tsx b/src/components/insights/coach-panel/reminder-suggestion-card.tsx index 9344c5868..383226eb6 100644 --- a/src/components/insights/coach-panel/reminder-suggestion-card.tsx +++ b/src/components/insights/coach-panel/reminder-suggestion-card.tsx @@ -12,7 +12,7 @@ import { invalidateReminderReads } from "@/hooks/use-measurement-reminders"; import type { CoachSuggestion } from "@/lib/ai/coach/types"; /** - * v1.18.1 (Workstream C) — the one-tap cadence-suggestion action card. + * v1.18.1 — the one-tap cadence-suggestion action card. * * Rendered under an assistant bubble when the turn carried a `suggestion` * (live from the SSE frame, or persisted on the message provenance). Three diff --git a/src/components/insights/coach-panel/sources-rail.tsx b/src/components/insights/coach-panel/sources-rail.tsx index f53812d38..fb4b825da 100644 --- a/src/components/insights/coach-panel/sources-rail.tsx +++ b/src/components/insights/coach-panel/sources-rail.tsx @@ -61,7 +61,7 @@ const WINDOW_OPTIONS = COACH_HISTORY_WINDOWS; export interface SourcesRailProps { className?: string; /** - * v1.21.2 (A2) — the human label of the metric the current conversation + * v1.21.2 — the human label of the metric the current conversation * was launched scoped to, or null for a generic (all-source) open. When * set, the rail surfaces a small "the Coach is already on " line * above the persisted-cluster toggles so the active narrowing is VISIBLE @@ -141,7 +141,7 @@ export function SourcesRail({ className, activeScopeLabel }: SourcesRailProps) { {t("insights.coach.sourcesTitle")} - {/* v1.21.2 (A2) — the visible launch scope for this conversation. Only + {/* v1.21.2 — the visible launch scope for this conversation. Only rendered when the Coach was opened narrowed to a metric; a generic open omits it entirely so the rail is unchanged for the default all-source case. */} diff --git a/src/components/insights/coach-panel/streamed-prose.tsx b/src/components/insights/coach-panel/streamed-prose.tsx index 2ea1be8d6..ecb79817d 100644 --- a/src/components/insights/coach-panel/streamed-prose.tsx +++ b/src/components/insights/coach-panel/streamed-prose.tsx @@ -21,7 +21,7 @@ import { * fades + drifts up on mount, giving the reply a "thinking out loud" * cadence rather than a hard dump of word groups. * - * v1.22 (W5) — paragraph-aware + SSE-safe. The reply now renders as real + * v1.22 — paragraph-aware + SSE-safe. The reply now renders as real * `

` blocks (shared `ProseBlocks`), so a model that emits blank-line * paragraph breaks reads as paragraphs, not one run-on block — consistent * with the daily briefing + insight cards. Under streaming, every COMPLETE diff --git a/src/components/insights/coach-panel/suggested-action-card.tsx b/src/components/insights/coach-panel/suggested-action-card.tsx index 9e528a079..6904289b7 100644 --- a/src/components/insights/coach-panel/suggested-action-card.tsx +++ b/src/components/insights/coach-panel/suggested-action-card.tsx @@ -13,7 +13,7 @@ import { invalidateReminderReads } from "@/hooks/use-measurement-reminders"; import type { CoachSuggestedAction } from "@/lib/ai/coach/suggest-action"; /** - * v1.22 (F6) — the generalised one-tap "confirm → apply" Coach action card. + * v1.22 — the generalised one-tap "confirm → apply" Coach action card. * * Rendered under an assistant bubble when the turn carried a `suggestedAction` * (live from the SSE frame, or persisted on the message provenance). The card diff --git a/src/components/insights/coach-panel/use-coach.ts b/src/components/insights/coach-panel/use-coach.ts index bbd356714..71139164b 100644 --- a/src/components/insights/coach-panel/use-coach.ts +++ b/src/components/insights/coach-panel/use-coach.ts @@ -55,7 +55,7 @@ import { import { useAuth } from "@/hooks/use-auth"; /** - * v1.4.20 phase B2b — TanStack Query + SSE client for the AI Coach + * v1.4.20 — TanStack Query + SSE client for the AI Coach * drawer. * * The shape mirrors the backend wire format from `phase-B2a-report.md`: @@ -221,7 +221,7 @@ export interface UseCoachConversationHistoryOptions { } /** - * v1.30.2 (QoL H1) — the full, paginated + server-searched conversation + * v1.30.2 — the full, paginated + server-searched conversation * history. Replaces the old "first page only, client-side substring * filter" behaviour: `useInfiniteQuery` walks the cursor chain * (`nextCursor`) so every conversation the caller has ever had is @@ -360,7 +360,7 @@ export function deleteCoachConversationRequest(id: string): Promise { }); } -// v1.30.1 M5 — delayed-commit delete window, in milliseconds. Long +// v1.30.1 — delayed-commit delete window, in milliseconds. Long // enough to read + tap Undo, short enough that the eventual DELETE // isn't a surprise days later. export const CONVERSATION_DELETE_UNDO_MS = 6000; @@ -484,7 +484,7 @@ export function commitCoachConversationDelete( } /** - * v1.30.1 M5 — one tap hides the row and schedules the real + * v1.30.1 — one tap hides the row and schedules the real * `DELETE /api/insights/chat/[id]` after `CONVERSATION_DELETE_UNDO_MS`; * `undoDelete` within that window cancels it. `pendingDeleteIds` is the * hide filter every surface subtracts from what it renders. @@ -712,12 +712,12 @@ export interface CoachStreamingMessage { /** Provenance frame — populated once the server emits it. */ metricSource: CoachProvenance | null; /** - * v1.18.1 (Workstream C) — cadence suggestion from the additive + * v1.18.1 — cadence suggestion from the additive * `suggestion` frame, if the turn carried one. Null otherwise. */ suggestion: CoachSuggestion | null; /** - * v1.22 (W7/W6) — generalised confirm-card action from the additive + * v1.22 — generalised confirm-card action from the additive * `suggestedAction` frame (mirrors `suggestion`). Persisted messages carry it * on `metricSource.suggestedAction` instead. Null otherwise. */ @@ -765,7 +765,7 @@ export interface CoachStreamingMessage { } /** - * v1.4.25 W5 — optimistic user bubble surfaced by the send hook so the + * v1.4.25 — optimistic user bubble surfaced by the send hook so the * thread paints the user's message immediately, before the assistant's * "Thinking…" placeholder. The server persists the user message before * the stream starts (see `appendMessage` in @@ -1042,7 +1042,7 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) { const queryClient = useQueryClient(); const [streaming, setStreaming] = useState(EMPTY_STREAMING); - // v1.4.25 W5 — optimistic user bubble. Mounts the user's message in + // v1.4.25 — optimistic user bubble. Mounts the user's message in // the thread immediately so the chronological render order matches // the user's mental model (send → my message → coach "Thinking…" → // reply). Cleared once the persisted twin lands via the invalidate- @@ -1136,7 +1136,7 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) { }; activeSendRef.current = sendMarker; try { - // v1.4.25 W5 — surface the user's message in the thread BEFORE + // v1.4.25 — surface the user's message in the thread BEFORE // the "Thinking…" placeholder paints. Without this the send hook // only exposed the streaming assistant bubble, so the first // thing the user saw was the placeholder followed by their own @@ -1149,7 +1149,7 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) { }); setStreaming({ ...EMPTY_STREAMING, inProgress: true }); - // v1.4.47 W8 — pre-check navigator.onLine so an airplane-mode + // v1.4.47 — pre-check navigator.onLine so an airplane-mode // user gets the offline-specific copy immediately rather than // waiting for the fetch to fail with a generic network error. if (typeof navigator !== "undefined" && navigator.onLine === false) { @@ -1205,7 +1205,7 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) { .toLowerCase() .startsWith("text/event-stream"); if (!response.ok && !isEventStream) { - // v1.4.25 W5 — for JSON errors the apiHandler envelope carries + // v1.4.25 — for JSON errors the apiHandler envelope carries // a structured `error` code (e.g. `coach.budget.exceeded`). // Surface the structured code directly so the drawer can show // the right copy + toast variant; fall back to the generic @@ -1442,7 +1442,7 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) { } optsRef.current.onDone?.(resolvedConversationId); } - // v1.4.25 W5 — drop the optimistic user bubble; the persisted + // v1.4.25 — drop the optimistic user bubble; the persisted // twin is on its way via the invalidate-refetch above. // v1.16.4 — but only when a twin IS coming (the turn resolved a // conversation) or the turn succeeded outright. An errored turn diff --git a/src/components/insights/coach-panel/use-resettable-value.ts b/src/components/insights/coach-panel/use-resettable-value.ts index 04904f95b..c14ff2636 100644 --- a/src/components/insights/coach-panel/use-resettable-value.ts +++ b/src/components/insights/coach-panel/use-resettable-value.ts @@ -4,7 +4,7 @@ import { useState } from "react"; import type { Dispatch, SetStateAction } from "react"; /** - * v1.4.23 H3 — local state seeded from a controlled prop, reset on + * v1.4.23 — local state seeded from a controlled prop, reset on * prop change. Same render-phase pattern React's docs recommend in * place of `useEffect(() => setState(prop), [prop])` (which the * `react-hooks/set-state-in-effect` ESLint rule banned). The reset @@ -39,7 +39,7 @@ export function useResettableValue( } /** - * v1.4.23 H3 — pure decision function behind `useResettableValue`. + * v1.4.23 — pure decision function behind `useResettableValue`. * Given the previous controlled value the hook recorded and the * incoming controlled value, returns either `{ reset: true, value }` * (the next render must seed local state with `value`) or diff --git a/src/components/insights/coincident-deviation-card.tsx b/src/components/insights/coincident-deviation-card.tsx index 3cdf69ec5..d2e56a9d8 100644 --- a/src/components/insights/coincident-deviation-card.tsx +++ b/src/components/insights/coincident-deviation-card.tsx @@ -299,7 +299,7 @@ export function CoincidentDeviationCard({ className="text-muted-foreground text-xs leading-snug" data-slot="coincident-factors" > - {/* v1.18.1 P4 — Rest Mode reframe. When an illness episode is active + {/* v1.18.1 — Rest Mode reframe. When an illness episode is active the deviations have a known explanation, so the card states it plainly instead of leaving "possible factors" open. Neutral copy, no extra colour. */} diff --git a/src/components/insights/confidence-badge.ts b/src/components/insights/confidence-badge.ts index c0cc7e819..6a9b275c4 100644 --- a/src/components/insights/confidence-badge.ts +++ b/src/components/insights/confidence-badge.ts @@ -1,5 +1,5 @@ /** - * v1.4.20 phase D reconcile — shared confidence-band classnames. + * v1.4.20 — shared confidence-band classnames. * * Both `` and `` paint a discrete * "low / moderate / high" badge with byte-identical semantic utility diff --git a/src/components/insights/correlation-card.tsx b/src/components/insights/correlation-card.tsx index def699aea..da3ba4eb6 100644 --- a/src/components/insights/correlation-card.tsx +++ b/src/components/insights/correlation-card.tsx @@ -21,7 +21,7 @@ import { } from "./pattern-dismiss-action"; /** - * v1.4.20 phase B3 — single Correlation card. + * v1.4.20 — single Correlation card. * * Each card surfaces ONE pre-defined hypothesis (BP × compliance, * mood × pulse, weight × weekday). Below-threshold results render an @@ -40,7 +40,7 @@ const ScatterCorrelationChart = dynamic( { ssr: false, loading: () => ( - // v1.4.43 W11 — mirror the loaded chart's responsive aspect- + // v1.4.43 — mirror the loaded chart's responsive aspect- // ratio classes (`scatter-correlation-chart.tsx:100`) so the // skeleton reserves the same space across breakpoints. The // legacy `h-[180px]` was ~60 px shorter than the painted chart @@ -79,7 +79,7 @@ const CONFIDENCE_LABEL_KEY: Record<"low" | "moderate" | "high", string> = { low: "insights.correlationRow.confidenceLow", }; -// v1.21.0 (C4 H2) — the two sources each correlation pair spans, so the +// v1.21.0 — the two sources each correlation pair spans, so the // Coach hand-off narrows its snapshot to exactly the relationship the // card describes. const COACH_SOURCES_BY_KIND: Record< @@ -91,7 +91,7 @@ const COACH_SOURCES_BY_KIND: Record< "weight-weekday": ["weight"], }; -// v1.22 (W6) — i18n keys (resolved at the call site), not hardcoded English. +// v1.22 — i18n keys (resolved at the call site), not hardcoded English. const COACH_QUESTION_BY_KIND: Record = { "bp-compliance": "insights.coach.seed.correlationBpCompliance", "mood-pulse": "insights.coach.seed.correlationMoodPulse", @@ -199,7 +199,7 @@ export function CorrelationCard({ result }: CorrelationCardProps) {

{/* Hero: ring + caption — a centred identity block below the header. */}
- {/* v1.15.12 F2 — vertical breathing room so the ring's bloom/glow is + {/* v1.15.12 — vertical breathing room so the ring's bloom/glow is never clipped top/bottom by the hero container. */} {!insufficient || showProvisionalRing ? (
diff --git a/src/components/insights/derived/vitals-dashboard.tsx b/src/components/insights/derived/vitals-dashboard.tsx index 265c99814..e86fe7efd 100644 --- a/src/components/insights/derived/vitals-dashboard.tsx +++ b/src/components/insights/derived/vitals-dashboard.tsx @@ -72,7 +72,7 @@ interface DashboardProps { */ batch: DashboardDerived; /** - * v1.15.11 W2c — the resolved insights layout, passed down from the page + * v1.15.11 — the resolved insights layout, passed down from the page * (which already mounts `useInsightsLayoutQuery()`) so this grid honours the * user's per-tile order + visibility without a second fetch. Each grid tile * maps to a layout tile id (see `VITAL_TILE_LAYOUT_ID` / @@ -87,7 +87,7 @@ interface DashboardProps { } /** - * v1.15.11 W2c — the dashboard's vitals-grid tile concepts mapped onto their + * v1.15.11 — the dashboard's vitals-grid tile concepts mapped onto their * layout tile id (the routed sub-page slug). The four derived re-frames map to * the slug of the metric they re-frame; the per-vital baseline tiles map by * their `MeasurementType`. A tile concept absent from this map (none today) @@ -124,7 +124,7 @@ const CUMULATIVE_TILE_LAYOUT_ID: Record = { }; /** - * v1.15.11 W2c — `true` when the layout marks this tile id visible (or the + * v1.15.11 — `true` when the layout marks this tile id visible (or the * layout does not enumerate the id, in which case the tile is always-on). A * `null` layout (caller passed none) is treated as everything-visible so the * default behaviour is unchanged. @@ -138,7 +138,7 @@ function tileVisible( } /** - * v1.15.11 W2c — the layout `order` for a tile id, used to sort the grid. + * v1.15.11 — the layout `order` for a tile id, used to sort the grid. * Falls back to `MAX_SAFE_INTEGER` (render last) for an unenumerated id and to * a stable 0 when no layout is present (callers keep their source order). */ @@ -773,7 +773,7 @@ export function VitalsDashboard({ batch, layout, className }: DashboardProps) { const refetch = batch.refetch; const tileProps: TileProps = { read, isLoading }; - // v1.15.11 W2c — the vitals-grid tiles in resolved layout order, dropping + // v1.15.11 — the vitals-grid tiles in resolved layout order, dropping // any the user has hidden. Each tile is `{ id, order, node }`; the tile's // own component still self-gates on data, so a visible tile with no readings // still renders nothing. The four derived re-frames map to the slug of the diff --git a/src/components/insights/ecg-cross-link.tsx b/src/components/insights/ecg-cross-link.tsx index 7121f87c8..15174c2a7 100644 --- a/src/components/insights/ecg-cross-link.tsx +++ b/src/components/insights/ecg-cross-link.tsx @@ -12,7 +12,7 @@ import { cn } from "@/lib/utils"; import { TileHeader } from "@/components/insights/tile-header"; /** - * S10 — ECG cross-link, the pointer from the resting-HR / pulse context into + * ECG cross-link, the pointer from the resting-HR / pulse context into * the ECG viewer (`/insights/ecg`). Points at the routed sub-page, which * always mounts, rather than the overview `#ecg` fragment, which is absent * until the overview section's own fetch resolves (UX-flows finding F1-1). diff --git a/src/components/insights/health-score-card.tsx b/src/components/insights/health-score-card.tsx index 0cb27e7f8..ffd2f3c5b 100644 --- a/src/components/insights/health-score-card.tsx +++ b/src/components/insights/health-score-card.tsx @@ -129,7 +129,7 @@ const PANEL_CHROME_CLASS = const PANEL_RESERVE_CLASS = "min-h-[28rem]"; /** - * v1.21.2 (A5) — readiness contributor keys the Tension Verdict surfaces. + * v1.21.2 — readiness contributor keys the Tension Verdict surfaces. * Kept as a local string union so the client never imports the server * derived-engine types. Localised through the existing * `insights.derived.composite.READINESS.component.{key}` labels. @@ -138,7 +138,7 @@ export type ReadinessContributorKey = "rhr" | "hrv" | "sleep" | "respiratory" | "mood"; /** - * v1.21.2 (A5) — readiness contributor key → existing localised label key. + * v1.21.2 — readiness contributor key → existing localised label key. * Reuses the score-anatomy contributor labels so the Tension Verdict reads the * same names the wellness ring's anatomy view renders — no new keys. */ @@ -151,7 +151,7 @@ const TENSION_CONTRIBUTOR_LABEL_KEY: Record = { }; /** - * v1.21.2 (A6) — `MeasurementType` → existing localised metric-name key + * v1.21.2 — `MeasurementType` → existing localised metric-name key * (`measurements.type*`). Only the salient deviation vitals the * return-to-baseline detector scans need an entry; an unmapped type falls back * to a prettified raw form so a new type never blanks. @@ -243,7 +243,7 @@ function reasonOf(report: HealthScoreReport, id: string): string { export interface HealthScoreCardProps { report: HealthScoreReport; /** - * v1.21.2 (A5) — Tension Verdict, server-resolved + locale-agnostic. `band` + * v1.21.2 — Tension Verdict, server-resolved + locale-agnostic. `band` * is the readiness composite's band; `positive` / `negative` carry the * readiness contributor KEYS. Null on a coherent day (or under a clinical * red-flag suppress). @@ -254,7 +254,7 @@ export interface HealthScoreCardProps { negative: ReadinessContributorKey[]; } | null; /** - * v1.21.2 (A6) — return-to-baseline, server-resolved + locale-agnostic. + * v1.21.2 — return-to-baseline, server-resolved + locale-agnostic. * `metricType` is a `MeasurementType` this card maps to its localised metric * name. Null when no salient metric returned from a genuine prior * out-of-band run. diff --git a/src/components/insights/healthkit-metric-page.tsx b/src/components/insights/healthkit-metric-page.tsx index c7c7ac737..0a595e3d9 100644 --- a/src/components/insights/healthkit-metric-page.tsx +++ b/src/components/insights/healthkit-metric-page.tsx @@ -167,7 +167,7 @@ export interface HealthKitMetricPageProps { /** Interpolation values for the explainer sentence (`SubPageShell`). */ explainerParams?: Record; /** - * v1.8.5 W5 — when set, renders `` beneath + * v1.8.5 — when set, renders `` beneath * the chart. Used by blood glucose, whose per-context ADA / DDG bands * live on the targets wire but whose page rides this generic scaffold * rather than a bespoke module. Omit it for metrics without a target. @@ -212,7 +212,7 @@ export interface HealthKitMetricPageProps { */ afterChart?: ReactNode; /** - * v1.30 (UX/IA audit H1) — optional content rendered at the very foot of the + * v1.30 — optional content rendered at the very foot of the * data-bearing spine, AFTER the metric-status assessment card. The * resting-pulse + HRV pages mount `` here so the ECG pointer * trails the assessment, matching the placement it has on `/insights/pulse`. @@ -535,7 +535,7 @@ export function HealthKitMetricPage({ enabled={!isEmpty} /> ) : null} - {/* v1.30 (H1) — foot-of-spine slot, after the assessment card. The + {/* v1.30 — foot-of-spine slot, after the assessment card. The resting-pulse + HRV pages mount the ECG cross-link here. */} {afterAssessment} diff --git a/src/components/insights/hero-strip.tsx b/src/components/insights/hero-strip.tsx index eacaf9b5c..974f2b535 100644 --- a/src/components/insights/hero-strip.tsx +++ b/src/components/insights/hero-strip.tsx @@ -178,7 +178,7 @@ export function HeroStrip({ {greeting}
- {/* v1.22 (W6) — the briefing paragraph renders through ProseBlocks + {/* v1.22 — the briefing paragraph renders through ProseBlocks so a multi-paragraph verdict-first briefing shows real paragraph spacing (still plain text children, no markdown). */}
` drives its icon off a component, so wrap * the node in a thin component that renders it. * - * v1.12.7 (M2) — `` owns the canonical `h-5 w-5 text-foreground` + * v1.12.7 — `` owns the canonical `h-5 w-5 text-foreground` * sizing and passes it through as `className` to its icon component. The * previous wrapper rendered the node verbatim and dropped that class, so a * caller passing an unsized (or mis-sized) node could drift off the contract. @@ -68,7 +68,7 @@ interface InsightStatusCardProps { */ assessment?: AssessmentStatus; /** - * v1.21.0 (C4 H2) — opt-in "Ask the Coach about this assessment" hand-off. + * v1.21.0 — opt-in "Ask the Coach about this assessment" hand-off. * When `coachQuestion` is set (the metric-aware caller supplies it), the * populated card renders a discreet Coach action seeded with that opener * and narrowed to `coachScope` when known. Callers that omit it keep the @@ -237,7 +237,7 @@ export function InsightStatusCard({ } return ( - // v1.4.38 W-D P2-2 — `aria-live="polite"` so screen readers + // v1.4.38 — `aria-live="polite"` so screen readers // announce the assessment when it swaps into the same slot the // structured skeleton occupied. The skeleton card already carries // `aria-busy="true" aria-live="polite"`; mirroring the live region @@ -297,7 +297,7 @@ export function InsightStatusCard({ } /** - * v1.4.27 MB7 / CF-39 — collapsible status copy. The assessment text + * v1.4.27 — collapsible status copy. The assessment text * routinely lands at 3-5 paragraphs of dense narrative which dominates * the surface on phones. The default collapses to 3 lines via * `line-clamp-3`; a "Show more" toggle reveals the full body. @@ -334,7 +334,7 @@ function LastUpdatedFooter({ updatedAt }: { updatedAt: string | null }) { // v1.11.5 — right-aligned so the timestamp tucks to the trailing edge // of the card for a tidier read against the left-aligned prose above. // - // v1.22 (W6) — the freshness caption is the calendar-bucketed + // v1.22 — the freshness caption is the calendar-bucketed // `formatUpdatedLabel` ("Updated today, 14:30" / "yesterday" / "on DD.MM."), // matching the briefing + per-metric cards. The day boundary follows the // user's profile timezone, not the browser's. diff --git a/src/components/insights/insights-edit-mode.tsx b/src/components/insights/insights-edit-mode.tsx index ecb985c3b..dec3df828 100644 --- a/src/components/insights/insights-edit-mode.tsx +++ b/src/components/insights/insights-edit-mode.tsx @@ -61,7 +61,7 @@ import { } from "@/lib/api/optimistic-token"; /** - * v1.15.11 W3 — inline "Anpassen" edit mode for the customizable Insights + * v1.15.11 — inline "Anpassen" edit mode for the customizable Insights * overview. * * Renders LIGHTWEIGHT edit cards (drag handle + localized title + eye toggle) @@ -143,7 +143,7 @@ export function InsightsEditMode({ update: (sections: InsightsSectionConfig[]) => InsightsSectionConfig[], ) => setDraftState((d) => ({ ...d, sections: update(d.sections) })); - // v1.15.11 QA L5 — on mount move focus to the edit-card heading so keyboard / + // v1.15.11 — on mount move focus to the edit-card heading so keyboard / // screen-reader users land on the surface they just opened (not the top of // the document). Inline, not modal, so no focus trap — focus returns to the // "Anpassen" toggle on close via the page's onClose handler. @@ -162,7 +162,7 @@ export function InsightsEditMode({ const saveMutation = useMutation({ mutationFn: async (sections: InsightsSectionConfig[]) => { - // v1.32.21 (R5a) — echo the optimistic-concurrency base token this edit + // v1.32.21 — echo the optimistic-concurrency base token this edit // was based on so an interleaved write (a Settings pill-order Save, or // this surface open in another tab) 409s instead of clobbering. return apiPut( @@ -185,7 +185,7 @@ export function InsightsEditMode({ onClose(); }, onError: (err) => { - // v1.32.21 (R5a) — explicit-Save disposition: a 409 means someone else + // v1.32.21 — explicit-Save disposition: a 409 means someone else // committed since this edit was based. Refetch so the token advances, // KEEP the draft (stay in edit mode) so the user can re-save, and nudge // gently — nothing was lost. @@ -490,7 +490,7 @@ function SortableSectionRow({ {title} - {/* v1.15.11 QA M1-design — the gated hint reads as a caption BELOW the + {/* v1.15.11 — the gated hint reads as a caption BELOW the title so the row's right-edge control position stays stable whether the section is gated or not. */} {gatedOff && ( @@ -502,7 +502,7 @@ function SortableSectionRow({ )}
- {/* v1.15.11 QA M1-design — a gated section keeps a DISABLED eye toggle in + {/* v1.15.11 — a gated section keeps a DISABLED eye toggle in the SAME position rather than swapping it for a text span, so the row layout never jumps. The section stays reorderable; only the toggle is inert until the gate opens. */} diff --git a/src/components/insights/insights-layout-shell.tsx b/src/components/insights/insights-layout-shell.tsx index 9baeb1c2e..4414792c7 100644 --- a/src/components/insights/insights-layout-shell.tsx +++ b/src/components/insights/insights-layout-shell.tsx @@ -20,7 +20,7 @@ import type { InsightInputs } from "@/lib/insights/metric-availability"; import { apiGet } from "@/lib/api/api-fetch"; /** - * v1.4.25 W4 — client shell for `src/app/insights/layout.tsx`. + * v1.4.25 — client shell for `src/app/insights/layout.tsx`. * * Owns the advisor query so the regenerate button on the sticky tab * strip works from every Insights surface (mother + the seven sub- @@ -29,7 +29,7 @@ import { apiGet } from "@/lib/api/api-fetch"; * `queryKeys.insightsAdvisor()` key, so the two consumers reuse the * same cache entry without extra network traffic. * - * v1.4.27 F19 — also owns the analytics + comprehensive reads that + * v1.4.27 — also owns the analytics + comprehensive reads that * power the tab-strip availability gate. The analytics consumer goes * through the shared `useAnalyticsQuery({ slice: "summaries" })` hook * (v1.4.33 IW2); the comprehensive consumer still owns its bespoke @@ -112,7 +112,7 @@ export function InsightsLayoutShell({ children }: { children: ReactNode }) { // degrades from "enable" → "no rows yet" → the three-card spine. const nutrientsEnabled = user?.modules?.nutrients === true; - // v1.30 (UX/IA audit H1) — ECG recording presence for the Heart-group ECG + // v1.30 — ECG recording presence for the Heart-group ECG // pill. ECG carries no `MeasurementType`, so the metric `availability` // model can't gate it; it rides its own `hasRecordings` probe instead. The // query shares `queryKeys.insightsEcgList()` with `` and the @@ -150,7 +150,7 @@ export function InsightsLayoutShell({ children }: { children: ReactNode }) { hasNutrients, ]); - // v1.15.14 W2 — couple the tab strip to the saved insights layout. + // v1.15.14 — couple the tab strip to the saved insights layout. // A sub-page pill now shows iff its metric has data AND its slug is // layout-`visible`; the strip orders pills by the saved `order`. We // pass the derived visibility set + order map only once the GET has diff --git a/src/components/insights/insights-tab-strip.tsx b/src/components/insights/insights-tab-strip.tsx index 7719277d0..9c873a1c5 100644 --- a/src/components/insights/insights-tab-strip.tsx +++ b/src/components/insights/insights-tab-strip.tsx @@ -41,7 +41,7 @@ import type { ModuleKey } from "@/lib/modules/registry"; import { isSurfaceVisible } from "@/lib/modules/surface"; /** - * v1.4.25 W4 — routed tab strip for `/insights`. + * v1.4.25 — routed tab strip for `/insights`. * * Behaviour evolved from the v1.4.25 W3 scroll-anchor version: each pill * is now a `` to a routed sub-page (`/insights/blood-pressure` …), @@ -107,14 +107,14 @@ export interface InsightsTabStripProps { */ regenerateSettling?: boolean; /** - * v1.4.27 F19 — analytics + event-driven availability inputs the + * v1.4.27 — analytics + event-driven availability inputs the * gating helper reads. When omitted the strip falls back to its * pre-v1.4.27 behaviour (every pill renders) so legacy mounts * stay backward-compatible. */ availability?: InsightInputs; /** - * v1.15.14 W2 — the set of sub-page slugs the user's saved insights + * v1.15.14 — the set of sub-page slugs the user's saved insights * layout marks `visible`. A pill shows iff its metric has data AND its * slug is in this set, so hiding a sub-page in "Anpassen" removes its * nav pill. `undefined` (layout not yet loaded) falls back to the @@ -123,7 +123,7 @@ export interface InsightsTabStripProps { */ visibleTileIds?: ReadonlySet; /** - * v1.15.14 W2 — slug → saved-layout `order`. When present the strip + * v1.15.14 — slug → saved-layout `order`. When present the strip * orders flat pills, group parent pills (by first member), and group * children by this order; absent, it falls back to the natural * `SUB_PAGE_SLUGS` / `SUB_PAGE_GROUP_ORDER` order. @@ -140,7 +140,7 @@ export interface InsightsTabStripProps { */ modules?: InsightsModuleMap; /** - * v1.30 (UX/IA audit H1) — true when the account has at least one ECG + * v1.30 — true when the account has at least one ECG * recording. ECG carries NO `MeasurementType`, so it can't ride the * `availability` / `SUB_PAGE_METRIC` model like every metric pill; it is * threaded here as a dedicated data-only signal instead. When true, an @@ -156,7 +156,7 @@ export interface InsightsTabStripProps { } /** - * v1.4.34 IW-D — a strip entry is either a flat `` pill or a + * v1.4.34 — a strip entry is either a flat `` pill or a * group "parent" pill that opens a popover containing the sub-pages * in the group. The discriminated union keeps the renderer branch- * exhaustive and lets future groups land with one new variant. @@ -187,7 +187,7 @@ type TabEntry = }; /** - * v1.30 (UX/IA audit H1) — the ECG entry injected into the Heart group's + * v1.30 — the ECG entry injected into the Heart group's * popover when the account has recordings. Not a `SubPageSlug`: ECG carries no * `MeasurementType`, so it lives on its own routed page (`/insights/ecg`) and * is gated on `hasEcgRecordings`, never on the metric-availability model. @@ -203,7 +203,7 @@ const ECG_TAB_CHILD = { * stay in sync from a single source of truth — adding a sub-page is * one row. */ -// v1.4.33 F9 — label + gating-metric for every routed sub-page. Pill +// v1.4.33 — label + gating-metric for every routed sub-page. Pill // ORDER is owned by `SUB_PAGE_SLUGS` (which iterates the keys of // `SUB_PAGE_METRIC`) so adding a new metric is one row in // `sub-page-metric.ts` and the strip picks it up automatically. @@ -420,7 +420,7 @@ function isSlugModuleEnabled( } /** - * v1.4.34 IW-D — group metadata (label + popover header) keyed by + * v1.4.34 — group metadata (label + popover header) keyed by * `SubPageGroup`. The strip renders one parent pill per group. v1.7.0 * adds the body / activity / hearing / environment / metabolic clusters * as the previously-orphan metrics each get a sub-page; collapsing them @@ -470,7 +470,7 @@ function buildTabs( modules: InsightsModuleMap | undefined, hasEcgRecordings: boolean | undefined, ): TabEntry[] { - // v1.4.36 W4d — strict gating contract: a sub-page only surfaces a + // v1.4.36 — strict gating contract: a sub-page only surfaces a // pill when the availability inputs CONFIRM the metric has at // least one observation in the 90-day window. The legacy // `if (!availability) return true` fallback rendered every pill @@ -482,7 +482,7 @@ function buildTabs( // flips from 0 to ≥1 — exactly the auto-light-up behaviour // described in `metric-availability.ts` line 26. // - // v1.15.14 W2 — the saved insights layout is now a SECOND gate on top + // v1.15.14 — the saved insights layout is now a SECOND gate on top // of data availability. A pill shows iff its metric has data AND its // slug is layout-`visible`. Data availability stays the FLOOR (a // layout-visible metric with zero data still gets no pill). When the @@ -506,7 +506,7 @@ function buildTabs( const visibleSet = new Set(visibleSlugs); - // v1.15.14 W2 — order the strip by the saved layout when present. The + // v1.15.14 — order the strip by the saved layout when present. The // top-level iteration order decides where each flat pill and each group // parent pill land; a group parent appears at the position of its // first (lowest-order) visible member. Falling back to the natural @@ -541,7 +541,7 @@ function buildTabs( }); } - // v1.4.34 IW-D — track which groups have already been emitted so the + // v1.4.34 — track which groups have already been emitted so the // first encountered group-member triggers the parent pill at the // same strip position (preserving categorical order). const emittedGroups = new Set(); @@ -578,7 +578,7 @@ function buildTabs( href: `${INSIGHTS_OVERVIEW_PATH}/${childSlug}`, labelKey: SUB_PAGE_TABS[childSlug].labelKey, })); - // v1.30 (H1) — ECG is a non-metric child of the Heart group, gated on + // v1.30 — ECG is a non-metric child of the Heart group, gated on // its own `hasEcgRecordings` signal (ECG has no `MeasurementType`). if (group === "heart" && hasEcgRecordings) { children.push({ ...ECG_TAB_CHILD }); @@ -600,7 +600,7 @@ function buildTabs( }); } - // v1.30 (H1) — an ECG-only device (a recording, but no pulse / resting-HR / + // v1.30 — an ECG-only device (a recording, but no pulse / resting-HR / // HRV rows) never lit up any Heart-group member above, so the loop never // emitted the group. Emit it now with ECG as its sole child so the user // still has a nav home for their recordings. Positioned after the metric @@ -682,7 +682,7 @@ function InsightsTabStripImpl({ // availability changed. Per // `.planning/research/v15-insights-blocking-bug.md` fix 2. // - // v1.15.14 W2 — the saved-layout visibility set + order map join the + // v1.15.14 — the saved-layout visibility set + order map join the // deps so the strip re-derives when the user toggles a sub-page in // "Anpassen". The shell memoises both props so an unchanged layout // doesn't churn the array. @@ -802,7 +802,7 @@ function InsightsTabStripImpl({ data-tour-id="insights-hero" data-hydrated={mounted ? "true" : undefined} aria-label={t("insights.navAriaLabel")} - // v1.4.28 FB-D3 — `touch-action: pan-y` lets vertical swipes that + // v1.4.28 — `touch-action: pan-y` lets vertical swipes that // begin on the sticky strip scroll the page through it. The // legacy `overflow-x-auto` on the outer `
diff --git a/src/components/insights/metric-empty-state.tsx b/src/components/insights/metric-empty-state.tsx index c3777c3b5..3c8cab674 100644 --- a/src/components/insights/metric-empty-state.tsx +++ b/src/components/insights/metric-empty-state.tsx @@ -6,7 +6,7 @@ import { CoachLaunchButton } from "@/components/insights/coach-launch-button"; import { EmptyState } from "@/components/ui/empty-state"; /** - * v1.4.28 R3d (BK-F-M1 + BK-MED-4) — shared empty-state primitive for + * v1.4.28 — shared empty-state primitive for * the insights sub-pages. * * Pre-fix, every sub-page repeated the same five-line incantation: diff --git a/src/components/insights/metric-stat-strip.tsx b/src/components/insights/metric-stat-strip.tsx index 51c99b482..0a36f349b 100644 --- a/src/components/insights/metric-stat-strip.tsx +++ b/src/components/insights/metric-stat-strip.tsx @@ -29,7 +29,7 @@ import type { MetricWindowStats } from "@/lib/charts/window-stats"; * reach parity with the bespoke pages — a rich numeric header even where * there is no target band or AI assessment. * - * v1.12.7 follow-up — two render modes: + * v1.12.7 — two render modes: * - single-series (default): one Card, one `` + 4-stat grid. * - multi-series (`series=[…]`): ONE Card with the series side by side * (two columns on desktop, stacking on narrow mobile). Blood pressure diff --git a/src/components/insights/metric-status-card.tsx b/src/components/insights/metric-status-card.tsx index ef7532727..99c315f3d 100644 --- a/src/components/insights/metric-status-card.tsx +++ b/src/components/insights/metric-status-card.tsx @@ -43,7 +43,7 @@ export function MetricStatusCard({ const mounted = useMounted(); const { data: status, isLoading } = useInsightMetricStatus(metric, enabled); - // v1.21.0 (C4 H2) — narrow the Coach hand-off to this metric's snapshot + // v1.21.0 — narrow the Coach hand-off to this metric's snapshot // source when one exists; the registry id is a `MeasurementType`-style // token, which `scopeSourceFromMetricKey` resolves for the metrics that // carry a snapshot block. Unmapped metrics still get a metric-named diff --git a/src/components/insights/metric-target-summary.tsx b/src/components/insights/metric-target-summary.tsx index 513e2bfdd..e0cc9789a 100644 --- a/src/components/insights/metric-target-summary.tsx +++ b/src/components/insights/metric-target-summary.tsx @@ -344,7 +344,7 @@ function TargetReferencePanel({ `; when it renders * (inTargetPct present) the same-number `in-target` takeaway is dropped * from the narrative feed so the percentage is not duplicated one row diff --git a/src/components/insights/mood/mood-better-days.tsx b/src/components/insights/mood/mood-better-days.tsx index 55738c513..ab24868a9 100644 --- a/src/components/insights/mood/mood-better-days.tsx +++ b/src/components/insights/mood/mood-better-days.tsx @@ -8,7 +8,7 @@ import { cn } from "@/lib/utils"; import type { MoodInfluenceConfidence } from "./mood-tag-influence"; /** - * v1.11.5 (F2) — "What's associated with your better days" board. + * v1.11.5 — "What's associated with your better days" board. * * One ranked, confidence-gated list folding the F1 tag deltas and the * mood × health-metric correlations into a single effect-size-ranked diff --git a/src/components/insights/mood/mood-correlation-cards.tsx b/src/components/insights/mood/mood-correlation-cards.tsx index 36c07db54..6f97fb5b8 100644 --- a/src/components/insights/mood/mood-correlation-cards.tsx +++ b/src/components/insights/mood/mood-correlation-cards.tsx @@ -157,7 +157,7 @@ function MoodCorrelationCard({ {t(STRENGTH_KEY[data.result.strength])} - {/* v1.12.4 (C4) — the "{n} paired days · r" line sat under the + {/* v1.12.4 — the "{n} paired days · r" line sat under the scatter as a half-empty row. Move it into an explainer icon in the header so the card footprint stays tight. */} = { HEART_RATE_VARIABILITY: "measurements.typeHeartRateVariability", RESTING_HEART_RATE: "measurements.typeRestingHeartRate", WEIGHT: "measurements.typeWeight", - // v1.25 (W-ENV) — environmental-exposure channels, so a "daylight → mood" + // v1.25 — environmental-exposure channels, so a "daylight → mood" // relation reads with its descriptive label instead of the raw ENV_ key. ENV_TEMP_MEAN: "environment.fields.tempMean", ENV_TEMP_MIN: "environment.fields.tempMin", diff --git a/src/components/insights/mood/mood-explainer-icon.tsx b/src/components/insights/mood/mood-explainer-icon.tsx index c65ca0295..4d9532441 100644 --- a/src/components/insights/mood/mood-explainer-icon.tsx +++ b/src/components/insights/mood/mood-explainer-icon.tsx @@ -3,7 +3,7 @@ import { cn } from "@/lib/utils"; /** - * v1.12.4 (C3/C4) — compact explainer for the mood surface. + * v1.12.4 — compact explainer for the mood surface. * * v1.22 — the round info glyph that hid the statistical caveat behind a * hover/focus tooltip is gone. The maintainer wants the trailing "i" diff --git a/src/components/insights/mood/mood-insights-breakdowns.tsx b/src/components/insights/mood/mood-insights-breakdowns.tsx index 29bf0eb16..c584cb449 100644 --- a/src/components/insights/mood/mood-insights-breakdowns.tsx +++ b/src/components/insights/mood/mood-insights-breakdowns.tsx @@ -134,7 +134,7 @@ export function MoodInsightsBreakdowns({ const hasStabilityTile = data.stability != null; const hasInTargetTile = data.summary.inTargetPct != null; - // F1 — fold the structured + flat influence axes into one list ranked by + // Fold the structured + flat influence axes into one list ranked by // absolute delta, so the strongest "this tag moves my mood" rows lead // regardless of which axis they came from. Defensive against a stale // server-cache payload minted before the v1.11.5 shape landed (the diff --git a/src/components/insights/mood/mood-narrative-feed.tsx b/src/components/insights/mood/mood-narrative-feed.tsx index 6560ce81d..a916e5b12 100644 --- a/src/components/insights/mood/mood-narrative-feed.tsx +++ b/src/components/insights/mood/mood-narrative-feed.tsx @@ -65,7 +65,7 @@ export function MoodNarrativeFeed({ items }: { items: MoodNarrativeItem[] }) { if (items.length === 0) return null; return ( - // v1.12.4 (C2) — the takeaways are short one-liners, so a single + // v1.12.4 — the takeaways are short one-liners, so a single // full-width column wasted horizontal space and stacked into a long // ladder. Lay them two-up on anything wider than a phone; a lone // takeaway still spans the row rather than orphaning half of it. diff --git a/src/components/insights/mood/mood-tag-influence.tsx b/src/components/insights/mood/mood-tag-influence.tsx index 4db56866a..2a2893733 100644 --- a/src/components/insights/mood/mood-tag-influence.tsx +++ b/src/components/insights/mood/mood-tag-influence.tsx @@ -5,7 +5,7 @@ import { moodTagIcon } from "@/components/mood/mood-tag-icons"; import { cn } from "@/lib/utils"; /** - * v1.11.5 (F1) — tag "Influence on Mood". + * v1.11.5 — tag "Influence on Mood". * * For each frequent flat / structured tag, the average daily mood on days * the tag is PRESENT vs the counterfactual baseline of days it is ABSENT, diff --git a/src/components/insights/period-narrative-card.tsx b/src/components/insights/period-narrative-card.tsx index 8ab40bb71..2495101b1 100644 --- a/src/components/insights/period-narrative-card.tsx +++ b/src/components/insights/period-narrative-card.tsx @@ -20,7 +20,7 @@ import { apiGet } from "@/lib/api/api-fetch"; import { InsightSectionCard } from "./insight-section-card"; /** - * v1.11.0 W3 — period-narrative card (Pillar P1). + * v1.11.0 — period-narrative card (Pillar P1). * * A calm, top-of-overview summary of the user's last week / month, drawn from * the read-only `GET /api/insights/narrative` route. The route is @@ -187,7 +187,7 @@ export function PeriodNarrativeCard({ />
- {/* v1.22 (W6) — real paragraphs via the shared ProseBlocks helper + {/* v1.22 — real paragraphs via the shared ProseBlocks helper (still plain text children — NO markdown renderer, XSS posture). */}
diff --git a/src/components/insights/personal-record-badge.tsx b/src/components/insights/personal-record-badge.tsx index 086433a70..1e0c5599d 100644 --- a/src/components/insights/personal-record-badge.tsx +++ b/src/components/insights/personal-record-badge.tsx @@ -1,7 +1,7 @@ "use client"; /** - * v1.4.25 W16c — Personal-record "PR" pill. + * v1.4.25 — Personal-record "PR" pill. * * Renders a small, text-only "PR" tag next to a metric label when the * given metric reached an all-time best in the last 30 days. Pulls diff --git a/src/components/insights/prose-blocks.tsx b/src/components/insights/prose-blocks.tsx index 3010bfb15..7857ffd26 100644 --- a/src/components/insights/prose-blocks.tsx +++ b/src/components/insights/prose-blocks.tsx @@ -8,7 +8,7 @@ import { learnUrl } from "@/lib/learn-links"; import { InlineLearnLink } from "@/components/ui/learn-more-link"; /** - * v1.22 (W5) — shared paragraph renderer for every trusted-as-text AI + * v1.22 — shared paragraph renderer for every trusted-as-text AI * narrative surface (the Coach, the daily briefing, the per-metric / * score insight cards, the period narrative). * diff --git a/src/components/insights/recovery/recovery-metric-block.tsx b/src/components/insights/recovery/recovery-metric-block.tsx index 6e7cc7aab..9c80134bd 100644 --- a/src/components/insights/recovery/recovery-metric-block.tsx +++ b/src/components/insights/recovery/recovery-metric-block.tsx @@ -14,7 +14,7 @@ import { MetricStatStrip } from "@/components/insights/metric-stat-strip"; import { MetricStatusCard } from "@/components/insights/metric-status-card"; /** - * v1.18.1 (B1 / B2) — one canonical chart block on `/insights/recovery`. + * v1.18.1 — one canonical chart block on `/insights/recovery`. * * The rebuilt recovery page reads exactly like every other metric page * (blood pressure, pulse, the HealthKit scaffold): each present device diff --git a/src/components/insights/sleep-duration-chart.tsx b/src/components/insights/sleep-duration-chart.tsx index 6f07fb701..02202401d 100644 --- a/src/components/insights/sleep-duration-chart.tsx +++ b/src/components/insights/sleep-duration-chart.tsx @@ -5,7 +5,7 @@ import { useTranslations } from "@/lib/i18n/context"; import type { ComparisonBaseline } from "@/lib/dashboard-layout"; /** - * v1.4.25 W4c — nightly TIME-ASLEEP over time. + * v1.4.25 — nightly TIME-ASLEEP over time. * * Thin wrapper around `` so the sleep sub-page gets * the same chart-cog parity (`chartKey="sleep"`) as the dashboard chart @@ -23,7 +23,7 @@ import type { ComparisonBaseline } from "@/lib/dashboard-layout"; * returns and the `` headline shows — so the axis reads e.g. * "8 h", not the inflated per-stage minutes the legacy path surfaced. * - * v1.4.28 R3d (BK-F-M2) — the inline `dynamic()` call site was retired + * v1.4.28 — the inline `dynamic()` call site was retired * in favour of the shared `` re-export so the * `` + `ssr: false` configuration stays in one place. */ diff --git a/src/components/insights/sleep-overview.tsx b/src/components/insights/sleep-overview.tsx index dadd2e58c..d360892bd 100644 --- a/src/components/insights/sleep-overview.tsx +++ b/src/components/insights/sleep-overview.tsx @@ -18,7 +18,7 @@ import { importWithRetry } from "@/lib/retry-import"; import type { SleepStageBreakdown } from "./sleep-stage-stacked-bar"; import { SleepDurationChart } from "./sleep-duration-chart"; -// v1.18.11 (W5 perf) — the stage stacked bar is the only recharts consumer +// v1.18.11 — the stage stacked bar is the only recharts consumer // on this page (the duration chart already routes through // `health-chart-dynamic`). Defer it through `next/dynamic` so recharts is // off `/insights/sleep`'s first-load JS; the `` loading shell @@ -43,7 +43,7 @@ function SleepStageStackedBar( } /** - * v1.4.25 W4c — Sleep insights composition. + * v1.4.25 — Sleep insights composition. * * Pulls the analytics `sleepStages` aggregate (added in v1.4.23 W2) * plus the SLEEP_DURATION trend series via `` and @@ -58,7 +58,7 @@ function SleepStageStackedBar( * the shared sleep-rhythm grid row directly below, so the top card only * duplicated it. * - * v1.18.7 W-D — the single-night "Last night" hypnogram card was removed: + * v1.18.7 — the single-night "Last night" hypnogram card was removed: * its per-stage breakdown (time + %) duplicated the phase distribution the * stage stacked bar already shows directly below it. The `/api/sleep/night` * route stays (the dashboard still reads it); only this redundant surface is @@ -90,7 +90,7 @@ export function SleepOverview() { const { t } = useTranslations(); const { compareBaseline } = useInsightsLayoutPrefs(isAuthenticated); - // v1.4.33 IW2 — sleep overview reads `sleepStages` (a thick-only + // v1.4.33 — sleep overview reads `sleepStages` (a thick-only // field) alongside `summaries.SLEEP_DURATION`, so it stays on the // default thick slice. The shared hook still centralises the cache // settings (60s staleTime, no refetch on mount / window focus) so diff --git a/src/components/insights/sleep-stage-stacked-bar.tsx b/src/components/insights/sleep-stage-stacked-bar.tsx index 8dbd0a833..6e9e0ef86 100644 --- a/src/components/insights/sleep-stage-stacked-bar.tsx +++ b/src/components/insights/sleep-stage-stacked-bar.tsx @@ -77,7 +77,7 @@ export interface SleepStageBreakdown { */ stages: Record; /** - * v1.4.25 W3f — per-night breakdown over the trailing 30 days, + * v1.4.25 — per-night breakdown over the trailing 30 days, * sorted ascending. The chart slices the trailing N entries based on * the active window toggle. */ @@ -273,7 +273,7 @@ export function SleepStageStackedBar({ breakdown }: SleepStageStackedBarProps) { const formatCalendar = useCalendarDate(); const fmtDay = (date: Date) => formatCalendar(dateOnlyKey(date)); - // v1.4.25 W3f — window toggle (7 / 14 / 30). Default 7d so the user + // v1.4.25 — window toggle (7 / 14 / 30). Default 7d so the user // sees their most recent week with maximal per-bar resolution. const [windowDays, setWindowDays] = useState(7); @@ -289,7 +289,7 @@ export function SleepStageStackedBar({ breakdown }: SleepStageStackedBarProps) { [NAP_KEY]: t("insights.sleep.stages.nap"), }; - // v1.4.25 W3f — per-night dataset. Slice the trailing N nights and + // v1.4.25 — per-night dataset. Slice the trailing N nights and // build a Recharts row per night with one numeric key per stage. // Empty perNight (legacy clients during the rollout) falls back to // the aggregate row so the chart still renders something rather @@ -378,7 +378,7 @@ export function SleepStageStackedBar({ breakdown }: SleepStageStackedBarProps) {
` (the + * v1.18.7 — the chronotype card moved out to `` (the * prominent bottom block); this section now owns the sleep-debt headline alone. */ diff --git a/src/components/insights/sleep/chronotype-section.tsx b/src/components/insights/sleep/chronotype-section.tsx index 09c4bddbc..d4e80ef86 100644 --- a/src/components/insights/sleep/chronotype-section.tsx +++ b/src/components/insights/sleep/chronotype-section.tsx @@ -6,7 +6,7 @@ import { useSleepRhythm } from "./use-sleep-rhythm"; import { ChronotypeCard } from "./chronotype-card"; /** - * v1.18.7 W-D — chronotype card on the Sleep view. + * v1.18.7 — chronotype card on the Sleep view. * * v1.19.0 — the standalone "Chronotyp" `` was removed: it * duplicated the heading the `` already carries in its own diff --git a/src/components/insights/sleep/sleep-rhythm-section.tsx b/src/components/insights/sleep/sleep-rhythm-section.tsx index d40cf03ff..39abac599 100644 --- a/src/components/insights/sleep/sleep-rhythm-section.tsx +++ b/src/components/insights/sleep/sleep-rhythm-section.tsx @@ -13,7 +13,7 @@ import { SleepDebtCard } from "./sleep-debt-card"; * fetches. The values are identical to what the iOS client renders (one server * computation). * - * v1.18.7 W-D — the chronotype card moved out to ``, the + * v1.18.7 — the chronotype card moved out to ``, the * prominent bottom treatment of the Sleep view; this section now owns the * sleep-debt headline alone. Both read the same `["sleep-rhythm"]` cache. * diff --git a/src/components/insights/sub-page-shell.tsx b/src/components/insights/sub-page-shell.tsx index 42466de23..77dc449b2 100644 --- a/src/components/insights/sub-page-shell.tsx +++ b/src/components/insights/sub-page-shell.tsx @@ -21,7 +21,7 @@ const INSIGHT_METRIC_RETURN_PATHS: Readonly> = SUB_PAGE_SLUGS.map((slug) => [`/insights/${slug}`, true] as const), ); /** - * v1.4.25 W4 — common visual frame for each metric sub-page. + * v1.4.25 — common visual frame for each metric sub-page. * * Each sub-page renders: * - a calm header (title + optional status badge), @@ -76,7 +76,7 @@ export interface SubPageShellProps { */ explainerParams?: Record; /** - * v1.4.27 MB7 / CF-35 — opt-in programmatic focus on mount. + * v1.4.27 — opt-in programmatic focus on mount. * * The legacy default-on `focus()` call moved screen-reader focus to * the heading but on mobile it also fought the soft-keyboard: a @@ -101,7 +101,7 @@ export interface SubPageShellProps { */ statStrip?: ReactNode; /** - * v1.21.2 (A1) — the "Coach read" strip, mounted between the stat strip and + * v1.21.2 — the "Coach read" strip, mounted between the stat strip and * the chart. A compact two-line own-baseline + lagged-association read that * makes the metric feel seen-in-context. Self-gating (renders nothing until * its server read lands, and nothing at all when there is no baseline and no @@ -110,7 +110,7 @@ export interface SubPageShellProps { */ coachReadStrip?: ReactNode; /** - * v1.8.5 W4b — measurement-diversity nudge. + * v1.8.5 — measurement-diversity nudge. * * v1.8.6 — relocated from an inline block under the stat strip to a * `Lightbulb` glyph beside the heading (a hover / focus Tooltip carries @@ -122,12 +122,12 @@ export interface SubPageShellProps { /** * v1.8.6 — opt-in Coach launch in the page header. * - * v1.18.6 (CCH-04) — RETIRED as a rendered control. With the Coach FAB + * v1.18.6 — RETIRED as a rendered control. With the Coach FAB * anchored bottom-right on every authenticated page, the per-metric * Coach icon was a redundant second entry point, so the shell no longer * paints anything for it. * - * v1.21.0 (C4 H1) — re-purposed without re-adding any control. When set + * v1.21.0 — re-purposed without re-adding any control. When set * (and `explainerMetric` maps to a Coach source), the shell registers * the page's metric as the launch context's ambient scope while it is * mounted. The global FAB then opens a conversation pre-scoped to this @@ -142,7 +142,7 @@ export interface SubPageShellProps { */ captureType?: string; /** - * v1.8.5 W4b — a "show all readings" entry linking to the dedicated + * v1.8.5 — a "show all readings" entry linking to the dedicated * `/insights/values/` subpage. Sub-pages pass the metric's * `MeasurementType`. * @@ -231,7 +231,7 @@ export function SubPageShell({ statStrip, coachReadStrip, diversityNudge, - // v1.21.0 (C4 H1) — `coachLaunch` no longer renders a control (CCH-04 + // v1.21.0 — `coachLaunch` no longer renders a control (CCH-04 // stands) but now gates whether the page registers its metric as the // Coach launch context's ambient scope, so the FAB opens contextual. coachLaunch = false, @@ -261,7 +261,7 @@ export function SubPageShell({ // CF-35 made the focus call opt-in via `focusOnMount` — the // default-on version stole focus from soft-keyboards on mobile. // - // v1.4.33 IW9 — the scroll-to-top affordance moved into the shared + // v1.4.33 — the scroll-to-top affordance moved into the shared // `useScrollResetOnRoute()` hook so the mother page + sub-page can't // both fire (which produced a visible double-snap when the chart // skeleton inflated between the two RAF callbacks). The hook itself @@ -277,7 +277,7 @@ export function SubPageShell({ return () => window.cancelAnimationFrame(handle); }, [focusOnMount]); - // v1.21.0 (C4 H1) — register the page's metric as the Coach launch + // v1.21.0 — register the page's metric as the Coach launch // context's ambient scope while the sub-page is mounted, so the global // FAB opens a conversation pre-scoped to this metric with a data-aware // seed question. Only when the page opted in (`coachLaunch`) and its @@ -295,7 +295,7 @@ export function SubPageShell({ also: resolved.also, window: resolved.window, }, - // v1.22 (W6) — `question` is now an i18n key; resolve it so the composer + // v1.22 — `question` is now an i18n key; resolve it so the composer // seed reads in the user's language. t(resolved.question), ); @@ -411,7 +411,7 @@ export function SubPageShell({ ) : null} - {/* v1.18.6 (CCH-04) — the per-metric Coach launch icon is + {/* v1.18.6 — the per-metric Coach launch icon is gone. With the Coach FAB anchored bottom-right on every authenticated page, a second Coach affordance in each metric header was redundant. The `coachLaunch` prop is @@ -426,7 +426,7 @@ export function SubPageShell({ // v1.8.4 — surface the metric's static definition inline, // directly beneath the heading. // - // v1.8.5 W4a — render the explainer body as the *single* caption + // v1.8.5 — render the explainer body as the *single* caption // under the heading. Pre-v1.8.5 both this paragraph and the // `description` below it stacked, so every metric page opened // with two near-duplicative muted captions before any data — the @@ -464,7 +464,7 @@ export function SubPageShell({ strip so the numbers always reflect the range the chart paints. No drag, no pill — the range tab is the single selector. */} {statStrip} - {/* v1.21.2 (A1) — the "Coach read" strip sits between the numbers-first + {/* v1.21.2 — the "Coach read" strip sits between the numbers-first stat strip and the chart, so the spine reads intro → numbers → Coach read → chart → target → assessment. Self-gating, so the rhythm holds when the strip has nothing to say. */} diff --git a/src/components/insights/trend-annotation.tsx b/src/components/insights/trend-annotation.tsx index 7551a88cc..daaf901b2 100644 --- a/src/components/insights/trend-annotation.tsx +++ b/src/components/insights/trend-annotation.tsx @@ -23,7 +23,7 @@ import { apiGet } from "@/lib/api/api-fetch"; import { useUnitDisplay } from "@/hooks/use-unit-display"; /** - * v1.4.20 phase B3 — single-sentence AI annotation rendered directly + * v1.4.20 — single-sentence AI annotation rendered directly * below a Trends-row chart. * * Pure presentational. The annotation string is sourced from the AI @@ -35,7 +35,7 @@ import { useUnitDisplay } from "@/hooks/use-unit-display"; * a backing correlation gives us one. The chip is purely visual and * never adds new copy beyond the `low / moderate / high` translation. * - * v1.4.36 W2 T3 — render-state contract (`status`). Pre-fix the + * v1.4.36 — render-state contract (`status`). Pre-fix the * component derived empty vs filled from `annotation == null` alone, * which painted "Mehr Daten nötig" on every cold mount and every * regenerate-in-flight, even when the advisor was about to deliver an @@ -91,7 +91,7 @@ export function TrendCaptionCard({ aria-hidden="true" />
- {/* v1.4.28 R3c-Insights — `line-clamp-3` bounds caption + {/* v1.4.28 — `line-clamp-3` bounds caption variance (FB-K2). A 1-sentence BP annotation paints ~3 lines wrapped; a 4-line mood annotation used to push the cell taller, which `auto-rows-fr` propagated to @@ -143,7 +143,7 @@ export function TrendAnnotation({ }: TrendAnnotationProps) { const { t } = useTranslations(); - // v1.4.36 W2 T3 — render-state contract. When `status` is supplied + // v1.4.36 — render-state contract. When `status` is supplied // it drives the branch directly; otherwise we fall back on the // legacy `annotation == null → empty` mapping so existing callers // (tests, isolated mounts) keep their previous behaviour. @@ -173,7 +173,7 @@ export function TrendAnnotation({

`'s empty state hints at the gap without * breaking the row's rhythm. * - * v1.8.6 W8 — every card carries a caption. Additive metrics (the + * v1.8.6 — every card carries a caption. Additive metrics (the * briefing-driven slots beyond the legacy triple) carry no advisor * annotation, so they fall back to a standard one-line description * (`config.captionKey`). Before this every additive card painted the @@ -100,7 +100,7 @@ interface TrendsRowProps { mood?: TrendAnnotationConfidenceBand; }; /** - * v1.4.36 W2 T3 — advisor query / mutation in flight. When true, + * v1.4.36 — advisor query / mutation in flight. When true, * every per-metric annotation slot paints a pending shimmer instead * of the "Mehr Daten nötig" empty hint. Resolves the recurring * complaint where the empty hint flashed across all three metrics @@ -168,7 +168,7 @@ export function TrendsRow({ hiddenMetrics, }); - // v1.4.36 W2 T3 — derive the tri-state status per metric from the + // v1.4.36 — derive the tri-state status per metric from the // advisor's loading flag + the annotation presence. Pending wins // over needs_data so a mid-generation regenerate doesn't flash the // empty hint between the spinner and the new prose. @@ -180,7 +180,7 @@ export function TrendsRow({ const annotationFor = (key: TrendAnnotationKey): string | null => annotations?.[key] ?? null; - // v1.11.4 item J — three-tier caption precedence for the legacy triple + // v1.11.4 — three-tier caption precedence for the legacy triple // (the only slots that carry an advisor annotation): // 1. advisor in flight → `` // shimmer (loading flag). @@ -230,23 +230,23 @@ export function TrendsRow({ title={t("insights.trendsRow.title")} subtitle={t("insights.trendsRow.subtitle")} /> - {/* v1.4.22 A4 — equal-height cards. The annotation prose + {/* v1.4.22 — equal-height cards. The annotation prose below each chart varies in length, which used to leave the three cards on visibly different baselines. Each card is now a flex column with `min-h-[300px]` so the chart anchors to the top and the annotation grows downward into a shared minimum height. - v1.4.25 W3 — strengthen the equal-height contract with + v1.4.25 — strengthen the equal-height contract with `md:auto-rows-fr` + `md:items-stretch` on the grid and `h-full` on each card so the tallest annotation still pins every row member to a single baseline. - v1.4.27 MB7 / CF-71 — drop the unconditional `min-h-[300px]` + v1.4.27 — drop the unconditional `min-h-[300px]` floor to `md:min-h-[300px]` so mobile single-column doesn't eat dead space below a short chart; the floor stays on `md+` to preserve the equal-height baseline across the row. - v1.4.28 R3c-Insights (FB-K1/K2) — three-slot template. + v1.4.28 — three-slot template. `auto-rows-fr` covers every viewport, not just `md+`, so the mobile single-column path picks up the same row contract when the user expands the strip side-by-side via the @@ -268,7 +268,7 @@ export function TrendsRow({ key={config.metric} data-slot="trends-row-card" data-metric={config.metric} - // v1.15.12 D1 — drop the `md:min-h-[300px]` floor. The chart + // v1.15.12 — drop the `md:min-h-[300px]` floor. The chart // slot is a fixed 180 px and the caption is `line-clamp-3`, so a // card's natural height tops out around 270 px; the 300 px floor // padded ~30 px of dead space below every caption, which read as @@ -279,12 +279,12 @@ export function TrendsRow({ // the floor was redundant. Cards now size to content. className="flex h-full flex-col gap-2" > - {/* v1.4.28 R3c-Insights — fixed chart slot. The mini + {/* v1.4.28 — fixed chart slot. The mini chart paints its own band; this wrapper pins the total chart-envelope height so every tile lines up on a single baseline regardless of the chart kind. - v1.11.4 item I — the slot is now the hard bound for + v1.11.4 — the slot is now the hard bound for every chart kind. Two changes close the mood-card overflow (its categorical y-axis + date x-axis bled the tick labels out the bottom, overlapping the @@ -319,10 +319,10 @@ export function TrendsRow({ {config.annotationKey ? ( renderLegacyCaption(config) ) : ( - // v1.8.6 W8 — additive metrics carry no advisor + // v1.8.6 — additive metrics carry no advisor // annotation. Paint the metric's standard one-line // description so the card is never caption-less. - // v1.8.7 W-E — render that description through the same + // v1.8.7 — render that description through the same // `` shell the advisor caption uses, so // the fallback caption shares the bordered card, Sparkles // affordance, and `text-foreground` typography rather than diff --git a/src/components/insights/use-insights-advisor.ts b/src/components/insights/use-insights-advisor.ts index 31eef1153..34bf5fd10 100644 --- a/src/components/insights/use-insights-advisor.ts +++ b/src/components/insights/use-insights-advisor.ts @@ -20,7 +20,7 @@ import { useAiCapability } from "@/hooks/use-ai-capability"; import type { BriefingFailureClass } from "@/lib/insights/briefing-failure-marker"; /** - * v1.4.16 phase D reconcile (CRITICAL C1 + C2) — shared TanStack Query + * v1.4.16 (CRITICAL C1 + C2) — shared TanStack Query * helper that reads the rich advisor payload from `/api/insights/generate`. * * Why a POST under a `useQuery` rather than a dedicated GET endpoint: @@ -42,7 +42,7 @@ export interface InsightAdvisorPayload { cachedAt?: string | null; legacyPayload?: boolean; /** - * v1.4.20 phase B1 — Daily Briefing block surfaced for the new hero + * v1.4.20 — Daily Briefing block surfaced for the new hero * strip + briefing card. Lives on the cached payload alongside the * legacy `insights` shape (see `aiInsightResponseSchema` — the * `.passthrough()` lets the field round-trip through any provider). @@ -51,7 +51,7 @@ export interface InsightAdvisorPayload { */ dailyBriefing?: DailyBriefingPayload | null; /** - * v1.4.20 phase B3 — optional trend annotations for the Trends row. + * v1.4.20 — optional trend annotations for the Trends row. * Same lift-pattern as `dailyBriefing` — validated client-side and * left null when the cached payload predates PROMPT_VERSION 4.20.1. */ @@ -359,7 +359,7 @@ export async function fetchAdvisor( payload.dailyBriefing = null; } - // v1.4.20 phase B3 — same lift for `trendAnnotations`. Cached payloads + // v1.4.20 — same lift for `trendAnnotations`. Cached payloads // from the 4.20.0 line predate the field, so null is the expected // default. A malformed candidate also resolves to null so the UI // surfaces the per-metric empty hint instead of a half-rendered card. diff --git a/src/components/labs/biomarker-aliases.tsx b/src/components/labs/biomarker-aliases.tsx index a652aefec..6da26efc4 100644 --- a/src/components/labs/biomarker-aliases.tsx +++ b/src/components/labs/biomarker-aliases.tsx @@ -1,7 +1,7 @@ "use client"; /** - * v1.43 (D1223) — the other spellings a marker answers to. + * v1.43 — the other spellings a marker answers to. * * Shown as chips on the marker; with the labs manage right they can be added * and removed. Every change sends the whole set (`PUT /api/biomarkers/{id}` diff --git a/src/components/labs/biomarker-manager.tsx b/src/components/labs/biomarker-manager.tsx index 6cd1065dd..f204f0842 100644 --- a/src/components/labs/biomarker-manager.tsx +++ b/src/components/labs/biomarker-manager.tsx @@ -52,7 +52,7 @@ export function BiomarkerManager() { const [formOpen, setFormOpen] = useState(false); const [editing, setEditing] = useState(null); const [formFooterEl, setFormFooterEl] = useState(null); - // v1.43 (D1223) — merging is a manage-level act, like edit and delete. + // v1.43 — merging is a manage-level act, like edit and delete. const { canManageDomain } = useRecordCapabilities(); const canMerge = canManageDomain("labs"); const [mergeOpen, setMergeOpen] = useState(false); diff --git a/src/components/labs/biomarker-merge-sheet.tsx b/src/components/labs/biomarker-merge-sheet.tsx index babc4a7f9..0853b410b 100644 --- a/src/components/labs/biomarker-merge-sheet.tsx +++ b/src/components/labs/biomarker-merge-sheet.tsx @@ -1,7 +1,7 @@ "use client"; /** - * v1.43 (D1223) — merge markers that are one analyte written two ways. + * v1.43 — merge markers that are one analyte written two ways. * * Three steps in one sheet: pick the markers, pick the one that stays, read * the preview, then confirm. The preview is the server's own dry run diff --git a/src/components/labs/lab-biomarker-detail.tsx b/src/components/labs/lab-biomarker-detail.tsx index 557d32a4c..f83352055 100644 --- a/src/components/labs/lab-biomarker-detail.tsx +++ b/src/components/labs/lab-biomarker-detail.tsx @@ -61,7 +61,7 @@ import type { LabResultListResponse, } from "./types"; -// v1.18.11 (W5 perf) — defer the recharts biomarker chart through +// v1.18.11 — defer the recharts biomarker chart through // `next/dynamic` so recharts is off `/labs/[biomarkerId]`'s first-load JS. // The chart only paints once the reading list resolves; the `` // loading shell matches the in-card chart footprint so the layout is stable. @@ -166,7 +166,7 @@ export function LabBiomarkerDetail({ biomarkerId }: { biomarkerId: string }) { queryFn: () => apiGet(`/api/biomarkers/${biomarkerId}`), }); - // v1.43 (D1223) — a marker merged into another answers 404 with the + // v1.43 — a marker merged into another answers 404 with the // marker it went into; a bookmark or a stale screen follows it there. const mergedInto = markerFailure instanceof ApiError && @@ -271,7 +271,7 @@ export function LabBiomarkerDetail({ biomarkerId }: { biomarkerId: string }) { invalidateAfterReadingWrite(); } - // v1.30.1 H3 — "Save & add another": same invalidation as `afterAdd`, + // v1.30.1 — "Save & add another": same invalidation as `afterAdd`, // minus the close. Useful here too — logging several past readings for // this one biomarker back-to-back without re-opening the sheet each time. function afterAddKeepOpen() { @@ -321,7 +321,7 @@ export function LabBiomarkerDetail({ biomarkerId }: { biomarkerId: string }) { ? marker.context.trim() : t("labs.detail.genericDescription"); - // v1.25.1 (H2-M2) — the error state keeps the heading + description and adds a + // v1.25.1 — the error state keeps the heading + description and adds a // retry, mirroring the insights `SubPageShell` recovery affordance rather than // collapsing to a bare line with no context and no way back. // A merged marker is on its way to its target (see above), not failed. @@ -405,7 +405,7 @@ export function LabBiomarkerDetail({ biomarkerId }: { biomarkerId: string }) { {canAddLab && ( ) : null} - {/* v1.30.1 H3 — additive: only renders when the caller wires up + {/* v1.30.1 — additive: only renders when the caller wires up `onSavedKeepOpen`. `data-action` is how `handleSubmit` tells this button apart from the plain Save below via `event.submitter`. */} {onSavedKeepOpen ? ( diff --git a/src/components/labs/lab-history-list.tsx b/src/components/labs/lab-history-list.tsx index f555f1154..cd794a430 100644 --- a/src/components/labs/lab-history-list.tsx +++ b/src/components/labs/lab-history-list.tsx @@ -368,7 +368,7 @@ export function LabHistoryList({ readings }: { readings: LabResultDto[] }) { ) : null; - // v1.18.6 (MOD-01) — the wrench links to the Vorsorge settings page (view, + // v1.18.6 — the wrench links to the Vorsorge settings page (view, // reorder, per-reminder enable toggles), left of the Add button — the // canonical medication-page pattern. The old kebab "Anpassen" sheet is // gone (MOD-07); its toggles moved to the settings page. `/settings` is the @@ -811,7 +811,7 @@ export function VorsorgeSection({ } /** - * v1.18.6 (MOD-06) — progress through the current interval toward the next + * v1.18.6 — progress through the current interval toward the next * due date, as a 0–100 percentage. Honest "are we on track" gamification * that reuses the medication card's `Progress` building block without * fabricating a streak the reminder data cannot support: 100 % (or beyond) @@ -991,7 +991,7 @@ function VorsorgeCard({ the primary action, the way the medication card carries "taken" and "skipped" side by side, so it is a card button now — see `postponeButton` below. */} - {/* v1.18.7 (Wave E) — jump to the measurements list pre-filtered to + {/* v1.18.7 — jump to the measurements list pre-filtered to this reminder's type. Only for measurement-linked reminders; a free-text reminder has no readings to show. */} {isLinked && ( @@ -1272,7 +1272,7 @@ function VorsorgeCard({

) : null; - // v1.18.6 (MOD-03) — compact list row variant. + // v1.18.6 — compact list row variant. if (view === "list") { return ( <> @@ -1442,7 +1442,7 @@ function VorsorgeCard({ {nextLastSlot} {skipSnoozeMeta} - {/* v1.18.6 (MOD-06) — light gamification: progress through the + {/* v1.18.6 — light gamification: progress through the current interval toward next-due, reusing the medication card's Progress building block. Honest "on track / due" signal without a fabricated streak; hidden for RRULE / unscheduled reminders. */} diff --git a/src/components/measurement-reminders/vorsorge-settings.tsx b/src/components/measurement-reminders/vorsorge-settings.tsx index 15311b2b3..26f2475d3 100644 --- a/src/components/measurement-reminders/vorsorge-settings.tsx +++ b/src/components/measurement-reminders/vorsorge-settings.tsx @@ -1,7 +1,7 @@ "use client"; /** - * v1.18.6 (W8 / MOD-03) — the Vorsorge module's own settings page body. + * v1.18.6 — the Vorsorge module's own settings page body. * * Reached from the wrench beside the page's "hinzufügen" button. Lets the * user reorder reminders + pick the card-vs-list page view, and re-homes the @@ -56,7 +56,7 @@ export function VorsorgeSettings() { : t("measurementReminders.selfPlanned"), })); - // v1.18.10 (W7) — the three Vorsorge settings groups adopt the shared + // v1.18.10 — the three Vorsorge settings groups adopt the shared // `SettingsCard` + `SettingsCardHeader` contract (rounded-xl bordered // surface, neutral muted icon, `text-lg` title, short muted description) // so this surface reads like every other Settings section instead of bare @@ -130,7 +130,7 @@ export function VorsorgeSettings() { }) } disabled={update.isPending} - // L3 — label each row's Switch by its own name span so SR + // Label each row's Switch by its own name span so SR // users hear the reminder name (plus the switch role's // on/off state) instead of N identical generic labels. aria-labelledby={`vorsorge-reminder-label-${reminder.id}`} diff --git a/src/components/measurements/__tests__/measurement-list-apple-health-badge.test.tsx b/src/components/measurements/__tests__/measurement-list-apple-health-badge.test.tsx index 7a453203f..b4f2f7313 100644 --- a/src/components/measurements/__tests__/measurement-list-apple-health-badge.test.tsx +++ b/src/components/measurements/__tests__/measurement-list-apple-health-badge.test.tsx @@ -53,7 +53,7 @@ vi.mock("@/hooks/use-auth", () => ({ }), })); -// v1.28.42 (H3) — the list now mounts only the active layout (desktop table +// v1.28.42 — the list now mounts only the active layout (desktop table // OR mobile card list), not both CSS-hidden copies. Drive the breakpoint hook // so each branch can be asserted independently. vi.mock("@/hooks/use-is-mobile", () => ({ @@ -103,7 +103,7 @@ describe("MeasurementList — APPLE_HEALTH badge", () => { // duplicate hand-entries on top of synced rows. Both layouts must // carry the badge so the surface is honest at every viewport. // - // v1.28.42 (H3) — only the active layout mounts now, so assert the + // v1.28.42 — only the active layout mounts now, so assert the // badge appears once in each branch (rather than twice in one render). vi.mocked(useIsMobile).mockReturnValue(false); const desktop = render("en").match( diff --git a/src/components/measurements/__tests__/measurement-list-bp-badge.test.tsx b/src/components/measurements/__tests__/measurement-list-bp-badge.test.tsx index 0ecc09a48..70d73f6f5 100644 --- a/src/components/measurements/__tests__/measurement-list-bp-badge.test.tsx +++ b/src/components/measurements/__tests__/measurement-list-bp-badge.test.tsx @@ -66,7 +66,7 @@ vi.mock("@/hooks/use-auth", () => ({ }), })); -// v1.28.42 (H3) — the list now mounts only the active layout, so drive the +// v1.28.42 — the list now mounts only the active layout, so drive the // breakpoint hook to assert the badge in each branch independently. vi.mock("@/hooks/use-is-mobile", () => ({ useIsMobile: vi.fn(() => false), @@ -95,7 +95,7 @@ function countOccurrences(haystack: string, needle: string): number { } describe("MeasurementList — Sys/Dia disambiguation badge (mobile list)", () => { - // v1.28.42 (H3) — only the active layout mounts now (previously both the + // v1.28.42 — only the active layout mounts now (previously both the // desktop table and the mobile card list rendered, CSS-hidden). Assert each // label appears exactly once per branch so the mobile-enum regression this // guard was written for (badge silently absent on mobile) still fails loudly. diff --git a/src/components/measurements/__tests__/measurement-list-empty.test.tsx b/src/components/measurements/__tests__/measurement-list-empty.test.tsx index 5a96a8d20..d85d23dc1 100644 --- a/src/components/measurements/__tests__/measurement-list-empty.test.tsx +++ b/src/components/measurements/__tests__/measurement-list-empty.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.15 phase-C5 — `/measurements` empty state. + * v1.4.15 — `/measurements` empty state. * * Before this phase the list rendered a single sentence * ("No measurements yet") in a dashed-border box with no CTA. Brand- diff --git a/src/components/measurements/__tests__/measurement-list-step-grouping.test.tsx b/src/components/measurements/__tests__/measurement-list-step-grouping.test.tsx index 5394b2261..d96cb668a 100644 --- a/src/components/measurements/__tests__/measurement-list-step-grouping.test.tsx +++ b/src/components/measurements/__tests__/measurement-list-step-grouping.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; /** - * v1.4.37 W7c — when the list filter is a cumulative HK type, the + * v1.4.37 — when the list filter is a cumulative HK type, the * list view renders one row per user-TZ day with the day's total and * a chevron that drills back into the per-sample chunks via a * separate query. This test mocks both queries (the parent + the diff --git a/src/components/measurements/measurement-form.tsx b/src/components/measurements/measurement-form.tsx index dbf6ef93b..4a4fd42e6 100644 --- a/src/components/measurements/measurement-form.tsx +++ b/src/components/measurements/measurement-form.tsx @@ -201,7 +201,7 @@ export const MEASUREMENT_TYPES = [ }, ] as const; -// v1.4.34 IW-G — single source of truth for the `/measurements?add=` +// v1.4.34 — single source of truth for the `/measurements?add=` // deep link the Insights empty-state CTAs ship. Derived from the form's // MEASUREMENT_TYPES so a new row in the form is immediately usable as // a deep-link target. @@ -270,7 +270,7 @@ interface MeasurementFormProps { */ defaultDate?: string; /** - * v1.4.27 R4 RC2 — when the form is mounted inside a + * v1.4.27 — when the form is mounted inside a * `` the caller passes the sheet's footer slot * element here. The form's action-row (kebab + Cancel + Save) is * portalled into that slot so the bottom-sheet branch can @@ -339,7 +339,7 @@ export function MeasurementForm({ ? "BLOOD_PRESSURE" : defaultType; - // v1.30.1 M3 — an explicit deep-link default always wins; absent one, + // v1.30.1 — an explicit deep-link default always wins; absent one, // seed from the last type the user actually saved rather than always // landing on BLOOD_PRESSURE. Lazy initializer so the localStorage read // happens once, at mount, not on every render. @@ -369,14 +369,14 @@ export function MeasurementForm({ const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - // v1.4.27 MB3 — wire `aria-describedby` on every required field so + // v1.4.27 — wire `aria-describedby` on every required field so // screen readers announce the form-level error banner the moment it // surfaces. The banner already carries `role="alert"`, so the // descriptor relationship is purely additive. const errorId = useId(); const errorDescriptor = error ? errorId : undefined; - // v1.4.27 R4 RC2 — stable id so the portalled Save button can keep + // v1.4.27 — stable id so the portalled Save button can keep // its `` association via the HTML `form` attribute. const formId = useId(); @@ -494,7 +494,7 @@ export function MeasurementForm({ }); } - // v1.30.1 M3 — remember this type as the next mount's smart + // v1.30.1 — remember this type as the next mount's smart // default (deep links still override it). setLastUsedMeasurementType(type); @@ -760,7 +760,7 @@ export function MeasurementForm({ id="measuredAt" value={measuredAt} onChange={setMeasuredAt} - // v1.17 W1b — mirror the server-side plausibility bound: the + // v1.17 — mirror the server-side plausibility bound: the // picker cannot select a future instant. Matches the // `validateEntryInstant` refine on `measuredAt`. max={getDefaultMeasuredAtValue()} diff --git a/src/components/measurements/measurement-list.tsx b/src/components/measurements/measurement-list.tsx index b910129e8..044567114 100644 --- a/src/components/measurements/measurement-list.tsx +++ b/src/components/measurements/measurement-list.tsx @@ -126,7 +126,7 @@ import { import { formatDurationMinutes } from "@/lib/i18n/duration"; /** - * v1.4.37 W7c — cumulative HK types whose list view collapses to one + * v1.4.37 — cumulative HK types whose list view collapses to one * row per user-TZ day with an expand-chevron drill-down to the * per-sample chunks. Mirrors the server-side * `CUMULATIVE_DAY_SUM_TYPES` so the route's `groupBy=day` branch and @@ -147,7 +147,7 @@ interface Measurement { measuredAt: string; notes: string | null; /** - * v1.4.37 W7c — present only on collapsed daily rows returned by + * v1.4.37 — present only on collapsed daily rows returned by * `?type=…&groupBy=day`. Drives the expand chevron + the drill-down * fetch keyed by the user's calendar day. */ @@ -190,7 +190,7 @@ function sleepStageLabel( interface MeasurementListProps { onEdit?: (m: Measurement) => void; /** - * v1.4.15 phase-C5: optional callback wired by the parent page so + * v1.4.15: optional callback wired by the parent page so * the empty-state's "Add your first measurement" CTA opens the same * dialog the header button does. When undefined we hide the button * (fallback to the header CTA) instead of crashing. @@ -376,7 +376,7 @@ export function MeasurementList({ }), [rowDisplay], ); - // v1.28.42 (H3) — the desktop table and the mobile card list used to BOTH + // v1.28.42 — the desktop table and the mobile card list used to BOTH // render (only CSS `display` hid one), so a dense cumulative (up to ~5000 // rows) or sleep page built every row subtree twice — double the DOM, double // the per-row `Intl` work. Gate on the SSR-safe `useIsMobile` so only the @@ -459,7 +459,7 @@ export function MeasurementList({ // page-bounded (≤ PAGE_SIZE) and well under the server's 200 cap. const [selectedIds, setSelectedIds] = useState>(() => new Set()); - // v1.4.37 W7c — set of dayKeys whose drill-down is currently + // v1.4.37 — set of dayKeys whose drill-down is currently // expanded. Each key maps to a one-shot query that lazy-fetches // the per-sample rows for that calendar day on first open. const [expandedDayKeys, setExpandedDayKeys] = useState>( @@ -485,12 +485,12 @@ export function MeasurementList({ }); const [editError, setEditError] = useState(null); const [editDeleteDialogOpen, setEditDeleteDialogOpen] = useState(false); - // v1.4.27 MB3 — link the edit dialog error banner to the inputs via + // v1.4.27 — link the edit dialog error banner to the inputs via // `aria-describedby` so screen readers announce the validation // failure when the user submits an invalid value or timestamp. const editErrorId = useId(); const editErrorDescriptor = editError ? editErrorId : undefined; - // v1.4.27 R4 RC2 — Sheet-branch sticky-pinned footer slot. + // v1.4.27 — Sheet-branch sticky-pinned footer slot. const editFormId = useId(); const [editFooterEl, setEditFooterEl] = useState(null); @@ -570,7 +570,7 @@ export function MeasurementList({ clearSelection(); } - // v1.4.37 W7c — when the type filter is a cumulative HK type + // v1.4.37 — when the type filter is a cumulative HK type // (steps, active energy, distance, flights, daylight) the list // collapses to one row per user-TZ day via the route's // `groupBy=day` mode. The chevron in the row drills back into the @@ -785,7 +785,7 @@ export function MeasurementList({ dismissEdit(); }, onError: (err) => { - // v1.4.28 FB-B1 — the PUT route returns a 409 with + // v1.4.28 — the PUT route returns a 409 with // `meta.errorCode === "measurement.duplicate_timestamp"` when the // edit collides with an existing row's // `(type, measuredAt, source, sleepStage)` tuple. ApiError carries @@ -1037,7 +1037,7 @@ export function MeasurementList({ void refetch()} /> ) : !data?.measurements?.length ? ( <> - {/* v1.4.15 phase-C5: replaces the bare-text empty rectangle. + {/* v1.4.15: replaces the bare-text empty rectangle. Filter-aware copy distinguishes brand-new accounts ("no measurements yet") from filtered-out lists ("no measurements match this filter") and exposes the right CTA for each case. */} @@ -1172,7 +1172,7 @@ export function MeasurementList({ const isExpanded = isGrouped ? expandedDayKeys.has(m.dayKey as string) : false; - // v1.4.38 W-D P1-1 — stable drill-down id so the + // v1.4.38 — stable drill-down id so the // disclosure chevron can thread aria-controls to the // expanded panel. dayKey is unique per row when // grouped; fall back to m.id otherwise. @@ -1276,7 +1276,7 @@ export function MeasurementList({ )} {/* - v1.4.43 QoL (L8) — `formatDateOrRelative` + v1.4.43 — `formatDateOrRelative` renders timestamps inside the last 24 h as "vor 3 min" so a fresh entry visible on the dashboard briefing and on the @@ -1400,7 +1400,7 @@ export function MeasurementList({ const isExpanded = isGrouped ? expandedDayKeys.has(m.dayKey as string) : false; - // v1.4.38 W-D P1-1 — see desktop counterpart. + // v1.4.38 — see desktop counterpart. const drilldownId = `drilldown-mobile-${m.dayKey ?? m.id}`; const isSelected = selectedIds.has(m.id); return ( @@ -1505,7 +1505,7 @@ export function MeasurementList({ opens and the source beside it. */}

{/* - v1.4.43 QoL (L8) — see desktop + v1.4.43 — see desktop counterpart at the same `measuredAt` site. Relative under 24 h, absolute older. @@ -1727,7 +1727,7 @@ export function MeasurementList({ id="edit-measuredAt" value={editMeasuredAt} onChange={setEditMeasuredAt} - // v1.17 W1b — match the server bound: no future instant. + // v1.17 — match the server bound: no future instant. max={toDateTimeLocalValue(new Date().toISOString())} required aria-required="true" @@ -1794,7 +1794,7 @@ export function MeasurementList({ {/* - v1.4.43 QoL (L7) — `[Cancel] [Save]` order is + v1.4.43 — `[Cancel] [Save]` order is iOS-first intentional per Apple HIG, which puts the primary / confirmation action rightmost. iOS is the load-bearing mobile @@ -1877,7 +1877,7 @@ export function MeasurementList({ } /** - * v1.4.37 W7c — lazy drill-down for a collapsed cumulative-type day. + * v1.4.37 — lazy drill-down for a collapsed cumulative-type day. * Fires a separate `GET /api/measurements?type=…&dayKey=…` only when * the row is expanded; the result is rendered as a nested list of the * per-sample chunks that summed into the parent row's daily total. diff --git a/src/components/medications/__tests__/dose-strength-curve.test.tsx b/src/components/medications/__tests__/dose-strength-curve.test.tsx index a3eeae814..c1c99190d 100644 --- a/src/components/medications/__tests__/dose-strength-curve.test.tsx +++ b/src/components/medications/__tests__/dose-strength-curve.test.tsx @@ -1,5 +1,5 @@ /** - * v1.8.6 W4c — dose-strength (titration) curve tests. + * v1.8.6 — dose-strength (titration) curve tests. * * Pins the three honest data states and the pure point-builder: * - 0 dose-change rows → empty state, current-dose absent. diff --git a/src/components/medications/__tests__/injection-site-picker.test.tsx b/src/components/medications/__tests__/injection-site-picker.test.tsx index dce77a99e..c87ced942 100644 --- a/src/components/medications/__tests__/injection-site-picker.test.tsx +++ b/src/components/medications/__tests__/injection-site-picker.test.tsx @@ -12,7 +12,7 @@ import { primeLocales } from "@/__tests__/helpers/prime-locales"; primeLocales("de"); /** - * v1.4.25 W4d-tests — RTL-style coverage for the body-map injection + * v1.4.25 — RTL-style coverage for the body-map injection * picker deferred by phase W4d. * * The picker is a controlled SVG with 8 click-targets. Each visible diff --git a/src/components/medications/__tests__/intake-history-list-v2-row-actions.test.tsx b/src/components/medications/__tests__/intake-history-list-v2-row-actions.test.tsx index e336ca125..675f68997 100644 --- a/src/components/medications/__tests__/intake-history-list-v2-row-actions.test.tsx +++ b/src/components/medications/__tests__/intake-history-list-v2-row-actions.test.tsx @@ -1,5 +1,5 @@ /** - * v1.5.5 F-1 C-2 — `` row-actions + selection + * v1.5.5 — `` row-actions + selection * contract. * * Mirrors the SSR + `setQueryData` seed pattern the v1.4.36 W4a tests diff --git a/src/components/medications/__tests__/intake-history-list-v2.test.tsx b/src/components/medications/__tests__/intake-history-list-v2.test.tsx index 3bf35055e..a5594d94a 100644 --- a/src/components/medications/__tests__/intake-history-list-v2.test.tsx +++ b/src/components/medications/__tests__/intake-history-list-v2.test.tsx @@ -10,7 +10,7 @@ import { primeLocales } from "@/__tests__/helpers/prime-locales"; primeLocales("de"); /** - * v1.4.36 W4a — IntakeHistoryListV2 SSR smoke tests. + * v1.4.36 — IntakeHistoryListV2 SSR smoke tests. * * Mirrors the project convention: `renderToStaticMarkup` + * react-query data seeded via `QueryClient.setQueryData()`. Sort-toggle @@ -65,7 +65,7 @@ function seedIntake( const sortDir = opts.sortDir ?? "desc"; const limit = opts.limit ?? 25; const offset = opts.offset ?? 0; - // v1.4.37 W3 — the component pins `status:"completed"` on the + // v1.4.37 — the component pins `status:"completed"` on the // server-side query and threads it through the cache key. The seed // routes through the canonical factory so the params shape stays in // lockstep with the component's read. @@ -151,7 +151,7 @@ describe(" — populated rows", () => { }); it("renders the taken row with a green Check chip and the skipped row with an outline SkipForward chip", () => { - // v1.4.37 W3 — guards the regression the maintainer reported on 2026-05-17: + // v1.4.37 — guards the regression the maintainer reported on 2026-05-17: // before the fix, every row used the same secondary badge with the // "Eingenommen" label regardless of skipped status. The chip must // surface a distinct icon + label per branch and never label a @@ -201,7 +201,7 @@ describe(" — populated rows", () => { }); it("renders the skipped chip in German without leaking the Eingenommen label", () => { - // v1.4.37 W3 — UAT echo. The maintainer reported a German row labelled + // v1.4.37 — UAT echo. The maintainer reported a German row labelled // both "übersprungen" and "eingenommen". The fix means a // skipped-only fixture renders "Übersprungen" exactly once and // "Eingenommen" zero times. diff --git a/src/components/medications/__tests__/intake-history-preview-bulk-toolbar.test.tsx b/src/components/medications/__tests__/intake-history-preview-bulk-toolbar.test.tsx index dc52307ae..572495c0a 100644 --- a/src/components/medications/__tests__/intake-history-preview-bulk-toolbar.test.tsx +++ b/src/components/medications/__tests__/intake-history-preview-bulk-toolbar.test.tsx @@ -1,5 +1,5 @@ /** - * v1.5.5 F-1 C-2 — `` smoke tests. + * v1.5.5 — `` smoke tests. * * The preview mounts in SSR with an empty selection so the bulk- * delete toolbar stays hidden by default. Selection / dialog flips diff --git a/src/components/medications/__tests__/medication-card-glp1.test.tsx b/src/components/medications/__tests__/medication-card-glp1.test.tsx index 9cddeffc1..5a405f085 100644 --- a/src/components/medications/__tests__/medication-card-glp1.test.tsx +++ b/src/components/medications/__tests__/medication-card-glp1.test.tsx @@ -13,7 +13,7 @@ import { primeLocales } from "@/__tests__/helpers/prime-locales"; primeLocales("de"); /** - * v1.4.25 W4d-tests — RTL-style component coverage for the GLP-1 + * v1.4.25 — RTL-style component coverage for the GLP-1 * medication-card variant deferred by phase W4d. * * The project never installed `@testing-library/react`; the convention @@ -190,7 +190,7 @@ afterEach(() => { describe(" — GLP-1 variant rendering", () => { it("renders the GLP-1 variant when treatmentClass === 'GLP1' is active", () => { - // v1.4.37 W4b — the category-label slot now mirrors the generic + // v1.4.37 — the category-label slot now mirrors the generic // card (real `medication.category` lookup) so Ramipril and // Mounjaro share the same row shape. The variant differentiator // moved into the GLP-1-specific rows below the header (last/next @@ -213,14 +213,14 @@ describe(" — GLP-1 variant rendering", () => { // Category is OTHER → "Other" badge wins. expect(html).toContain("Other"); expect(html).not.toContain("GLP-1 injection"); - // v1.4.28 FB-G1 — the Syringe glyph + middle-dot separator on the + // v1.4.28 — the Syringe glyph + middle-dot separator on the // list row are gone. The list row reads as the canonical two-line // shape: `{name} {dose}` on line 1, class label on line 2. expect(html).not.toMatch(/lucide-syringe/i); }); it("collapses edit / history into a single overflow kebab + navigable header", () => { - // v1.7.2 W3 — the former header icon-buttons collapse into one + // v1.7.2 — the former header icon-buttons collapse into one // kebab; the card header links to the detail page. The menu items // (edit / history) render inside the portalled dropdown content, so // SSR markup carries only the kebab trigger + the header link, not @@ -302,7 +302,7 @@ describe(" — GLP-1 variant rendering", () => { expect(html).toContain("Mounjaro"); expect(html).toContain("7.5 mg"); - // FB-G1 — the GLP-1 row drops the middle-dot separator; the dose now + // The GLP-1 row drops the middle-dot separator; the dose now // rides its OWN muted line under the name (the shared // `` primitive), localised via `formatDose`. expect(html).toContain('data-slot="medication-card-header-dose"'); @@ -466,7 +466,7 @@ describe(" — GLP-1 variant rendering", () => { }); it("folds the side-effect quick-log into the same overflow kebab", () => { - // v1.7.2 W3 — the side-effect quick-log folds into the SAME overflow + // v1.7.2 — the side-effect quick-log folds into the SAME overflow // menu as edit / history / advanced, so the GLP-1 header carries one // kebab whether or not the hand-off is wired (parity with the generic // card). SSR doesn't open Radix Portal content, so we assert the @@ -504,7 +504,7 @@ describe(" — GLP-1 variant rendering", () => { }); it("still renders the single kebab when onLogSideEffect is not supplied", () => { - // v1.7.2 W3 — the kebab is unconditional now (it always carries edit + // v1.7.2 — the kebab is unconditional now (it always carries edit // / history / advanced); the side-effect item is the only opt-in // part. Mounjaro and Ramipril therefore share the same one-kebab // header shape regardless of the hand-off prop. @@ -601,7 +601,7 @@ describe(" — GLP-1 variant rendering", () => { "de", ); - // v1.4.37 W4b — the German category badge now comes from the + // v1.4.37 — the German category badge now comes from the // shared category-label lookup (Sonstiges for OTHER) instead of // the hard-coded "GLP-1-Injektion" label, so the GLP-1 card stays // visually symmetric with the generic card on the medications list. diff --git a/src/components/medications/__tests__/medication-card-symmetry.test.tsx b/src/components/medications/__tests__/medication-card-symmetry.test.tsx index c0bc21fc6..5b8ab213f 100644 --- a/src/components/medications/__tests__/medication-card-symmetry.test.tsx +++ b/src/components/medications/__tests__/medication-card-symmetry.test.tsx @@ -10,12 +10,12 @@ import { } from "@/components/medications/glp1-medication-card"; /** - * v1.4.37 W4b — symmetry contract between the generic medication card + * v1.4.37 — symmetry contract between the generic medication card * (Ramipril/non-GLP-1) and the GLP-1 variant (Mounjaro). The two cards * must read as the same row shape. These tests pin the load-bearing * structural seams so a future refactor can't quietly regress parity. * - * v1.7.2 W3 — the four former header icon-buttons (open / edit / history + * v1.7.2 — the four former header icon-buttons (open / edit / history * / advanced) collapsed into a SINGLE overflow kebab on both cards; the * card header itself links to the detail page (the former chevron * target). The GLP-1 card folds its "Log side effect" item into the same @@ -179,7 +179,7 @@ describe("medication card symmetry — Ramipril vs Mounjaro", () => { [ramiprilHtml, `/medications/${ramipril.id}`], [mounjaroHtml, `/medications/${mounjaro.id}`], ] as const) { - // v1.7.2 W3 — exactly one overflow kebab; the four former icon + // v1.7.2 — exactly one overflow kebab; the four former icon // buttons (chevron / pencil / history / sliders) are gone from the // header. The menu items render on open (portalled), not in SSR. expect(html).toContain('aria-label="More options"'); diff --git a/src/components/medications/__tests__/side-effects-section.test.tsx b/src/components/medications/__tests__/side-effects-section.test.tsx index f5c43705e..03df2c4dc 100644 --- a/src/components/medications/__tests__/side-effects-section.test.tsx +++ b/src/components/medications/__tests__/side-effects-section.test.tsx @@ -45,7 +45,7 @@ import { primeLocales } from "@/__tests__/helpers/prime-locales"; primeLocales("de"); /** - * v1.4.25 W19d — SideEffectsSection SSR smoke tests. + * v1.4.25 — SideEffectsSection SSR smoke tests. * * The project never installed `@testing-library/react`; the * convention is `renderToStaticMarkup` + assertions against the SSR @@ -266,7 +266,7 @@ describe(" — surface render", () => { seedSideEffects(client, "med-1", []); const html = render(, client); expect(html).toContain("Side effects"); - // v1.4.28 FB-F1 — the CTA dropped its qualifier so the chip stops + // v1.4.28 — the CTA dropped its qualifier so the chip stops // overflowing the side-effects card on narrow viewports. The // section title carries the context. expect(html).toContain(">Log<"); diff --git a/src/components/medications/dose-strength-curve.tsx b/src/components/medications/dose-strength-curve.tsx index 4b08d8082..a21a2fe17 100644 --- a/src/components/medications/dose-strength-curve.tsx +++ b/src/components/medications/dose-strength-curve.tsx @@ -1,7 +1,7 @@ "use client"; /** - * v1.8.6 W4c — GLP-1 dose-strength (titration) curve. + * v1.8.6 — GLP-1 dose-strength (titration) curve. * * Replaces the EMA *reference*-ladder block that showed no values when * the medication's brand fell outside the catalog. This surface plots diff --git a/src/components/medications/drug-level-chart.tsx b/src/components/medications/drug-level-chart.tsx index 86e2fdcd7..d53bddc93 100644 --- a/src/components/medications/drug-level-chart.tsx +++ b/src/components/medications/drug-level-chart.tsx @@ -241,7 +241,7 @@ export function DrugLevelChart({ medication, asOf }: DrugLevelChartProps) { // previously threaded a window-length override retired with FB-A2. const stepHours = pickSampleStepHours(WINDOW_HOURS_BEFORE); - // UI-H1 — the chart mounts inside the shared + // The chart mounts inside the shared // `` chrome alongside Titration / Scheduling // / SideEffects so the `/medications/[id]/history` page reads on one // section recipe. The drug-INN qualifier rides the headerExtras slot @@ -260,7 +260,7 @@ export function DrugLevelChart({ medication, asOf }: DrugLevelChartProps) { const body = !drugId ? ( ) : isLoading ? ( - // v1.4.43 W11-L6 — match the loaded chart height (240 px) so the + // v1.4.43 — match the loaded chart height (240 px) so the // dashboard tile doesn't reflow when the levels render.

- {/* v1.4.27 MB6 CF-16 — dropped a stray empty `` child + {/* v1.4.27 — dropped a stray empty `` child that previously declared no content and rendered an invisible SVG node beneath the x-axis. */} ` above the chart is the single source of @@ -516,7 +516,7 @@ function ChartBody({ * Re-export the shared parser so the component-test file * (which historically imported `parseDoseMg` from this surface) * keeps working without coupling to the helper module path. v1.4.25 - * W21 Fix-N — single implementation now lives in + * Single implementation now lives in * `src/lib/medications/dose-string.ts`. */ export { parseDoseMg }; diff --git a/src/components/medications/glp1-medication-card.tsx b/src/components/medications/glp1-medication-card.tsx index cf4068cdf..72a73b447 100644 --- a/src/components/medications/glp1-medication-card.tsx +++ b/src/components/medications/glp1-medication-card.tsx @@ -44,7 +44,7 @@ import { useMedicationComplianceSummary } from "@/lib/queries/use-medication-com import { DEFAULT_TIMEZONE } from "@/lib/tz/format"; /** - * v1.4.25 W4d — GLP-1 medication card variant. + * v1.4.25 — GLP-1 medication card variant. * * Maintainer directive 2026-05-14: NO chart on the medication card. The card * stays text-rich (drug name + current dose, last/next injection, @@ -263,7 +263,7 @@ export function Glp1MedicationCard({ refetch: refetchCompliance, } = useMedicationComplianceSummary(medication.id); - // v1.4.37 W4b — same reminder-thresholds source as the generic + // v1.4.37 — same reminder-thresholds source as the generic // medication card so the take-now / overdue / very-overdue pill // tiers identically on both surfaces. const { data: thresholds } = useQuery({ @@ -348,7 +348,7 @@ export function Glp1MedicationCard({ // verbatim with the generic card. Defaults to "upcoming" (calm). const doseStatus = display?.currentDose.status ?? "upcoming"; - // v1.4.37 W4b — symmetric take-now / overdue pill with the generic + // v1.4.37 — symmetric take-now / overdue pill with the generic // card. Sort schedules by `windowStart` so the most-actionable // earliest window wins when multiple GLP-1 windows overlap (rare // today; the parity is what matters). diff --git a/src/components/medications/intake-edit-dialog.tsx b/src/components/medications/intake-edit-dialog.tsx index afe03c1ea..48da1b6f4 100644 --- a/src/components/medications/intake-edit-dialog.tsx +++ b/src/components/medications/intake-edit-dialog.tsx @@ -1,7 +1,7 @@ "use client"; /** - * v1.5.5 F-1 C-2 — per-row Bearbeiten dialog on the detail-page + * v1.5.5 — per-row Bearbeiten dialog on the detail-page * intake-history preview. * * Restores the row-level edit affordance the v1.5.4 flat-form @@ -12,7 +12,7 @@ * medication cache bundle so the inline compliance tile + dashboard * chart converge in the same tick. * - * v1.15.19 (audit P0-4) — date-typo guardrails on the edit path: + * v1.15.19 — date-typo guardrails on the edit path: * the picker is capped at "now" (mirrors the add dialog), the row's * scheduled slot renders under the input so the user sees what they * are editing against, and a non-blocking hint appears when the diff --git a/src/components/medications/intake-history-list-v2.tsx b/src/components/medications/intake-history-list-v2.tsx index ebfc211dc..c89c77180 100644 --- a/src/components/medications/intake-history-list-v2.tsx +++ b/src/components/medications/intake-history-list-v2.tsx @@ -48,7 +48,7 @@ import { import { apiGet } from "@/lib/api/api-fetch"; /** - * v1.4.36 W4a — Lite intake-history surface. + * v1.4.36 — Lite intake-history surface. * * The 886-LoC v1 was retired in v1.4.28 (commit 8c81af10) with the * note "no other consumer". The standalone medication-history page @@ -103,7 +103,7 @@ interface IntakeListResponse { } /** - * v1.5.5 F-1 C-2 — optional selection contract. When the wrapper + * v1.5.5 — optional selection contract. When the wrapper * passes `selection`, each row renders a leading `` that * mirrors the selected set. Single mode hides the bulk-toolbar; multi * mode wires through to ``. The contract lives on @@ -121,7 +121,7 @@ interface IntakeHistoryListV2Props { /** Override the default page size — primarily for tests. */ pageSize?: number; /** - * v1.5.5 F-1 C-2 — per-row Bearbeiten callback. When provided the + * v1.5.5 — per-row Bearbeiten callback. When provided the * row renders a kebab `` with an Edit action. v1.5.6 * G-1 §9 Q1: threads the whole `IntakeEvent` (not just the id) so * the edit dialog seeds from the row's real `takenAt` / `skipped` @@ -129,12 +129,12 @@ interface IntakeHistoryListV2Props { */ onEditIntake?: (event: IntakeEvent) => void; /** - * v1.5.5 F-1 C-2 — per-row Löschen callback. When provided the row + * v1.5.5 — per-row Löschen callback. When provided the row * renders a kebab `` with a Delete action. */ onDeleteIntake?: (eventId: string) => void; /** - * v1.5.5 F-1 C-2 — selection contract; see `IntakeHistoryListV2Selection`. + * v1.5.5 — selection contract; see `IntakeHistoryListV2Selection`. */ selection?: IntakeHistoryListV2Selection; /** @@ -151,7 +151,7 @@ type SortKey = IntakeHistorySortKey; const DEFAULT_PAGE_SIZE = 25; /** - * v1.4.37 W3 — pinned to "completed" for the detail-page surface so + * v1.4.37 — pinned to "completed" for the detail-page surface so * planned / missed rows never leak into the table. Lifted to a * module-level constant so the queryKey stays stable across renders * and `setQueryData` in tests can pre-seed the same shape. @@ -189,7 +189,7 @@ export function IntakeHistoryListV2({ // to the stacked list after hydration. const isMobile = useIsMobile("md"); - // v1.5.5 F-1 C-2 — the kebab + checkbox columns are gated by the + // v1.5.5 — the kebab + checkbox columns are gated by the // caller-passed callbacks. The list never assumes a wrapper; it // renders the extra columns only when explicitly opted-in. const showRowActions = Boolean(onEditIntake || onDeleteIntake); @@ -352,7 +352,7 @@ export function IntakeHistoryListV2({ {events.map((event) => { - // v1.4.37 W3 — `status=completed` server filter + // v1.4.37 — `status=completed` server filter // guarantees that every row is either a taken or a // skipped event. We derive the branch from the data // itself rather than trusting a single `skipped` diff --git a/src/components/medications/log-intake-dialog.tsx b/src/components/medications/log-intake-dialog.tsx index e985ab0b2..3c2918c74 100644 --- a/src/components/medications/log-intake-dialog.tsx +++ b/src/components/medications/log-intake-dialog.tsx @@ -218,7 +218,7 @@ export function LogIntakeDialog({ t, queryClient, recordName, - // v1.30.1 M7 — a backdated/manual intake now gets the same + // v1.30.1 — a backdated/manual intake now gets the same // Undo affordance the card's take/skip path already has. undoIntake: (eventId) => runUndoIntake({ diff --git a/src/components/medications/medication-card-header.tsx b/src/components/medications/medication-card-header.tsx index 74551ce95..b0334a120 100644 --- a/src/components/medications/medication-card-header.tsx +++ b/src/components/medications/medication-card-header.tsx @@ -7,7 +7,7 @@ import { Badge } from "@/components/ui/badge"; import { CardHeader, CardTitle } from "@/components/ui/card"; /** - * v1.4.28 FB-G1 — shared medication-list row header. + * v1.4.28 — shared medication-list row header. * * The generic `` and the `` carry * the same two-line row shape on the medications list page. Both rows @@ -24,7 +24,7 @@ import { CardHeader, CardTitle } from "@/components/ui/card"; * ride on line 2 alongside the class label so a narrow viewport keeps a * single inline run. * - * v1.7.2 W3 — when `href` is set the name + dose + category region + * v1.7.2 — when `href` is set the name + dose + category region * becomes a Link to the medication detail page (the former chevron * target). The kebab in `actions` is a sibling outside the Link so * opening the menu never also navigates. @@ -57,7 +57,7 @@ export interface MedicationCardHeaderProps { stateBadges?: ReactNode; actions?: ReactNode; /** - * v1.7.2 W3 — when set, the name + dose + category region links to the + * v1.7.2 — when set, the name + dose + category region links to the * medication detail page so the card body is tappable/navigable. The * `actions` kebab stays a sibling outside the Link. */ @@ -91,7 +91,7 @@ export function MedicationCardHeader({ Vorsorge / lab-list rows pass `dose=""` and get only the category badge. The unit is already localised by `formatDose` at the call site (the raw `Medication.dose` carries the English unit key). - D-H6 — state badges (without-notification, paused-since, inactive) + State badges (without-notification, paused-since, inactive) ride their OWN row below (see `stateBadges`) so the canonical row stays two lines at 320 px even for a drug carrying a state badge. */}
diff --git a/src/components/medications/medication-card-menu.tsx b/src/components/medications/medication-card-menu.tsx index 1021232ca..b26c9c0b1 100644 --- a/src/components/medications/medication-card-menu.tsx +++ b/src/components/medications/medication-card-menu.tsx @@ -1,7 +1,7 @@ "use client"; /** - * v1.7.2 W3 — shared medication-card overflow menu. + * v1.7.2 — shared medication-card overflow menu. * * Both the generic `` and the `` * collapsed their former four header icon-buttons (open / edit / history diff --git a/src/components/medications/medication-card.tsx b/src/components/medications/medication-card.tsx index 56a5e00a5..7c7a1f5ed 100644 --- a/src/components/medications/medication-card.tsx +++ b/src/components/medications/medication-card.tsx @@ -69,7 +69,7 @@ interface Medication { /** v1.40 — the label of a custom category; null for a built-in one. */ categoryLabel?: string | null; /** - * v1.4.25 W4d — Prisma treatment-class discriminator. When set to + * v1.4.25 — Prisma treatment-class discriminator. When set to * "GLP1" the parent should render the {@link Glp1MedicationCard} * variant instead of this generic card. The field is optional for * backwards compatibility with mocks. @@ -411,7 +411,7 @@ export function MedicationCard({ /> ); - // v1.7.2 W3 — the four former header icon-buttons (open / edit / + // v1.7.2 — the four former header icon-buttons (open / edit / // history / advanced) collapse into a single overflow kebab. The card // header itself links to the detail page (the former chevron target). const headerActions = ( diff --git a/src/components/medications/medication-detail-section.tsx b/src/components/medications/medication-detail-section.tsx index 3898c3742..d8ec87395 100644 --- a/src/components/medications/medication-detail-section.tsx +++ b/src/components/medications/medication-detail-section.tsx @@ -3,7 +3,7 @@ import { type ReactNode } from "react"; /** - * v1.4.25 W21 Fix-N — section wrapper for the medication-detail panels + * v1.4.25 — section wrapper for the medication-detail panels * (Titration, Scheduling, SideEffects, DrugLevelChart). The earlier * sections hand-rolled the same chrome — border + header row + dotted * divider + body padding — and drifted on padding, border opacity, and diff --git a/src/components/medications/medication-detail-summary.tsx b/src/components/medications/medication-detail-summary.tsx index 9e043be23..5cabdaf00 100644 --- a/src/components/medications/medication-detail-summary.tsx +++ b/src/components/medications/medication-detail-summary.tsx @@ -1,7 +1,7 @@ "use client"; /** - * v1.7.2 W3 — compact, non-editable medication-detail header. + * v1.7.2 — compact, non-editable medication-detail header. * * Supersedes the v1.7.0 `` (Edit / History / * Advanced action row) and the `` "Rhythmus" block. diff --git a/src/components/medications/sections/__tests__/notifications-section.test.tsx b/src/components/medications/sections/__tests__/notifications-section.test.tsx index 34380a55e..537bd6e12 100644 --- a/src/components/medications/sections/__tests__/notifications-section.test.tsx +++ b/src/components/medications/sections/__tests__/notifications-section.test.tsx @@ -1,5 +1,5 @@ /** - * v1.5.5 F-1 H-1 — pin the split title ids on the notifications row. + * v1.5.5 — pin the split title ids on the notifications row. * * The earlier single `TITLE_ID` constant landed on both the section * heading (``) and the inner diff --git a/src/components/medications/sections/api-tokens-row.tsx b/src/components/medications/sections/api-tokens-row.tsx index 6ce63e5aa..9f50d2e7b 100644 --- a/src/components/medications/sections/api-tokens-row.tsx +++ b/src/components/medications/sections/api-tokens-row.tsx @@ -66,7 +66,7 @@ export function ApiTokensRow({ // stays out of the default-visible DOM until the user opts in). const [examplesOpen, setExamplesOpen] = useState(false); - // v1.5.5 F-1 H-2 — single source of truth for the per-medication + // v1.5.5 — single source of truth for the per-medication // api-endpoint key. The earlier inline `useMemo` minted the same // tuple as a bare array, slipping past `healthlog/queryKey-factory`. // Reading from the factory keeps the bundle invalidation + @@ -205,7 +205,7 @@ export function ApiTokensRow({

{t("medications.detail.api.caption", { name: medicationName })}

- {/* R26 — the endpoint URL is an inline read-only field with a copy + {/* The endpoint URL is an inline read-only field with a copy icon on the right rather than a separate "URL kopieren" button. The `` carries the value; the icon button copies it. */}
`'s heading // (`titleId` prop) and the inner `` carrying the switch's diff --git a/src/components/medications/side-effects-section.tsx b/src/components/medications/side-effects-section.tsx index 68d897770..114c1b23b 100644 --- a/src/components/medications/side-effects-section.tsx +++ b/src/components/medications/side-effects-section.tsx @@ -52,7 +52,7 @@ import { apiDelete, apiFetchEnvelope, apiPost } from "@/lib/api/api-fetch"; import { queryKeys } from "@/lib/query-keys"; /** - * v1.4.25 W19d — GLP-1 side-effect section for the medication detail + * v1.4.25 — GLP-1 side-effect section for the medication detail * page. Sits below the drug-level chart and above the intake history * timeline. Mounted only for `treatmentClass === "GLP1"` rows. * @@ -64,7 +64,7 @@ import { queryKeys } from "@/lib/query-keys"; * each row showing category badge + entry label + severity chip + * timestamp + delete button + collapsed notes. * - * v1.4.25 W21 Fix-N — wraps the shared `` so + * v1.4.25 — wraps the shared `` so * the three sibling sections share one chrome contract, and swaps the * native `` so the picker * matches the rest of the form library (focus rings, keyboard nav, @@ -138,7 +138,7 @@ export function SideEffectsSection({ medicationId }: SideEffectsSectionProps) { const [severity, setSeverity] = useState<1 | 2 | 3 | 4 | 5 | null>(null); const [notes, setNotes] = useState(""); const [formError, setFormError] = useState(null); - // v1.4.27 R4 RC2 — sticky-pin the Save / Cancel row by portalling it + // v1.4.27 — sticky-pin the Save / Cancel row by portalling it // into the `` footer slot. The submit button keeps // its `` association via the HTML `form` attribute. const [footerEl, setFooterEl] = useState(null); @@ -192,7 +192,7 @@ export function SideEffectsSection({ medicationId }: SideEffectsSectionProps) { severity: number; notes: string | null; }) => { - // v1.4.25 W21 Fix-N (code-M6) — drop client-side `category` + // v1.4.25 (code-M6) — drop client-side `category` // from the wire payload. The server derives it from the entry // via `categoryForEntry`. Backwards-compatible with older // clients during the cut window (route accepts both shapes). @@ -352,7 +352,7 @@ export function SideEffectsSection({ medicationId }: SideEffectsSectionProps) { className="bg-muted/30 flex items-start justify-between gap-2 rounded-md px-2.5 py-2" >
- {/* v1.4.28 FB-F2 — fixed-width date slot pinned to + {/* v1.4.28 — fixed-width date slot pinned to the right of the row meta so the date stamp keeps its column across rows. The category badge + entry label + severity chip share the left-most @@ -376,7 +376,7 @@ export function SideEffectsSection({ medicationId }: SideEffectsSectionProps) {

( @@ -288,7 +288,7 @@ export async function runLogIntake(deps: { : "medications.intakeToastTaken", { name: medication.name }, ); - // v1.30.1 M7 — only pass a second `toast.success` argument when there's + // v1.30.1 — only pass a second `toast.success` argument when there's // a real Undo action to attach; keeps the no-undo call signature // identical to the pre-fix behaviour (existing unit tests assert the // single-argument call). diff --git a/src/components/medications/wizard/__tests__/wizard-payload.test.ts b/src/components/medications/wizard/__tests__/wizard-payload.test.ts index f8edcbfa3..c6c15e826 100644 --- a/src/components/medications/wizard/__tests__/wizard-payload.test.ts +++ b/src/components/medications/wizard/__tests__/wizard-payload.test.ts @@ -194,7 +194,7 @@ describe("validateStep", () => { }); /** - * v1.8.6 W4b — `firstInvalidIndex` is the multi-step forward lookahead + * v1.8.6 — `firstInvalidIndex` is the multi-step forward lookahead * that powers the dot stepper's jump gate (`goToStep`) and its * `reachableUntil` ceiling. The dialog's rule: * - backward jumps are always allowed; diff --git a/src/components/medications/wizard/medication-wizard-dialog.tsx b/src/components/medications/wizard/medication-wizard-dialog.tsx index 9369fdef0..d96cbcddc 100644 --- a/src/components/medications/wizard/medication-wizard-dialog.tsx +++ b/src/components/medications/wizard/medication-wizard-dialog.tsx @@ -88,7 +88,7 @@ export interface MedicationWizardDialogProps { /** Fires with the medication id on a successful create / save. */ onSuccess?: (id: string) => void; /** - * v1.39 (C2) — whether a create lands on the new medication's page. The + * v1.39 — whether a create lands on the new medication's page. The * medications list wants that; the setup flow's first-result screen mounts * the wizard inline and shows the result where it stands, so it opts out. */ @@ -190,7 +190,7 @@ function WizardDialogShell({ const isLastStep = displayStep === totalSteps; const isFirstStep = displayStep === 1; - // v1.8.6 W4b — forward-reachability ceiling for the dot stepper. The + // v1.8.6 — forward-reachability ceiling for the dot stepper. The // dot at path-index `j` is reachable iff every gate from the active // slot up to (but not including) `j` passes. `firstInvalidIndex` // returns the first failing slot from `fallbackIndex`; every slot up @@ -246,7 +246,7 @@ function WizardDialogShell({ } }, [payload.mode, payload.cadence.kind, step]); - // v1.8.6 W4b — jump-to-arbitrary-step from the dot stepper. Backward + // v1.8.6 — jump-to-arbitrary-step from the dot stepper. Backward // jumps are always allowed; a forward jump is gated on EVERY // intervening path gate validating (a multi-step lookahead over the // single-step `validateStep`). A jump that lands on the final review @@ -384,7 +384,7 @@ function WizardDialogShell({ total: totalSteps, }); - // v1.8.6 W4b — short per-slot labels for the dot stepper. Keyed by + // v1.8.6 — short per-slot labels for the dot stepper. Keyed by // raw step number so the path-aware `stepList` maps straight through. const stepperLabels = useMemo(() => { const map: Record = {}; @@ -476,7 +476,7 @@ function WizardDialogShell({ aria-busy={submitting || undefined} className="flex flex-col" > - {/* v1.8.6 W4b — dot stepper replaces the v1.5.5 continuous + {/* v1.8.6 — dot stepper replaces the v1.5.5 continuous `` strip + "Step X of Y" caption. The dot row + body share the same outer padding so they read as one column; the right padding leaves room for the shell's @@ -561,7 +561,7 @@ function WizardDialogShell({ {stepTitle}

{stepSubline}

- {/* v1.8.6 W4b — muted "what's next" hint so the user + {/* v1.8.6 — muted "what's next" hint so the user knows what the Next tap leads to (or, on the final slot, that Next saves). */}

` bar + "Step X of Y" caption with * one dot per path slot (so the one-shot path shows 5 dots, the diff --git a/src/components/module-list/module-order-editor.tsx b/src/components/module-list/module-order-editor.tsx index 93a045e2f..a589d7407 100644 --- a/src/components/module-list/module-order-editor.tsx +++ b/src/components/module-list/module-order-editor.tsx @@ -25,7 +25,7 @@ import { prefersReducedMotion } from "@/lib/charts/reduced-motion"; import { useTranslations } from "@/lib/i18n/context"; /** - * v1.18.6 (W8 / MOD-03) — generic, controlled reorder list shared by the + * v1.18.6 — generic, controlled reorder list shared by the * Vorsorge / Illness / Labs settings pages. * * A single section (no active/inactive split — the secondary modules have a diff --git a/src/components/module-list/module-view-toggle.tsx b/src/components/module-list/module-view-toggle.tsx index 139a55845..9a9205de7 100644 --- a/src/components/module-list/module-view-toggle.tsx +++ b/src/components/module-list/module-view-toggle.tsx @@ -7,7 +7,7 @@ import { ViewToggle } from "@/components/ui/view-toggle"; import type { ModuleListView } from "@/lib/module-list-prefs"; /** - * v1.18.6 (W8 / MOD-03) — icon-only card/list view toggle shared by the + * v1.18.6 — icon-only card/list view toggle shared by the * Vorsorge / Illness / Labs settings pages. Thin wrapper over the shared * `ViewToggle` primitive (mirrors the medication module's * `MedicationViewToggle`, which is the same wrapper switching between a diff --git a/src/components/monitoring/web-vitals-reporter.tsx b/src/components/monitoring/web-vitals-reporter.tsx index f475c6b36..79eca5564 100644 --- a/src/components/monitoring/web-vitals-reporter.tsx +++ b/src/components/monitoring/web-vitals-reporter.tsx @@ -6,7 +6,7 @@ import { useReportWebVitals } from "next/web-vitals"; import { apiFetchRaw } from "@/lib/api/api-fetch"; /** - * v1.4.28 R3d — client-side mount that pipes Web Vitals measurements + * v1.4.28 — client-side mount that pipes Web Vitals measurements * to `/api/internal/web-vitals`. * * Next 16 ships `useReportWebVitals` as the supported hook for @@ -14,7 +14,7 @@ import { apiFetchRaw } from "@/lib/api/api-fetch"; * navigation. The hook fires once per metric per page load, so a * heavy navigation can produce up to six beacons per route change. * - * v1.4.33 F19 — the reporter previously posted EVERY measurement on + * v1.4.33 — the reporter previously posted EVERY measurement on * EVERY page-load. In production the runtime audit captured * `POST /api/internal/web-vitals 429` for almost every metric after * the first navigation: six beacons × N navigations burned through diff --git a/src/components/mood/manage/tag-groups-card.tsx b/src/components/mood/manage/tag-groups-card.tsx index 6c87547c0..9efec6176 100644 --- a/src/components/mood/manage/tag-groups-card.tsx +++ b/src/components/mood/manage/tag-groups-card.tsx @@ -149,7 +149,7 @@ export function TagGroupsCard({ catalog }: TagGroupsCardProps) { reorderGroups(current, nextOrder), ); try { - // v1.32.22 (R5b) — echo the base token this order was based on. The two + // v1.32.22 — echo the base token this order was based on. The two // cards on this page write DIFFERENT fields of the same blob (group order // vs tag placements); a stale merge would resurrect the other card's // overwritten field, which the token now 409s instead. Seed the token diff --git a/src/components/mood/manage/tag-manager-card.tsx b/src/components/mood/manage/tag-manager-card.tsx index 9f39d4bae..b76e65029 100644 --- a/src/components/mood/manage/tag-manager-card.tsx +++ b/src/components/mood/manage/tag-manager-card.tsx @@ -147,7 +147,7 @@ export function TagManagerCard({ catalog }: TagManagerCardProps) { const rollback = await snapshotManageCache(queryClient); updateManageCache(queryClient, () => mergeBack(catalog, nextCatalog)); try { - // v1.32.22 (R5b) — echo + seed the layout token (see `putGroupOrder` in + // v1.32.22 — echo + seed the layout token (see `putGroupOrder` in // the groups card): the sibling group-order card writes the same blob, so // a stale placements merge is exactly the overwrite the token 409s. const resolved = await apiPut( @@ -264,7 +264,7 @@ export function TagManagerCard({ catalog }: TagManagerCardProps) { } // Both kinds pin the resulting per-group order in the layout blob // so the resolved tree matches what the user just saw happen. - // v1.32.22 (R5b) — echo + seed the layout token so a concurrent write to + // v1.32.22 — echo + seed the layout token so a concurrent write to // the same blob 409s instead of clobbering. const resolved = await apiPut( "/api/mood/tags/layout", diff --git a/src/components/mood/manage/use-mood-tag-manage.ts b/src/components/mood/manage/use-mood-tag-manage.ts index 0d0529a3d..c21793381 100644 --- a/src/components/mood/manage/use-mood-tag-manage.ts +++ b/src/components/mood/manage/use-mood-tag-manage.ts @@ -63,7 +63,7 @@ export interface ManageCatalog { } /** - * v1.32.22 (R5b) — the resolved `GET/PUT /api/mood/tags/layout` response. + * v1.32.22 — the resolved `GET/PUT /api/mood/tags/layout` response. * `updatedAt` is the optimistic-concurrency token the two management cards * echo on their next write; no active query reads this key (it is only ever * invalidated), so the token is seeded straight from each write response into diff --git a/src/components/mood/mood-form.tsx b/src/components/mood/mood-form.tsx index bced7ad4f..2fecf8747 100644 --- a/src/components/mood/mood-form.tsx +++ b/src/components/mood/mood-form.tsx @@ -86,7 +86,7 @@ const MOOD_LEVELS = [ { value: "LAUSIG", score: 1, labelKey: "mood.levelLausig" }, ] as const; -// v1.8.5 (C1) — note free-text limit. Mirrors the `maxLength` on the +// v1.8.5 — note free-text limit. Mirrors the `maxLength` on the // textarea and the server-side Zod bound; surfaced as a character counter // so the truncation at the cap is no longer silent. const NOTE_MAX_LENGTH = 500; @@ -95,7 +95,7 @@ interface MoodFormProps { onSuccess?: () => void; onCancel?: () => void; /** - * v1.4.27 R4 RC2 — when the form is mounted inside a + * v1.4.27 — when the form is mounted inside a * `` the caller can pass the sheet's footer slot * element here. The action-row (kebab + Cancel + Save) is portalled * into that slot so the bottom-sheet branch can sticky-pin it; the @@ -144,7 +144,7 @@ export function MoodForm({ const [tagsInput, setTagsInput] = useState(""); // v1.8.5 — structured-tag keys picked from the taxonomy catalog. const [tagKeys, setTagKeys] = useState([]); - // v1.8.5 (C1) — first-class free-text note. The model + API already + // v1.8.5 — first-class free-text note. The model + API already // accepted `note`; the web form was the only surface that couldn't // write it. const [note, setNote] = useState(""); @@ -188,11 +188,11 @@ export function MoodForm({ ); } - // v1.4.27 MB3 — error banner descriptor for the timestamp input. + // v1.4.27 — error banner descriptor for the timestamp input. const errorId = useId(); const errorDescriptor = error ? errorId : undefined; - // v1.4.27 R4 RC2 — stable form id so the portalled Save button can + // v1.4.27 — stable form id so the portalled Save button can // associate with this `` element via the HTML `form` attribute // even when DOM-mounted inside the `` footer slot. const formId = useId(); @@ -417,7 +417,7 @@ export function MoodForm({ id="mood-logged-at" value={moodLoggedAt} onChange={setMoodLoggedAt} - // v1.17 W1b — match the server bound: no future instant. + // v1.17 — match the server bound: no future instant. max={getDefaultMoodLoggedAtValue()} required aria-required="true" @@ -550,7 +550,7 @@ export function MoodForm({

- {/* v1.8.5 (C1) — free-text note. */} + {/* v1.8.5 — free-text note. */} (null); @@ -719,7 +719,7 @@ export function MoodList({ onAddFirst, onEmptyChange }: MoodListProps = {}) { onRetry={() => void refetch()} /> ) : !data?.entries?.length ? ( - // v1.4.15 phase-C5: replace bare-text empty rectangle with + // v1.4.15: replace bare-text empty rectangle with // EmptyState. Filter-aware copy splits "no mood entries yet" // (brand-new account) from "no entries match this filter".
- {/* v1.15.13 L2 — match the measurements list's 44px pagination + {/* v1.15.13 — match the measurements list's 44px pagination target (was `size="sm"`, below the WCAG 2.5.5 floor). */}
- {/* v1.4.33 F17 — the switch label used to flip between + {/* v1.4.33 — the switch label used to flip between "Auto" and "Überschrieben" with the current state. The maintainer's audit caught that as confusing: a user looking at a row with "Auto" + the toggle off reads it diff --git a/src/components/settings/thresholds-section.tsx b/src/components/settings/thresholds-section.tsx index 0af5bd747..795be86f7 100644 --- a/src/components/settings/thresholds-section.tsx +++ b/src/components/settings/thresholds-section.tsx @@ -21,7 +21,7 @@ import { ThresholdsEditorSection } from "@/components/settings/thresholds-editor * 2. `` — per-metric range inputs. The editor * card keeps its own affordance row (icon, reset-all button). * - * v1.4.16 phase B6: file renamed from the historic + * v1.4.16: file renamed from the historic * `thresholds-settings-section.tsx` (``) * so the filename + default export match the slug — every other * section in `src/components/settings/` follows the diff --git a/src/components/settings/timezone-picker.tsx b/src/components/settings/timezone-picker.tsx index 3b86a61df..cb2e94775 100644 --- a/src/components/settings/timezone-picker.tsx +++ b/src/components/settings/timezone-picker.tsx @@ -29,7 +29,7 @@ import { NativeSelect } from "@/components/ui/native-select"; import { listSupportedTimezones, DEFAULT_TIMEZONE } from "@/lib/tz/format"; import { useTranslations } from "@/lib/i18n/context"; -// v1.4.27 MB7 / CF-52 — the in-file `NATIVE_SELECT_CLASS` constant +// v1.4.27 — the in-file `NATIVE_SELECT_CLASS` constant // retired; the shared `` primitive owns the visual // contract now. The select spans the full width of its grid cell so it // lines up with the fields above it rather than sitting narrower. diff --git a/src/components/targets/__tests__/consistency-strip.test.tsx b/src/components/targets/__tests__/consistency-strip.test.tsx index b37778ded..a8ba90597 100644 --- a/src/components/targets/__tests__/consistency-strip.test.tsx +++ b/src/components/targets/__tests__/consistency-strip.test.tsx @@ -8,7 +8,7 @@ import { primeLocales } from "@/__tests__/helpers/prime-locales"; primeLocales("de"); /** - * v1.4.25 W3e — `` replaces the v1.4.22 per-card + * v1.4.25 — `` replaces the v1.4.22 per-card * sparkline. Seven dots, one per Berlin-tz day; band-coloured when the * day's mean reading was in range, hollow when no readings, and a * trailing caption that flips between "in range" and "logged" depending diff --git a/src/components/targets/__tests__/range-bar.test.tsx b/src/components/targets/__tests__/range-bar.test.tsx index cb512949e..bb44a7a84 100644 --- a/src/components/targets/__tests__/range-bar.test.tsx +++ b/src/components/targets/__tests__/range-bar.test.tsx @@ -5,7 +5,7 @@ import { I18nProvider } from "@/lib/i18n/context"; import { EDGE_PADDING_PERCENT, RangeBar } from "../range-bar"; /** - * v1.4.25 W3e — `` was extracted from the inline 790-line + * v1.4.25 — `` was extracted from the inline 790-line * `targets/page.tsx`. Behaviour is intentionally unchanged from the * v1.4.22 inline version; these tests pin the contract so the * extraction is provably side-effect-free. diff --git a/src/components/targets/__tests__/target-edit-sheet.test.tsx b/src/components/targets/__tests__/target-edit-sheet.test.tsx index 3a6f72606..d37a7f996 100644 --- a/src/components/targets/__tests__/target-edit-sheet.test.tsx +++ b/src/components/targets/__tests__/target-edit-sheet.test.tsx @@ -6,7 +6,7 @@ import { I18nProvider } from "@/lib/i18n/context"; import { TargetEditSheet } from "../target-edit-sheet"; /** - * v1.4.25 W3f — target-edit dialog. SSR-style assertions because the + * v1.4.25 — target-edit dialog. SSR-style assertions because the * Radix Dialog body is portalled at runtime; we only check the * outer Dialog wiring + the body-mount toggle. * @@ -200,7 +200,7 @@ describe("", () => { }); /** - * v1.4.25 W3f — focus-management contract. The dialog's + * v1.4.25 — focus-management contract. The dialog's * `onOpenAutoFocus` handler must steal focus from Radix's default * (the close X button) and place it on the first numeric input so a * keyboard user lands on the editable field. We verify the handler diff --git a/src/components/targets/__tests__/target-status-pill.test.tsx b/src/components/targets/__tests__/target-status-pill.test.tsx index f2f60a402..8901ca2f0 100644 --- a/src/components/targets/__tests__/target-status-pill.test.tsx +++ b/src/components/targets/__tests__/target-status-pill.test.tsx @@ -8,7 +8,7 @@ import { } from "../target-status-pill"; /** - * v1.8.5 W5 — `` was extracted from `target-card.tsx` + * v1.8.5 — `` was extracted from `target-card.tsx` * so the Targets card and the Insights reference panel render the * identical pill from the same server-emitted classification category. * These tests pin the category → semantic-group mapping (which drives diff --git a/src/components/targets/consistency-strip.tsx b/src/components/targets/consistency-strip.tsx index 2e502b879..3d1bad894 100644 --- a/src/components/targets/consistency-strip.tsx +++ b/src/components/targets/consistency-strip.tsx @@ -4,7 +4,7 @@ import { cn } from "@/lib/utils"; import { useTranslations } from "@/lib/i18n/context"; /** - * v1.4.25 W3e — 7-day consistency strip. + * v1.4.25 — 7-day consistency strip. * * Renders one dot per Berlin-tz day in `days` (oldest → newest, index 6 * is today). Each dot's appearance encodes both whether the day was diff --git a/src/components/targets/range-bar.tsx b/src/components/targets/range-bar.tsx index 986daafc5..8fb37e153 100644 --- a/src/components/targets/range-bar.tsx +++ b/src/components/targets/range-bar.tsx @@ -147,7 +147,7 @@ export function RangeBar({ : t("targets.aboveTarget", { delta: fmt.number(delta, 1), unit }) : t("targets.inTarget"); - // 2026-07-17 a11y audit (M1) — the marker's value + range live only in a + // The marker's value + range live only in a // hover-only Radix tooltip on a non-focusable `
`. Reusing the same // three strings the tooltip already shows as one text alternative closes // 1.1.1 (no separate copy to drift) — `tabIndex` below makes the trigger diff --git a/src/components/targets/target-edit-sheet.tsx b/src/components/targets/target-edit-sheet.tsx index c0f018f18..c51f643ee 100644 --- a/src/components/targets/target-edit-sheet.tsx +++ b/src/components/targets/target-edit-sheet.tsx @@ -110,7 +110,7 @@ export function TargetEditSheet(props: TargetEditSheetProps) { // QueryClientProvider, and the hooks only execute once the user // actually opens the editor. // - // v1.4.27 R4 RC2 — migrated raw → so the + // v1.4.27 — migrated raw → so the // bottom-sheet branch sticky-pins reset + cancel + save above the // soft keyboard on phones. The lazy-mount toggle stays at this layer // so closed renders never instantiate the TanStack Query hooks. @@ -218,7 +218,7 @@ function TargetEditSheetBody({ // Focus management — the first input gets focus when the sheet // opens, matching the Dashboard chart cog's open-on-focus pattern. - // v1.4.27 R4 RC2 — under the ResponsiveSheet primitive we no longer + // v1.4.27 — under the ResponsiveSheet primitive we no longer // hook Radix's `onOpenAutoFocus`; a mount effect lands focus on the // primary input as soon as the portal paints. const firstInputRef = useRef(null); @@ -402,7 +402,7 @@ function TargetEditSheetBody({ ? Boolean(thresholdsData?.overrides?.[metric]) : false; - // v1.4.27 R4 RC2 — focus the first numeric input on mount instead of + // v1.4.27 — focus the first numeric input on mount instead of // relying on Radix's -specific onOpenAutoFocus. The body // only mounts once `open=true` (lazy mount above), so the effect // runs exactly when the surface comes into view. diff --git a/src/components/targets/target-status-pill.tsx b/src/components/targets/target-status-pill.tsx index 57e05fcc9..394d5be78 100644 --- a/src/components/targets/target-status-pill.tsx +++ b/src/components/targets/target-status-pill.tsx @@ -10,7 +10,7 @@ import { cn } from "@/lib/utils"; import { useTranslations } from "@/lib/i18n/context"; /** - * v1.8.5 W5 — `` extracted from `target-card.tsx`. + * v1.8.5 — `` extracted from `target-card.tsx`. * * The verbal status pill ("Optimal", "Slightly elevated", localized) * is now a shared primitive so both the Targets card and the Insights diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index 16fa2f862..3d6e628f0 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -5,7 +5,7 @@ import { Slot } from "radix-ui"; import { cn } from "@/lib/utils"; /** - * v1.4.33 IW9 — loading-state convention. + * v1.4.33 — loading-state convention. * * Callers driving a transient loading state on a button must SWAP the * leading icon for `` rather than APPENDING the spinner diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index faca2edf7..7be6f4b05 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -3,7 +3,7 @@ import * as React from "react"; import { cn } from "@/lib/utils"; /** - * v1.4.33 IW9 — Card padding normalised on `p-4 md:p-6`. + * v1.4.33 — Card padding normalised on `p-4 md:p-6`. * * Pre-v1.4.33 the shadcn defaults shipped `py-6` + `px-6` (uniform * 24 px on every breakpoint) while the dashboard tile-strip + chart diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx index bde433d61..3f41b1473 100644 --- a/src/components/ui/checkbox.tsx +++ b/src/components/ui/checkbox.tsx @@ -7,7 +7,7 @@ import { Check, Minus } from "lucide-react"; import { cn } from "@/lib/utils"; /** - * v1.4.27 R3d MB4 / CF-40 — shadcn-style `` primitive backed + * v1.4.27 — shadcn-style `` primitive backed * by `radix-ui`'s `Checkbox` namespace. Brought in so the Coach * sources-rail can drop its raw `` for a * keyboard + touch accessible control that ships proper focus rings diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx index c51604155..fc11b09f6 100644 --- a/src/components/ui/dialog.tsx +++ b/src/components/ui/dialog.tsx @@ -63,7 +63,7 @@ function DialogContent({ ` at the viewport's + // v1.4.27 — cap `` at the viewport's // dynamic height with internal scroll so long forms (medication // editor, API endpoint, intake import) never spill past the // viewport on short / soft-keyboard-collapsed mobile screens. @@ -93,7 +93,7 @@ function DialogContent({ > {children} {showCloseButton && ( - // v1.4.27 R3d MB2 — widen the icon-only close-X tap target + // v1.4.27 — widen the icon-only close-X tap target // to a 36 px floor per WCAG 2.5.8 minimum target size. // 36 px (not 44 px) is the intentional exception for fixed // icon-only close affordances per Decision I — 44 px would @@ -103,7 +103,7 @@ function DialogContent({ className="ring-offset-background focus-visible:border-ring focus-visible:ring-ring/50 text-muted-foreground hover:bg-accent hover:text-foreground absolute top-3 right-4 inline-flex min-h-11 min-w-11 items-center justify-center rounded-md transition-colors focus-visible:ring-[3px] focus-visible:outline-none disabled:pointer-events-none sm:min-h-9 sm:min-w-9 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" > - {/* v1.4.34 IW-G — read `common.close` so screen readers + {/* v1.4.34 — read `common.close` so screen readers hear the localised label instead of literal English. */} {t("common.close")} diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index 17293a6f7..9a76a7f2a 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -74,7 +74,7 @@ function DropdownMenuItem({ data-inset={inset} data-variant={variant} className={cn( - // v1.4.37 W4a item 2 — `whitespace-nowrap` keeps every + // v1.4.37 — `whitespace-nowrap` keeps every // dropdown row on a single line (the sidebar 3-dot menu's // "Benachrichtigungs-Center" otherwise wrapped at the // hyphen on sub-pixel rounding inside the legacy w-56 diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index 307d5f0c3..0bcf70e7c 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -74,7 +74,7 @@ function Input({ data-1p-ignore={skipAutofill ? "true" : undefined} data-bwignore={skipAutofill ? "true" : undefined} className={cn( - // v1.4.34 IW-G — floor at 44 px on mobile to clear WCAG 2.5.5; + // v1.4.34 — floor at 44 px on mobile to clear WCAG 2.5.5; // shrink to 40 px on sm+ where pointer precision is higher. "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-11 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 sm:h-10 md:pointer-fine:text-sm", "focus-visible:border-input-focus aria-invalid:focus-visible:ring-[3px]", diff --git a/src/components/ui/learn-more-link.tsx b/src/components/ui/learn-more-link.tsx index 1b6c5f6ba..a5a4ee51a 100644 --- a/src/components/ui/learn-more-link.tsx +++ b/src/components/ui/learn-more-link.tsx @@ -58,7 +58,7 @@ export function LearnMoreLink({ concept, className }: LearnMoreLinkProps) { } /** - * v1.22 (W5) — inline, catalog-whitelisted Learn anchor for AI prose. + * v1.22 — inline, catalog-whitelisted Learn anchor for AI prose. * * Where `LearnMoreLink` resolves a *concept* to its guide for a deterministic * UI surface, this variant linkifies a `/learn/` reference that already diff --git a/src/components/ui/native-select.tsx b/src/components/ui/native-select.tsx index 0c44916a0..ba59a2640 100644 --- a/src/components/ui/native-select.tsx +++ b/src/components/ui/native-select.tsx @@ -5,7 +5,7 @@ import * as React from "react"; import { cn } from "@/lib/utils"; /** - * v1.4.27 MB7 / CF-52 — shared `` primitive. * * Three settings/admin surfaces (`AccountSection`, `TimezonePicker`, * `GeneralSettingsSection`) each declared a local `NATIVE_SELECT_CLASS` @@ -35,7 +35,7 @@ import { cn } from "@/lib/utils"; * uniform with the rest of the form's `` + `` * rows. */ -// v1.4.34 IW-G — floor at 44 px on mobile to clear WCAG 2.5.5; shrink +// v1.4.34 — floor at 44 px on mobile to clear WCAG 2.5.5; shrink // to 40 px on sm+ where the pointer precision is higher. // // Chevron symmetry: `appearance-none` drops the browser-drawn dropdown arrow, diff --git a/src/components/ui/password-input.tsx b/src/components/ui/password-input.tsx index 2489fe2be..5b7c79392 100644 --- a/src/components/ui/password-input.tsx +++ b/src/components/ui/password-input.tsx @@ -14,7 +14,7 @@ import { useTranslations } from "@/lib/i18n/context"; * register) can share the same UX without duplicating the * visibility-toggle plumbing. * - * v1.4.27 R3d MB2 — the icon-only toggle wraps in an `inline-flex h-11 + * v1.4.27 — the icon-only toggle wraps in an `inline-flex h-11 * w-11` hit area so the 44 px WCAG 2.5.5 floor is honoured on touch. * The icon stays at 16 px; the surrounding padding does the work. The * input itself gets `pr-12` so user input never collides with the diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx index 1eaf7d21c..9811a852f 100644 --- a/src/components/ui/popover.tsx +++ b/src/components/ui/popover.tsx @@ -9,7 +9,7 @@ import { cn } from "@/lib/utils"; * Popover primitive — Radix `Popover` wrapped with the same Dracula * surface tokens the rest of the shadcn primitives use. * - * v1.4.27 MB3 — introduced so the Coach composer hint can swap off + * v1.4.27 — introduced so the Coach composer hint can swap off * the `` primitive. Radix tooltips never tap-toggle reliably * on touch surfaces; popovers do. The mouse-hover preview that the * tooltip used to provide on desktop is dropped intentionally — diff --git a/src/components/ui/responsive-sheet.tsx b/src/components/ui/responsive-sheet.tsx index 17fa6f950..9177a9bb8 100644 --- a/src/components/ui/responsive-sheet.tsx +++ b/src/components/ui/responsive-sheet.tsx @@ -111,7 +111,7 @@ const CONTENT_WIDTH_CLASS: Record< }; /** - * v1.4.27 R3d MB1 — shared mount primitive that flips between a + * v1.4.27 — shared mount primitive that flips between a * bottom-anchored `` on narrow viewports and the existing * centred `` on tablet/desktop. Picks the breakpoint via * the `useIsMobile()` hook (Tailwind `md` — 768 px). diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 3d3230c6d..110449d32 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -37,7 +37,7 @@ function SelectTrigger({ data-slot="select-trigger" data-size={size} className={cn( - // v1.4.34 IW-G — `size=default` floors at 44 px on mobile to + // v1.4.34 — `size=default` floors at 44 px on mobile to // clear WCAG 2.5.5 and shrinks to 40 px on sm+ to match the // desktop input rhythm. `size=sm` (filter chips) intentionally // keeps the 32 px footprint. diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx index a58b0a5db..2b75a0f1e 100644 --- a/src/components/ui/sheet.tsx +++ b/src/components/ui/sheet.tsx @@ -86,7 +86,7 @@ function SheetContent({ > {children} {showCloseButton && ( - // v1.4.27 R4 RC3 — widen the icon-only close-X tap target to + // v1.4.27 — widen the icon-only close-X tap target to // a 36 px floor, matching the Dialog close-X treatment from // MB2 (Decision I). 36 px (not 44 px) is the intentional // exception for fixed icon-only close affordances per the @@ -97,7 +97,7 @@ function SheetContent({ className="ring-offset-background focus-visible:border-ring focus-visible:ring-ring/50 text-muted-foreground hover:bg-accent hover:text-foreground absolute top-3 right-4 inline-flex min-h-11 min-w-11 items-center justify-center rounded-md transition-colors focus-visible:ring-[3px] focus-visible:outline-none disabled:pointer-events-none sm:min-h-9 sm:min-w-9 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4" > - {/* v1.4.34 IW-G — read `common.close` so screen readers + {/* v1.4.34 — read `common.close` so screen readers hear the localised label instead of literal English. */} {t("common.close")} diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx index cef45ddef..e6034e80e 100644 --- a/src/components/ui/switch.tsx +++ b/src/components/ui/switch.tsx @@ -17,7 +17,7 @@ function Switch({ data-slot="switch" data-size={size} className={cn( - // v1.4.43 W5-H1 — extend the WCAG 2.5.5 hit target to 44×44 px via + // v1.4.43 — extend the WCAG 2.5.5 hit target to 44×44 px via // a `::before` pseudo-element while leaving the visual track at // 18×32 px. The same trick is used on Input (v1.4.34 IW-G). The // wrapping `relative` and `before:absolute … before:inset-[-13px]` diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx index 4aadf57fb..1084d57be 100644 --- a/src/components/ui/tabs.tsx +++ b/src/components/ui/tabs.tsx @@ -35,7 +35,7 @@ function Tabs({ // looks identical because the inline-flex shrink-wraps inside the // wider container. // -// v1.4.19 A7 — `overflow-y-hidden` clamps the y-axis. Browsers couple +// v1.4.19 — `overflow-y-hidden` clamps the y-axis. Browsers couple // the two overflow axes once one is `auto`: setting only // `overflow-x: auto` silently flips `overflow-y` to `auto` as well. // Combined with the fixed `h-9` strip and badge children that ride diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx index 7750dc85b..b60e652eb 100644 --- a/src/components/ui/textarea.tsx +++ b/src/components/ui/textarea.tsx @@ -5,7 +5,7 @@ import { cn } from "@/lib/utils"; /** * Base textarea primitive — mirror of `` for multi-line input. * - * v1.4.47 W2 — extracted the iOS-zoom defence + tap-target floor + + * v1.4.47 — extracted the iOS-zoom defence + tap-target floor + * autocomplete defaults that the bugreport / medications-import / * side-effects / admin-feedback textareas were copy-pasting into a * single primitive. Six call sites previously each maintained their diff --git a/src/hooks/_internal/use-query-client-safe.ts b/src/hooks/_internal/use-query-client-safe.ts index a404383b7..bb0d4fe7c 100644 --- a/src/hooks/_internal/use-query-client-safe.ts +++ b/src/hooks/_internal/use-query-client-safe.ts @@ -4,7 +4,7 @@ import { useContext } from "react"; import { QueryClientContext, type QueryClient } from "@tanstack/react-query"; /** - * v1.4.48 M4 — shared SSR-safety helper for hooks that delegate to + * v1.4.48 — shared SSR-safety helper for hooks that delegate to * React Query under a ``. * * Several presentational components (``, ``, diff --git a/src/hooks/use-auth.ts b/src/hooks/use-auth.ts index 22a0ce25b..28eee182d 100644 --- a/src/hooks/use-auth.ts +++ b/src/hooks/use-auth.ts @@ -73,7 +73,7 @@ export interface AuthUser { timezone: string; onboardingCompletedAt: string | null; /** - * v1.4.15 Phase B5: whether the user has finished or dismissed + * v1.4.15: whether the user has finished or dismissed * the spotlight tour overlaid on the dashboard. Distinct from * `onboardingCompletedAt` (the wizard at /onboarding) — see the * `` component for the gating logic. @@ -87,7 +87,7 @@ export interface AuthUser { */ onboardingTourProgress: AuthTourProgress | null; /** - * v1.39 (C1) — the needs-based setup flow's state for the record the payload + * v1.39 — the needs-based setup flow's state for the record the payload * describes — the active one under a switch: the nine ordered steps with * their `pending | done | skipped` status, the answers as given, the flow's * own completion stamp (distinct from `onboardingCompletedAt` above, which @@ -100,7 +100,7 @@ export interface AuthUser { */ onboarding?: OnboardingStateDto | null; /** - * v1.18.6 (DISC-02) — ISO timestamp of the one-time medical-disclaimer + * v1.18.6 — ISO timestamp of the one-time medical-disclaimer * acknowledgment, or null when never acknowledged. The onboarding welcome * step gates "Get started" on a non-null value for a fresh account. * Optional in the type so a stale /me payload (older server image without @@ -147,7 +147,7 @@ export interface AuthUser { */ dateFormat: DateFormatPreference; /** - * v1.4.47 W3 — the signed-in person's own stored Coach opt-out. It is + * v1.4.47 — the signed-in person's own stored Coach opt-out. It is * the value the Activate-Coach switch and the module toggle edit, and * nothing else should read it: it belongs to the account, not to the * record on screen, and it is only one of the layers that decide whether @@ -350,7 +350,7 @@ export async function fetchMe(): Promise { // throws `ApiError`, which `useAuth` treats as "not authenticated" // exactly like the old hand-rolled throw. // - // v1.4.47 W3 — coerce `disableCoach` against `undefined` so a stale + // v1.4.47 — coerce `disableCoach` against `undefined` so a stale // /me payload from a partial-deploy rollback (older server image // without the field) keeps the Coach surface visible by default. const data = await apiGet< @@ -421,7 +421,7 @@ export async function fetchMe(): Promise { // v1.18.6 — coerce against a stale /me payload (older server image // without the field) to null so the tour starts from the top. onboardingTourProgress: data.onboardingTourProgress ?? null, - // v1.39 (C1) — coerce against a stale /me payload (an older server image + // v1.39 — coerce against a stale /me payload (an older server image // without the field) to null, which every reader treats as "no flow was // ever run" rather than as an unfinished one. onboarding: data.onboarding ?? null, @@ -502,7 +502,7 @@ function resolveAccountAccess(value: object): { export function useAuth() { const query = useQuery({ - // v1.4.40 W-RSC — factory-routed to `queryKeys.authMe()`. Pre-fix + // v1.4.40 — factory-routed to `queryKeys.authMe()`. Pre-fix // the literal `["auth", "me"]` was the canonical example in // audit-H1 of factory drift. The prefix `["auth"]` still matches // `queryKeys.auth()` invalidations. diff --git a/src/hooks/use-chart-overlay-prefs.ts b/src/hooks/use-chart-overlay-prefs.ts index 462d4a9ab..4e3f74306 100644 --- a/src/hooks/use-chart-overlay-prefs.ts +++ b/src/hooks/use-chart-overlay-prefs.ts @@ -22,7 +22,7 @@ import { apiGet, apiPut } from "@/lib/api/api-fetch"; * dashboard doesn't fire a second network request — the layout * already lands once on initial dashboard render. * - * v1.4.29 C4 — share the same `queryKeys.dashboardWidgets()` slot + * v1.4.29 — share the same `queryKeys.dashboardWidgets()` slot * the dashboard page + Settings → Dashboard already consume. * Pre-fix the hook keyed under `["dashboard-layout"]`, splitting the * cache into two slots for the same endpoint and firing diff --git a/src/hooks/use-coach-prefs.ts b/src/hooks/use-coach-prefs.ts index db3d3907b..fc1bdb366 100644 --- a/src/hooks/use-coach-prefs.ts +++ b/src/hooks/use-coach-prefs.ts @@ -15,7 +15,7 @@ import { } from "@/lib/validations/coach-prefs"; /** - * v1.32.22 (R5b) — the cached Coach-prefs row carries the optimistic- + * v1.32.22 — the cached Coach-prefs row carries the optimistic- * concurrency token (`User.updatedAt`) alongside its payload so the writer * can echo it. The read query and the save mutation both speak this shape; * the token is opaque (the client only ever echoes a server-returned value). @@ -23,7 +23,7 @@ import { type CoachPrefsWithToken = CoachPrefs & { updatedAt?: string }; /** - * v1.4.23 W6 (S-03) — shared accessor for the per-user Coach + * v1.4.23 — shared accessor for the per-user Coach * preferences row. Both the Coach drawer's settings sheet and the * message-thread evidence-disclosure default consume the same * `/api/auth/me/coach-prefs` payload; before the extraction the two @@ -71,7 +71,7 @@ export function useSaveCoachPrefs(opts?: { const queryClient = useQueryClient(); return useMutation({ mutationKey: queryKeys.coachPrefs(), - // v1.32.22 (R5b) — echo the base token this edit was based on so a + // v1.32.22 — echo the base token this edit was based on so a // concurrent write to the same row (another tab, the chat sources rail) // 409s instead of silently overwriting the newer state. Read straight from // the cache at mutate time so it reflects the latest settled write. @@ -91,7 +91,7 @@ export function useSaveCoachPrefs(opts?: { opts?.onSuccess?.(); }, onError: (err) => { - // v1.32.22 (R5b) — a 409 means the stored prefs advanced since this edit + // v1.32.22 — a 409 means the stored prefs advanced since this edit // was based. Refetch so the token advances and the form re-syncs to the // fresh server state, then let the caller nudge gently — nothing was // clobbered. Every other error is handed back to the caller. diff --git a/src/hooks/use-coach-reminders.ts b/src/hooks/use-coach-reminders.ts index 1f4b7db25..b7141cbfc 100644 --- a/src/hooks/use-coach-reminders.ts +++ b/src/hooks/use-coach-reminders.ts @@ -1,7 +1,7 @@ "use client"; /** - * v1.22 (B2/B6) — read + mutate the user's Coach episodic reminders. + * v1.22 — read + mutate the user's Coach episodic reminders. * * One shared hook for the in-app reminder tile/indicator and the "Your context" * ledger. Reads unwrap `(await res.json()).data` per the envelope convention; diff --git a/src/hooks/use-insight-status.ts b/src/hooks/use-insight-status.ts index ebccbecc3..cfb5be945 100644 --- a/src/hooks/use-insight-status.ts +++ b/src/hooks/use-insight-status.ts @@ -312,7 +312,7 @@ export function useInsightStatus(metric: InsightStatusMetric) { ), enabled: isAuthenticated && statusText.available, staleTime: 60 * 1000, - // v1.4.28 FB-D2 — the status route returns a deterministic envelope on + // v1.4.28 — the status route returns a deterministic envelope on // a miss/timeout. Retrying inline re-fires work and lengthens the // perceived hang; the bounded `preparing` poll below covers the warm-up. retry: 0, diff --git a/src/hooks/use-insights-analytics.ts b/src/hooks/use-insights-analytics.ts index 5f354a798..b10a55663 100644 --- a/src/hooks/use-insights-analytics.ts +++ b/src/hooks/use-insights-analytics.ts @@ -9,7 +9,7 @@ import { useAnalyticsQuery } from "@/lib/queries/use-analytics-query"; import type { SubPageAnalyticsData } from "@/types/analytics"; /** - * v1.4.28 R3d (BK-F-H1) — shared React-Query wrapper for the analytics + * v1.4.28 — shared React-Query wrapper for the analytics * payload behind every insights sub-page. * * The five hard-data sub-pages (`puls`, `blutdruck`, `gewicht`, `bmi`, @@ -61,7 +61,7 @@ export function useInsightsAnalytics( metric: InsightMetric, ): UseInsightsAnalyticsResult { const { isAuthenticated } = useAuth(); - // v1.4.33 IW2 — sub-pages only read `summaries[METRIC].count` for the + // v1.4.33 — sub-pages only read `summaries[METRIC].count` for the // gating short-circuit, so they ride on the slim slice. The mother // page keeps the default thick slice for `correlations` / // `healthScore` / `bpInTargetPct*`; both consumers live in the same diff --git a/src/hooks/use-insights-layout-prefs.ts b/src/hooks/use-insights-layout-prefs.ts index e7890ecc4..c59cc800d 100644 --- a/src/hooks/use-insights-layout-prefs.ts +++ b/src/hooks/use-insights-layout-prefs.ts @@ -10,7 +10,7 @@ import { } from "@/lib/dashboard-layout"; /** - * v1.4.25 W4 — shared reader for the dashboard-layout payload across + * v1.4.25 — shared reader for the dashboard-layout payload across * the insights surfaces. * * The mother page and every sub-page need the persisted diff --git a/src/hooks/use-insights-layout.ts b/src/hooks/use-insights-layout.ts index a53964079..59860c5aa 100644 --- a/src/hooks/use-insights-layout.ts +++ b/src/hooks/use-insights-layout.ts @@ -33,7 +33,7 @@ import { * `DEFAULT_INSIGHTS_LAYOUT` and a "Fertig" save would PUT defaults over their * real saved layout. * - * v1.32.21 (R5a, B-3) — the resolver rebuilds a FIXED `InsightsLayout` shape + * v1.32.21 — the resolver rebuilds a FIXED `InsightsLayout` shape * and would DROP the server's `updatedAt` optimistic-concurrency token. If it * did, every insights-layout write would fall back to the server's compat * (unconditional) arm and the #581-class guard would be dead on arrival. So we diff --git a/src/hooks/use-is-mobile.ts b/src/hooks/use-is-mobile.ts index 0142cbb14..c93c6b581 100644 --- a/src/hooks/use-is-mobile.ts +++ b/src/hooks/use-is-mobile.ts @@ -5,7 +5,7 @@ import { useSyncExternalStore } from "react"; /** * Viewport-width hook seeded from a SSR-safe snapshot. * - * v1.4.27 R4 RC3 — the previous `useEffect`-driven flavour returned + * v1.4.27 — the previous `useEffect`-driven flavour returned * `false` on the server AND on the first client paint, then flipped to * the live value on the effect tick. Two consumers * (``, ``) branch their render tree off diff --git a/src/hooks/use-module-enabled.ts b/src/hooks/use-module-enabled.ts index 5d405125a..2ab328f33 100644 --- a/src/hooks/use-module-enabled.ts +++ b/src/hooks/use-module-enabled.ts @@ -5,7 +5,7 @@ import { useQueryClientMounted } from "@/hooks/_internal/use-query-client-safe"; import type { ModuleKey } from "@/lib/modules/registry"; /** - * v1.18.0 R4 — SSR-safe accessor for a per-account module flag. + * v1.18.0 — SSR-safe accessor for a per-account module flag. * * `useAuth()` calls `useQuery` directly and * crashes when a legacy presentational component (``, the diff --git a/src/hooks/use-module-toggle.ts b/src/hooks/use-module-toggle.ts index c54fbce6b..17e109bf1 100644 --- a/src/hooks/use-module-toggle.ts +++ b/src/hooks/use-module-toggle.ts @@ -64,7 +64,7 @@ export function useModuleToggle(options?: { } // DISABLED allowlist: send only the single key the user flipped. // - // v1.32.22 (R5b) — echo the optimistic-concurrency token so an + // v1.32.22 — echo the optimistic-concurrency token so an // interleaved modules PATCH (another tab, the iOS client) 409s instead of // clobbering. The token rides on `User.updatedAt`; the module map itself // rides on `/auth/me`, and no dedicated GET populates the token, so it is @@ -97,7 +97,7 @@ export function useModuleToggle(options?: { options?.onToggled?.(vars); }, onError: (err) => { - // v1.32.22 (R5b) — a 409 means the module map advanced since this + // v1.32.22 — a 409 means the module map advanced since this // switch was based (another tab / the iOS client wrote in between). // Drop the now-stale seeded token and refetch /auth/me so the switch // snaps back to server truth, then nudge — nothing was clobbered. diff --git a/src/hooks/use-pull-to-refresh.ts b/src/hooks/use-pull-to-refresh.ts index 20b25431f..bf736a729 100644 --- a/src/hooks/use-pull-to-refresh.ts +++ b/src/hooks/use-pull-to-refresh.ts @@ -60,7 +60,7 @@ export function usePullToRefresh({ let busy = false; let lastDistance = 0; - // v1.30.1 M12 — the authenticated shell's actual scroll container is + // v1.30.1 — the authenticated shell's actual scroll container is // `
` (`AuthShell` sets it `overflow-y-auto` // inside a height-locked `h-dvh` layout); `document.scrollingElement` // never moves there; it stays permanently at 0. Checking the document diff --git a/src/hooks/use-scroll-reset-on-route.ts b/src/hooks/use-scroll-reset-on-route.ts index 7cae39724..cd00b6af8 100644 --- a/src/hooks/use-scroll-reset-on-route.ts +++ b/src/hooks/use-scroll-reset-on-route.ts @@ -3,7 +3,7 @@ import { useEffect } from "react"; /** - * v1.4.33 IW9 — single source of truth for the "scroll to top on + * v1.4.33 — single source of truth for the "scroll to top on * route mount" affordance. * * The pre-v1.4.33 wiring had `` AND the insights mother diff --git a/src/lib/__tests__/achievements-no-insults.test.ts b/src/lib/__tests__/achievements-no-insults.test.ts index 38c8e4419..0e1ea30e4 100644 --- a/src/lib/__tests__/achievements-no-insults.test.ts +++ b/src/lib/__tests__/achievements-no-insults.test.ts @@ -4,7 +4,7 @@ import deMessages from "../../../messages/de.json"; import enMessages from "../../../messages/en.json"; /** - * v1.4.19 A8 / F-03 — `achievements.list.overIntake1.title === "Idiot"` and + * v1.4.19 — `achievements.list.overIntake1.title === "Idiot"` and * `achievements.list.skippedIntake1.title === "Lazy Boy"` were shipping in * both EN and DE bundles. For a personal health app that translates to a * literal insult any time the user double-doses (e.g. forgot they already diff --git a/src/lib/__tests__/api-response-zod.test.ts b/src/lib/__tests__/api-response-zod.test.ts index 902349e4e..441acecb3 100644 --- a/src/lib/__tests__/api-response-zod.test.ts +++ b/src/lib/__tests__/api-response-zod.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"; import { z } from "zod/v4"; import { returnAllZodIssues, sanitiseZodIssues } from "../api-response"; -// v1.4.42 W2 — every Zod-validated route historically returned +// v1.4.42 — every Zod-validated route historically returned // `parsed.error.issues[0].message`, dropping every issue past the first. // `returnAllZodIssues` is the additive replacement: clients that only // read `error` keep working, new callers branch on `details.issues`. diff --git a/src/lib/__tests__/crypto.test.ts b/src/lib/__tests__/crypto.test.ts index fc2a0a299..82ee76c8b 100644 --- a/src/lib/__tests__/crypto.test.ts +++ b/src/lib/__tests__/crypto.test.ts @@ -74,7 +74,7 @@ describe("crypto (AES-256-GCM, key versioning)", () => { }); it("rotation: rows encrypted under old key still decrypt after switching active id", () => { - // Phase 1 — write under v1 + // Write under v1 vi.stubEnv("ENCRYPTION_KEYS", JSON.stringify({ v1: KEY_V1 })); vi.stubEnv("ENCRYPTION_ACTIVE_KEY_ID", "v1"); vi.stubEnv("ENCRYPTION_KEY", ""); @@ -82,7 +82,7 @@ describe("crypto (AES-256-GCM, key versioning)", () => { const oldRow = encrypt("rotate-me"); expect(oldRow.startsWith("v1.")).toBe(true); - // Phase 2 — add v2 and rotate active to v2; old row still decrypts + // Add v2 and rotate active to v2; old row still decrypts vi.stubEnv("ENCRYPTION_KEYS", JSON.stringify({ v1: KEY_V1, v2: KEY_V2 })); vi.stubEnv("ENCRYPTION_ACTIVE_KEY_ID", "v2"); _resetCryptoCacheForTests(); diff --git a/src/lib/__tests__/dashboard-layout.test.ts b/src/lib/__tests__/dashboard-layout.test.ts index 0f7eba045..21f0ddfaf 100644 --- a/src/lib/__tests__/dashboard-layout.test.ts +++ b/src/lib/__tests__/dashboard-layout.test.ts @@ -18,7 +18,7 @@ import { import { PRIORITY_ITEM_KINDS } from "@/lib/daily/priority-item"; /** - * v1.4.15 Fix 5 — Dashboard layout: top tiles selectable. + * v1.4.15 — Dashboard layout: top tiles selectable. * * Until v1.4.14 the layout schema had a single `visible` flag per * widget that controlled BOTH the upper-row strip tile AND the lower- @@ -129,7 +129,7 @@ describe("serializeDashboardLayout() — tileVisible persistence", () => { }); /** - * v1.4.16 Fix A5 — top-tile selector real-fix. + * v1.4.16 — top-tile selector real-fix. * * The maintainer reported the toggles in Settings → Dashboard didn't filter * the dashboard tile-strip. Root cause: the `widgetIdEnum` Zod schema @@ -222,7 +222,7 @@ describe("DEFAULT_DASHBOARD_LAYOUT contract", () => { }); /** - * v1.4.16 phase B8 — comparison baseline persistence. + * v1.4.16 — comparison baseline persistence. * * The comparison toggle (Vormonat / Vorjahr) piggy-backs on the * existing `User.dashboardWidgetsJson` blob per research §7 Q3 @@ -444,7 +444,7 @@ describe("resolveDashboardLayout() — retired heroVisible field", () => { */ describe("DASHBOARD_WIDGET_CATALOGUE_IDS — 27-id catalogue", () => { it("carries exactly 42 distinct ids (29 server-known + 13 iOS-only)", () => { - // v1.11.2 B5 — the 8 v1.10 additive metrics became web-writable, so + // v1.11.2 — the 8 v1.10 additive metrics became web-writable, so // the server-known set grew 16 → 24 and the catalogue 27 → 35. // v1.18.2 — the Vorsorge summary widget added one server-known id // (24 → 25), so the catalogue grew 35 → 36. @@ -616,7 +616,7 @@ describe("v1.28.52 — vitals + body-composition strip tiles", () => { }); /** - * v1.11.2 HIGH-1 — the 8 B5 metrics are WRITABLE (members of + * v1.11.2 — the 8 B5 metrics are WRITABLE (members of * `DASHBOARD_WIDGET_IDS`, so the widgets PUT enum — derived from the * catalogue — accepts them, which the iOS Home-pin request requires) but * have NO web render path. `IOS_PIN_ONLY_WIDGET_IDS` names exactly that @@ -661,7 +661,7 @@ describe("IOS_PIN_ONLY_WIDGET_IDS — writable but not web-rendered", () => { }); /** - * v1.7.0 W1 — iOS-only ids round-trip through the stored layout so the + * v1.7.0 — iOS-only ids round-trip through the stored layout so the * native client can drop its local merge workarounds * (`byMergingIosOnlyDefaults` / `byRestoringIosOnlyWidgets`). The * resolver + serializer must RETAIN every catalogue id (27) while the diff --git a/src/lib/__tests__/db.test.ts b/src/lib/__tests__/db.test.ts index 9ca117bee..251b55e68 100644 --- a/src/lib/__tests__/db.test.ts +++ b/src/lib/__tests__/db.test.ts @@ -70,7 +70,7 @@ describe("per-process database connection budget", () => { }); }); -// A-1 — a per-session statement timeout so a single stuck query can't hold a +// A per-session statement timeout so a single stuck query can't hold a // pool slot forever and starve every DB-backed route. describe("getStatementTimeoutMs", () => { const originalEnv = process.env.DATABASE_STATEMENT_TIMEOUT_MS; diff --git a/src/lib/__tests__/doctor-report-allergy-reaction.test.ts b/src/lib/__tests__/doctor-report-allergy-reaction.test.ts index cff19fd89..b3faa2480 100644 --- a/src/lib/__tests__/doctor-report-allergy-reaction.test.ts +++ b/src/lib/__tests__/doctor-report-allergy-reaction.test.ts @@ -1,5 +1,5 @@ /** - * F-CRYPTO-3 — an undecryptable allergy reaction must be distinguishable from a + * An undecryptable allergy reaction must be distinguishable from a * genuinely-unset one on a clinician-facing export. The builder used to fail * SOFT (→ null, silently omitted) where a sibling dose-change note fails CLOSED, * so a reaction that existed but was unreadable rendered as a blank "—" that a diff --git a/src/lib/__tests__/doctor-report-data.test.ts b/src/lib/__tests__/doctor-report-data.test.ts index 1a40eb0d9..47bb06580 100644 --- a/src/lib/__tests__/doctor-report-data.test.ts +++ b/src/lib/__tests__/doctor-report-data.test.ts @@ -301,7 +301,7 @@ describe("buildLedgerCompliance — doctor-report adherence via the dose ledger" } /** - * v1.17 W1a — the raw-row tally double-counted cross-source duplicate + * v1.17 — the raw-row tally double-counted cross-source duplicate * rows (a REMINDER + an API row for the same slot both counted as taken) * and counted every row toward the denominator. The ledger dedups onto * one slot. This pins that the doctor report now matches the detail page: diff --git a/src/lib/__tests__/doctor-report-pdf-core.test.ts b/src/lib/__tests__/doctor-report-pdf-core.test.ts index d8c9c2f8d..8fd1fcc04 100644 --- a/src/lib/__tests__/doctor-report-pdf-core.test.ts +++ b/src/lib/__tests__/doctor-report-pdf-core.test.ts @@ -632,7 +632,7 @@ describe("doctor-report clinical glucose panel", () => { }); }); -// v1.18.1 P4 — illness / condition episodes section. Retrospective + factual: +// v1.18.1 — illness / condition episodes section. Retrospective + factual: // label, type, course, onset, resolved. Present only when the aggregator // populated `illnessEpisodes` (module on + episode in window). describe("doctor-report recorded precision", () => { diff --git a/src/lib/__tests__/export.test.ts b/src/lib/__tests__/export.test.ts index 8fcb32467..dfc62060c 100644 --- a/src/lib/__tests__/export.test.ts +++ b/src/lib/__tests__/export.test.ts @@ -199,7 +199,7 @@ describe("formatMeasurementsForExport", () => { expect(result[0].unit).toBe("kg"); }); - // v1.4.25 W7 — per-user timezone offset in exports (issue #167). + // v1.4.25 — per-user timezone offset in exports (issue #167). it("emits ISO-8601 with user-tz offset when userTz is provided", () => { const measurements = [ { diff --git a/src/lib/__tests__/format-active-locale.test.ts b/src/lib/__tests__/format-active-locale.test.ts index b7b86984f..6c61e181b 100644 --- a/src/lib/__tests__/format-active-locale.test.ts +++ b/src/lib/__tests__/format-active-locale.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.43 QoL (M8) — pin the widened cookie / localStorage decoder in + * v1.4.43 — pin the widened cookie / localStorage decoder in * `src/lib/format.ts`. Pre-fix a French user reading the dashboard saw * "12/24/2025, 2:30 PM" via the silent `en` fallback; the formatter * now honours every supported locale via the full `Locale` union. diff --git a/src/lib/__tests__/format-date-or-relative.test.ts b/src/lib/__tests__/format-date-or-relative.test.ts index 8d7f075cd..6eeb0b50b 100644 --- a/src/lib/__tests__/format-date-or-relative.test.ts +++ b/src/lib/__tests__/format-date-or-relative.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.43 QoL (L8) — `formatDateOrRelative()` boundary cases. + * v1.4.43 — `formatDateOrRelative()` boundary cases. * * The helper switches between relative ("vor 12 min") and absolute * ("21.05.2026, 14:32") rendering based on a 24h cutoff, so two diff --git a/src/lib/__tests__/format-locale.test.ts b/src/lib/__tests__/format-locale.test.ts index c62ffe823..9fe33b8bb 100644 --- a/src/lib/__tests__/format-locale.test.ts +++ b/src/lib/__tests__/format-locale.test.ts @@ -31,7 +31,7 @@ describe("parseLocaleFromAcceptLanguage", () => { expect(parseLocaleFromAcceptLanguage("*")).toBe("en"); }); - // v1.4.25 W9e — the four AI-initial locales added in this release. + // v1.4.25 — the four AI-initial locales added in this release. // Each maps via the same primary-tag prefix match the DE branch uses. it("returns the matching tag for FR / ES / IT / PL", () => { expect(parseLocaleFromAcceptLanguage("fr-FR,fr;q=0.9")).toBe("fr"); @@ -117,7 +117,7 @@ describe("makeFormatters", () => { }); }); - // v1.4.25 W7 — formatters accept a per-user timezone override. + // v1.4.25 — formatters accept a per-user timezone override. it("renders time in the user's tz when userTz is passed", () => { const tokyo = makeFormatters("en", "Asia/Tokyo", "H24", "AUTO"); // 14:30 UTC = 23:30 Tokyo diff --git a/src/lib/__tests__/geo-asn.test.ts b/src/lib/__tests__/geo-asn.test.ts index 437afe6cc..032fd4871 100644 --- a/src/lib/__tests__/geo-asn.test.ts +++ b/src/lib/__tests__/geo-asn.test.ts @@ -16,7 +16,7 @@ vi.mock("undici", async (importOriginal) => { }); /** - * v1.4.27 B3 — ASN + carrier lookup tests. + * v1.4.27 — ASN + carrier lookup tests. * * The MaxMind GeoLite2-ASN MMDB is too large for the test fixtures * directory (~10 MB) and shipping it would bloat git history. These diff --git a/src/lib/__tests__/geo.test.ts b/src/lib/__tests__/geo.test.ts index 34af526e2..ecdf5b7f2 100644 --- a/src/lib/__tests__/geo.test.ts +++ b/src/lib/__tests__/geo.test.ts @@ -15,7 +15,7 @@ vi.mock("undici", async (importOriginal) => { }; }); -// v1.18.11 (W3): capture wide-event warnings so the non-ok-status path can +// v1.18.11: capture wide-event warnings so the non-ok-status path can // be asserted. `getEvent()` returns null outside a request context, so // without this mock the `getEvent()?.addWarning(...)` call is a silent // no-op and there is nothing to assert against. @@ -71,7 +71,7 @@ describe("lookupIpLocation IP-geolocation HTTPS guard", () => { expect(fetchSpy).not.toHaveBeenCalled(); }); - // v1.4.16 A8a: helper turns a JS object into a real `Response` whose + // v1.4.16: helper turns a JS object into a real `Response` whose // body is the UTF-8 byte stream of `JSON.stringify(value)`. The geo // helper now reads via `arrayBuffer() + TextDecoder('utf-8')` so the // test fakes have to expose those, not just `json()`. @@ -201,7 +201,7 @@ describe("lookupIpLocation IP-geolocation HTTPS guard", () => { }); }); -// v1.18.11 (W3): a non-ok HTTP status (403 free-plan/CORS rejection, 429 +// v1.18.11: a non-ok HTTP status (403 free-plan/CORS rejection, 429 // rate-limit, 5xx) must surface a wide-event warning instead of being // swallowed as a clean miss. The ipwho.is 403 that produced the prod "—" // never emitted a single signal; this guards against that regression. @@ -246,7 +246,7 @@ describe("lookupIpLocation non-ok status warning (v1.18.11 W3)", () => { }); }); -// v1.4.16 A8a: the maintainer spotted "Nrnberg" rendered in /admin/login-overview +// v1.4.16: the maintainer spotted "Nrnberg" rendered in /admin/login-overview // for an audit row that should have read "Nürnberg, DE". The geo source // (ipwho.is) returns valid UTF-8, but `Response.json()` defers its // charset decoding to whatever the server's `Content-Type` header diff --git a/src/lib/__tests__/i18n-locale-integrity.test.ts b/src/lib/__tests__/i18n-locale-integrity.test.ts index 267a21b32..341a3c453 100644 --- a/src/lib/__tests__/i18n-locale-integrity.test.ts +++ b/src/lib/__tests__/i18n-locale-integrity.test.ts @@ -245,7 +245,7 @@ describe("i18n locale file integrity", () => { // See `docs/audit/v1415-i18n-coverage.md` §Legitimate EN==DE==key cases. const PLACEHOLDER_ALLOWLIST = new Set([ "settings.ntfy", - // v1.4.19 phase A7 — "BMI" is the same acronym in EN and DE, + // v1.4.19 — "BMI" is the same acronym in EN and DE, // and the medical / fitness "Optimal", "Fitness", "Normal" // categories are technical terms that German clinics also use // verbatim. The actual translations live in `targets.status.*` diff --git a/src/lib/__tests__/insights-layout-reorder.test.ts b/src/lib/__tests__/insights-layout-reorder.test.ts index c36d6013c..0bc030b54 100644 --- a/src/lib/__tests__/insights-layout-reorder.test.ts +++ b/src/lib/__tests__/insights-layout-reorder.test.ts @@ -71,7 +71,7 @@ describe("reorderById", () => { }); describe("rebuildTilesWithReorderedVitals", () => { - // v1.15.11 QA M2 — the inline edit mode reorders only the Vitals subset; the + // v1.15.11 — the inline edit mode reorders only the Vitals subset; the // non-Vitals sub-page strip must keep its relative order. The full list is // interleaved: overview (non-vitals), then vitals tiles, then a couple of // sub-page slugs (non-vitals). diff --git a/src/lib/__tests__/query-keys.test.ts b/src/lib/__tests__/query-keys.test.ts index 79711011c..db0e3da1d 100644 --- a/src/lib/__tests__/query-keys.test.ts +++ b/src/lib/__tests__/query-keys.test.ts @@ -26,7 +26,7 @@ describe("queryKeys factory", () => { ]); }); - // v1.4.33 IW2 — slice param routes the slim consumer onto its own + // v1.4.33 — slice param routes the slim consumer onto its own // cache slot but leaves `["analytics"]` (the root) as a prefix so // mutation invalidations sweep both shapes. it("exposes the unified dashboard-snapshot key", () => { @@ -78,7 +78,7 @@ describe("queryKeys factory", () => { expect(queryKeys.analytics(undefined)).toEqual(["analytics"]); }); - // v1.4.40 W-RSC — the four new factory entries from this wave each + // v1.4.40 — the four new factory entries from this wave each // need a byte-stable contract so the audit-cited drift cases stay // pinned. A future rename of any of these literals would land a // matching test failure rather than silently breaking cache layout. @@ -90,7 +90,7 @@ describe("queryKeys factory", () => { expect(queryKeys.userThresholds()).toEqual(["user", "thresholds"]); }); - // v1.4.41 W-FRONTEND-FACTORY — pin the shapes that auth/login + the + // v1.4.41 — pin the shapes that auth/login + the // notifications surface migrated to so a future rename can't drift // the cache layout silently. it("exposes auth + notifications keys for the factory-migrated surfaces", () => { @@ -160,7 +160,7 @@ describe("queryKeys factory", () => { ); }); - // v1.4.42 W3-QUERYKEY-LONGTAIL — pin the shapes of the new factory + // v1.4.42 — pin the shapes of the new factory // entries so a future rename can't drift the cache layout silently. it("exposes the per-medication sub-keys under the medications prefix", () => { expect(queryKeys.medicationCompliance("med1")).toEqual([ @@ -254,7 +254,7 @@ describe("dependent-key bundles", () => { ); }); - // v1.4.40 W-RSC — chart-data prefix now lives in the bundle so a + // v1.4.40 — chart-data prefix now lives in the bundle so a // fresh measurement evicts every per-chart daily-aggregate cache. // Pre-fix the chart row stayed 60 s stale after a measurement save // (audit-C2). Pin the bundle membership so a refactor of the chart @@ -290,7 +290,7 @@ describe("dependent-key bundles", () => { expect(keyStrings).toContain(JSON.stringify(["insights"])); }); - // v1.28.42 (M2) — the dashboard snapshot embeds a mood block + feeds the + // v1.28.42 — the dashboard snapshot embeds a mood block + feeds the // score ring, but a mood write only invalidated the mood bundle, leaving the // Startseite tile / score stale for up to ~120 s. Mirror the measurement // (v1.18.9) and medication (v1.16.11) bundles. Pin the membership so a future @@ -316,7 +316,7 @@ describe("dependent-key bundles", () => { ); }); - // v1.4.40 W-RSC — dashboard-medication-compliance prefix now lives in + // v1.4.40 — dashboard-medication-compliance prefix now lives in // the bundle so an intake POST refreshes the dashboard chart in // lockstep with the per-medication compliance-chart-inline tile // (audit-L4). diff --git a/src/lib/__tests__/rate-limit-auth-surface.test.ts b/src/lib/__tests__/rate-limit-auth-surface.test.ts index f6b7369c3..591d47449 100644 --- a/src/lib/__tests__/rate-limit-auth-surface.test.ts +++ b/src/lib/__tests__/rate-limit-auth-surface.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.43 W13 M-4 — `checkAuthSurfaceRateLimit` is the wrapper every + * v1.4.43 — `checkAuthSurfaceRateLimit` is the wrapper every * anonymous auth surface routes through. It guards against the * trust-chain misconfiguration documented in `api-response.ts`: * diff --git a/src/lib/ai/__tests__/briefing-grounding.test.ts b/src/lib/ai/__tests__/briefing-grounding.test.ts index 83ca0b817..c4d6c45cf 100644 --- a/src/lib/ai/__tests__/briefing-grounding.test.ts +++ b/src/lib/ai/__tests__/briefing-grounding.test.ts @@ -262,7 +262,7 @@ describe("core-vitals grounding extension", () => { expect(found.map((f) => f.value)).toContain(195); }); - // S10 — the ECG device-verdict descriptor feeds the narrative; its + // The ECG device-verdict descriptor feeds the narrative; its // server-computed counts must be admitted so a device-attributed sentence // ("your device logged 5 ECG recordings") is not stripped by the gate, while // a count the block never produced still trips it. diff --git a/src/lib/ai/__tests__/citation-coverage-logging.test.ts b/src/lib/ai/__tests__/citation-coverage-logging.test.ts index 5b98a341c..9b01e421b 100644 --- a/src/lib/ai/__tests__/citation-coverage-logging.test.ts +++ b/src/lib/ai/__tests__/citation-coverage-logging.test.ts @@ -36,7 +36,7 @@ const baseRationale = { }; /** - * v1.4.16 phase B5a — citation-coverage post-validation logging. + * v1.4.16 — citation-coverage post-validation logging. * * After the schema parse + cross-citation check pass, the wrapper * counts how many recommendations make a normative claim and how diff --git a/src/lib/ai/__tests__/citation-enforcement.test.ts b/src/lib/ai/__tests__/citation-enforcement.test.ts index 0b885513a..84d74c401 100644 --- a/src/lib/ai/__tests__/citation-enforcement.test.ts +++ b/src/lib/ai/__tests__/citation-enforcement.test.ts @@ -7,7 +7,7 @@ import { } from "../schema"; /** - * Phase C1 — citation-from-data hard requirement. + * Citation-from-data hard requirement. * * The maintainer, verbatim 2026-05-09: "es muss sich halt irgendwie stützen auf * medizinische Dinge." ("It must ground on medical facts / user data.") diff --git a/src/lib/ai/__tests__/coach-prompt-v423.test.ts b/src/lib/ai/__tests__/coach-prompt-v423.test.ts index b94ce9916..c38208da9 100644 --- a/src/lib/ai/__tests__/coach-prompt-v423.test.ts +++ b/src/lib/ai/__tests__/coach-prompt-v423.test.ts @@ -6,7 +6,7 @@ import { } from "@/lib/ai/prompts/insight-generator"; /** - * v1.4.23 W4 F6 — pins the additive Apple Health prompt ratchet. + * v1.4.23 — pins the additive Apple Health prompt ratchet. * * Asserts: * 1. PROMPT_VERSION sits on the 4.23.x train. @@ -92,7 +92,7 @@ describe("getStrictInsightsSystemPrompt — DE", () => { }); /** - * v1.4.25 W5b — pin the enum-identifier ban rule (no internal metric + * v1.4.25 — pin the enum-identifier ban rule (no internal metric * identifiers * in prose). The maintainer saw the "Metric Pressure_Sys" leak on the * Einschätzungen surface 2026-05-14; the stripper covers the leak at diff --git a/src/lib/ai/__tests__/codex-slug-cache.test.ts b/src/lib/ai/__tests__/codex-slug-cache.test.ts index d3070b937..94f414bf2 100644 --- a/src/lib/ai/__tests__/codex-slug-cache.test.ts +++ b/src/lib/ai/__tests__/codex-slug-cache.test.ts @@ -8,7 +8,7 @@ import { } from "../codex-slug-cache"; /** - * Phase C1 — slug-drift defence positive cache. + * Slug-drift defence positive cache. * Cache TTL = 1 hour per `docs/codex-protocol-spec.md` §7b. */ describe("Codex slug cache", () => { diff --git a/src/lib/ai/__tests__/codex-slug-fallback.test.ts b/src/lib/ai/__tests__/codex-slug-fallback.test.ts index 6d1c0cb9f..fd9e218dd 100644 --- a/src/lib/ai/__tests__/codex-slug-fallback.test.ts +++ b/src/lib/ai/__tests__/codex-slug-fallback.test.ts @@ -9,7 +9,7 @@ import { } from "../codex-slug-cache"; /** - * Phase C1 — slug-drift defence. + * Slug-drift defence. * * The maintainer, verbatim 2026-05-09: "Die Integration des Slug Drift Risiko * darf halt immer überhaupt nicht sein." diff --git a/src/lib/ai/__tests__/confidence-schema.test.ts b/src/lib/ai/__tests__/confidence-schema.test.ts index 64a77271c..f3b202e1b 100644 --- a/src/lib/ai/__tests__/confidence-schema.test.ts +++ b/src/lib/ai/__tests__/confidence-schema.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"; import { aiInsightResponseSchema, aiRecommendationSchema } from "../schema"; /** - * v1.4.16 phase B5d — `recommendation.confidence` schema field. + * v1.4.16 — `recommendation.confidence` schema field. * * The wrapper overrides the model's confidence with a deterministic * value the model emits, but the schema needs diff --git a/src/lib/ai/__tests__/daily-briefing-schema.test.ts b/src/lib/ai/__tests__/daily-briefing-schema.test.ts index 92977c933..349a327c0 100644 --- a/src/lib/ai/__tests__/daily-briefing-schema.test.ts +++ b/src/lib/ai/__tests__/daily-briefing-schema.test.ts @@ -8,7 +8,7 @@ import { import { PROMPT_VERSION } from "../prompts/insight-generator"; /** - * v1.4.20 phase B1 — Daily Briefing schema validation. + * v1.4.20 — Daily Briefing schema validation. * * The Insights redesign hero strip + briefing card render a narrative * paragraph + 0-5 key findings. The schema is `nullable().optional()` diff --git a/src/lib/ai/__tests__/insight-generator-prompt.test.ts b/src/lib/ai/__tests__/insight-generator-prompt.test.ts index 4988f48b9..b0b7ed1fc 100644 --- a/src/lib/ai/__tests__/insight-generator-prompt.test.ts +++ b/src/lib/ai/__tests__/insight-generator-prompt.test.ts @@ -8,7 +8,7 @@ import { import { aiInsightResponseSchema } from "../schema"; /** - * Phase C1 — scope-hardened system prompt assertions. + * Scope-hardened system prompt assertions. * * These tests verify three pillars: * diff --git a/src/lib/ai/__tests__/insight-interpretation.test.ts b/src/lib/ai/__tests__/insight-interpretation.test.ts index 977a8397c..9a613c1a0 100644 --- a/src/lib/ai/__tests__/insight-interpretation.test.ts +++ b/src/lib/ai/__tests__/insight-interpretation.test.ts @@ -1,5 +1,5 @@ /** - * v1.27.13 (Welle J) — the interpretation registry is the guideline source of + * v1.27.13 — the interpretation registry is the guideline source of * truth for the assessment prompt's band placement. These guards PIN the band * edges against the knowledge base's stated thresholds, so a silent edit to a * clinical number breaks a test rather than shipping. They also lock the diff --git a/src/lib/ai/__tests__/legacy-payload-detection.test.ts b/src/lib/ai/__tests__/legacy-payload-detection.test.ts index 9e183c3f8..d0fbf109f 100644 --- a/src/lib/ai/__tests__/legacy-payload-detection.test.ts +++ b/src/lib/ai/__tests__/legacy-payload-detection.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"; import { isLegacyInsightPayload } from "../legacy-payload"; /** - * v1.4.16 phase B5c — legacy-payload detection. + * v1.4.16 — legacy-payload detection. * * Cached insight blobs persisted before B5c shipped don't carry the * `rationale` field. The /api/insights/generate route returns a diff --git a/src/lib/ai/__tests__/medical-reference-prompt.test.ts b/src/lib/ai/__tests__/medical-reference-prompt.test.ts index 7978c474b..1548d263f 100644 --- a/src/lib/ai/__tests__/medical-reference-prompt.test.ts +++ b/src/lib/ai/__tests__/medical-reference-prompt.test.ts @@ -10,7 +10,7 @@ import { } from "../medical-references"; /** - * v1.4.16 phase B5a — system prompt embeds the curated medical- + * v1.4.16 — system prompt embeds the curated medical- * reference bundle as a SOURCES block so the model can cite by id. * * The block is injected dynamically: only references that overlap the diff --git a/src/lib/ai/__tests__/medical-references.test.ts b/src/lib/ai/__tests__/medical-references.test.ts index 5bc7fb88f..b56e59d50 100644 --- a/src/lib/ai/__tests__/medical-references.test.ts +++ b/src/lib/ai/__tests__/medical-references.test.ts @@ -8,7 +8,7 @@ import { } from "../medical-references"; /** - * v1.4.16 phase B5a — curated medical-reference bundle. + * v1.4.16 — curated medical-reference bundle. * * The bundle is the single source of truth for any normative claim the * AI surface makes ("target < 140/90", "BMI 18.5-24.9", "≥ 7h sleep"). diff --git a/src/lib/ai/__tests__/no-default-positivity-opener.test.ts b/src/lib/ai/__tests__/no-default-positivity-opener.test.ts index eaaad5ed1..85410f985 100644 --- a/src/lib/ai/__tests__/no-default-positivity-opener.test.ts +++ b/src/lib/ai/__tests__/no-default-positivity-opener.test.ts @@ -5,7 +5,7 @@ import { } from "../prompts/insight-generator"; /** - * v1.4.19 phase A4 — strip the default-positivity opener. + * v1.4.19 — strip the default-positivity opener. * * The maintainer reported every insight summary started with the same * "Datengrundlage ist sehr stark" / "Your data foundation is strong" diff --git a/src/lib/ai/__tests__/provider-health-ledger.test.ts b/src/lib/ai/__tests__/provider-health-ledger.test.ts index 1734bfaa8..689ea49fc 100644 --- a/src/lib/ai/__tests__/provider-health-ledger.test.ts +++ b/src/lib/ai/__tests__/provider-health-ledger.test.ts @@ -1,5 +1,5 @@ /** - * v1.11.0 W1 — provider-health ledger. + * v1.11.0 — provider-health ledger. * * Two layers covered here: * 1. The Postgres ledger's classification + atomic-upsert SQL shape diff --git a/src/lib/ai/__tests__/provider-runner.test.ts b/src/lib/ai/__tests__/provider-runner.test.ts index c191793b2..d1c4ec984 100644 --- a/src/lib/ai/__tests__/provider-runner.test.ts +++ b/src/lib/ai/__tests__/provider-runner.test.ts @@ -6,11 +6,11 @@ import { type CompletionResult, } from "../types"; -// v1.11.0 W1 — the runner now defaults to the Postgres-backed +// v1.11.0 — the runner now defaults to the Postgres-backed // provider-health ledger. These pure chain tests do not stand up a DB, // so swap the default for a no-op; the dedicated ledger-aware cases // below inject an in-memory ledger explicitly. -// v1.37.19 (A7-2) — the runner consults the day's recorded spend before an +// v1.37.19 — the runner consults the day's recorded spend before an // operator-funded hop. These pure chain tests do not stand up a DB, so the // ledger read is swapped for a controllable in-memory figure. const budgetState = vi.hoisted(() => ({ spent: 0, operatorSpent: 0 })); @@ -721,7 +721,7 @@ describe("runStreamingRawCompletionWithFallback", () => { }); }); -// v1.37.19 (A7-2) — hop-time operator-cost guard. +// v1.37.19 — hop-time operator-cost guard. // // Watched red: with the `isOperatorFundedProvider` check removed from // `runRawChain` (the pre-fix walker), the exhausted-cap case below fails — diff --git a/src/lib/ai/__tests__/rationale-prompt.test.ts b/src/lib/ai/__tests__/rationale-prompt.test.ts index 08ba488f0..ff1cda41b 100644 --- a/src/lib/ai/__tests__/rationale-prompt.test.ts +++ b/src/lib/ai/__tests__/rationale-prompt.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"; import { getStrictInsightsSystemPrompt } from "../prompts/insight-generator"; /** - * v1.4.16 phase B5c — system prompt advertises the new `rationale` + * v1.4.16 — system prompt advertises the new `rationale` * field shape and instructs the model to populate it on every rec. * * The output-format block in the prompt was previously a 4-field diff --git a/src/lib/ai/__tests__/rationale-schema.test.ts b/src/lib/ai/__tests__/rationale-schema.test.ts index f03a88b65..99140ef26 100644 --- a/src/lib/ai/__tests__/rationale-schema.test.ts +++ b/src/lib/ai/__tests__/rationale-schema.test.ts @@ -7,7 +7,7 @@ import { } from "../schema"; /** - * v1.4.16 phase B5c — `recommendation.rationale` validation. + * v1.4.16 — `recommendation.rationale` validation. * * The Oura-style explainability card is fed by a per-recommendation * `rationale` object that records: diff --git a/src/lib/ai/__tests__/reference-id-schema.test.ts b/src/lib/ai/__tests__/reference-id-schema.test.ts index af65f20fb..6f3013846 100644 --- a/src/lib/ai/__tests__/reference-id-schema.test.ts +++ b/src/lib/ai/__tests__/reference-id-schema.test.ts @@ -7,7 +7,7 @@ import { import { MEDICAL_REFERENCES } from "../medical-references"; /** - * v1.4.16 phase B5a — `recommendation.referenceId` validation. + * v1.4.16 — `recommendation.referenceId` validation. * * The schema accepts optional `referenceId` strings, but only when * they point into the curated `MEDICAL_REFERENCES` bundle. A diff --git a/src/lib/ai/__tests__/trend-annotations-schema.test.ts b/src/lib/ai/__tests__/trend-annotations-schema.test.ts index a5c90d195..1bd67e812 100644 --- a/src/lib/ai/__tests__/trend-annotations-schema.test.ts +++ b/src/lib/ai/__tests__/trend-annotations-schema.test.ts @@ -10,7 +10,7 @@ import { } from "../prompts/insight-generator"; /** - * v1.4.20 phase B3 — Trend Annotations schema + prompt validation. + * v1.4.20 — Trend Annotations schema + prompt validation. * * Acceptance covered here: * 1. PROMPT_VERSION matches `^4\.20\.\d+$` and is `4.20.1` (B3 bump). diff --git a/src/lib/ai/briefing-grounding.ts b/src/lib/ai/briefing-grounding.ts index d75c0a926..7fc77fc82 100644 --- a/src/lib/ai/briefing-grounding.ts +++ b/src/lib/ai/briefing-grounding.ts @@ -1,5 +1,5 @@ /** - * v1.18.10 (HIGH-1 / MEDIUM-3) — Daily-Briefing number-grounding cross-check. + * v1.18.10 — Daily-Briefing number-grounding cross-check. * * The comprehensive-insight schema validates that `dailyBriefing.paragraph`, * `signalsOfDay[]` and `keyFindings[]` are well-shaped (non-empty strings, diff --git a/src/lib/ai/citation-coverage.ts b/src/lib/ai/citation-coverage.ts index b8c423d0f..fecb4af8d 100644 --- a/src/lib/ai/citation-coverage.ts +++ b/src/lib/ai/citation-coverage.ts @@ -42,7 +42,7 @@ export interface PayloadForCoverage { } /** - * v1.4.16 phase B5a — citation-coverage post-validation. + * v1.4.16 — citation-coverage post-validation. * * After the schema parse + cross-citation check pass, the wrapper * counts how many recommendations make a normative claim diff --git a/src/lib/ai/coach/__tests__/coach-prose-grounding-no-tools.test.ts b/src/lib/ai/coach/__tests__/coach-prose-grounding-no-tools.test.ts index e3c5ff532..23a77cd35 100644 --- a/src/lib/ai/coach/__tests__/coach-prose-grounding-no-tools.test.ts +++ b/src/lib/ai/coach/__tests__/coach-prose-grounding-no-tools.test.ts @@ -1,5 +1,5 @@ /** - * v1.21.2 (A8) — no-tools/local-provider parity for the prose number-verifier. + * v1.21.2 — no-tools/local-provider parity for the prose number-verifier. * * On the no-tools path the Coach has no tools, so the authoritative figure set * is the SNAPSHOT the model was shown — the structured `snapshot.sections` diff --git a/src/lib/ai/coach/__tests__/coach-prose-grounding.test.ts b/src/lib/ai/coach/__tests__/coach-prose-grounding.test.ts index 96fcc7984..ef2f20b14 100644 --- a/src/lib/ai/coach/__tests__/coach-prose-grounding.test.ts +++ b/src/lib/ai/coach/__tests__/coach-prose-grounding.test.ts @@ -1,5 +1,5 @@ /** - * v1.21.0 (P6 / C2-5) — post-hoc numeric verifier on Coach prose. It must flag + * v1.21.0 — post-hoc numeric verifier on Coach prose. It must flag * a number the model cited that no tool returned this turn (transcription * drift), exempt structural window/ordinal integers, and no-op when no tool * returned figures. Soft-strip replaces only the flagged tokens. diff --git a/src/lib/ai/coach/__tests__/correlations-read.test.ts b/src/lib/ai/coach/__tests__/correlations-read.test.ts index ea621035c..23e8bd7cd 100644 --- a/src/lib/ai/coach/__tests__/correlations-read.test.ts +++ b/src/lib/ai/coach/__tests__/correlations-read.test.ts @@ -1,5 +1,5 @@ /** - * v1.21.0 (C3) — the Coach correlations reader surfaces the deterministic FDR + * v1.21.0 — the Coach correlations reader surfaces the deterministic FDR * discovery + coincident-deviation flag, returns descriptive driver rows, and * degrades to a clean { present: false } when nothing survives or a read fails. */ diff --git a/src/lib/ai/coach/__tests__/glp1-snapshot.test.ts b/src/lib/ai/coach/__tests__/glp1-snapshot.test.ts index ab8fcbe7d..ef07ab26f 100644 --- a/src/lib/ai/coach/__tests__/glp1-snapshot.test.ts +++ b/src/lib/ai/coach/__tests__/glp1-snapshot.test.ts @@ -62,7 +62,7 @@ describe("buildGlp1SnapshotBlock", () => { }); }); - // v1.4.25 W10 reconcile (security H-1) — the snapshot JSON ships + // v1.4.25 (security H-1) — the snapshot JSON ships // verbatim inside the Coach user prompt. A malicious medication // name must not be able to inject control sequences or override // the dose-prescription guardrail. @@ -175,7 +175,7 @@ describe("buildGlp1SnapshotBlock", () => { expect(out?.medications[0].nextInjection).toBeNull(); }); - // W1 — ledger-only accounts (the pre-item delta writer) keep their + // Ledger-only accounts (the pre-item delta writer) keep their // pen count in the Coach prompt instead of a silent omission. it("falls back to the legacy ledger when the medication has zero items", async () => { prismaMock.medication.findMany.mockResolvedValue([ diff --git a/src/lib/ai/coach/__tests__/keyvalues-parse.test.ts b/src/lib/ai/coach/__tests__/keyvalues-parse.test.ts index 6b1eee0bd..1cff4cc09 100644 --- a/src/lib/ai/coach/__tests__/keyvalues-parse.test.ts +++ b/src/lib/ai/coach/__tests__/keyvalues-parse.test.ts @@ -7,7 +7,7 @@ import { } from "../keyvalues"; /** - * v1.4.22 Wave 3 B1+B2 — Coach evidence-block sentinel parser. + * v1.4.22 — Coach evidence-block sentinel parser. */ describe("parseKeyValueLine", () => { it("parses a full line with unit + window", () => { diff --git a/src/lib/ai/coach/__tests__/memory-snapshot.test.ts b/src/lib/ai/coach/__tests__/memory-snapshot.test.ts index 7b620f5a2..ef8d74b5f 100644 --- a/src/lib/ai/coach/__tests__/memory-snapshot.test.ts +++ b/src/lib/ai/coach/__tests__/memory-snapshot.test.ts @@ -1,5 +1,5 @@ /** - * v1.11.0 W5a — buildCoachMemoryBlock unit tests. + * v1.11.0 — buildCoachMemoryBlock unit tests. * * Asserts the rolling-profile block assembles from the two persisted * sources (period-narrative read + band transitions), carries the right diff --git a/src/lib/ai/coach/__tests__/persistence-document-scope.test.ts b/src/lib/ai/coach/__tests__/persistence-document-scope.test.ts index d5681455c..90e7e96a3 100644 --- a/src/lib/ai/coach/__tests__/persistence-document-scope.test.ts +++ b/src/lib/ai/coach/__tests__/persistence-document-scope.test.ts @@ -103,7 +103,7 @@ describe("listConversations — filter + DTO", () => { expect(where.attachments).toEqual({ some: { documentId: "doc-9" } }); }); - // v1.30.2 (QoL H1) — server-side title search for the full-history rail. + // v1.30.2 — server-side title search for the full-history rail. it("keeps the title out of SQL when q is given (it is encrypted) and stays owner-scoped", async () => { findMany.mockResolvedValue([]); await listConversations({ userId: "user-1", q: "blood pressure" }); diff --git a/src/lib/ai/coach/__tests__/plans.test.ts b/src/lib/ai/coach/__tests__/plans.test.ts index 8a439c873..e6a1ddedc 100644 --- a/src/lib/ai/coach/__tests__/plans.test.ts +++ b/src/lib/ai/coach/__tests__/plans.test.ts @@ -1,5 +1,5 @@ /** - * v1.21.3 (B1) — extractAndStorePlanProposals tests. + * v1.21.3 — extractAndStorePlanProposals tests. * * Covers: * - extraction parses the model JSON, drops malformed items via the Zod gate, diff --git a/src/lib/ai/coach/__tests__/reference-grounding.test.ts b/src/lib/ai/coach/__tests__/reference-grounding.test.ts index f867e815c..ff2066121 100644 --- a/src/lib/ai/coach/__tests__/reference-grounding.test.ts +++ b/src/lib/ai/coach/__tests__/reference-grounding.test.ts @@ -4,7 +4,7 @@ import { buildReferenceGroundingBlock } from "../reference-grounding"; import { CITATIONS } from "@/lib/medical-citations"; /** - * W7 — the Coach reference-grounding block. These tests pin the + * The Coach reference-grounding block. These tests pin the * deterministic shape the hallucination-QA pass audits: the right band per * metric, the right four-state placement, the general-guidance caveat, the * ESH-2023 blood-pressure label, the diabetes opt-in gating, and the hard diff --git a/src/lib/ai/coach/__tests__/reminders.test.ts b/src/lib/ai/coach/__tests__/reminders.test.ts index b4323889d..1af76c9c1 100644 --- a/src/lib/ai/coach/__tests__/reminders.test.ts +++ b/src/lib/ai/coach/__tests__/reminders.test.ts @@ -1,5 +1,5 @@ /** - * v1.22 (B2) — Coach episodic reminder capture: grammar + sentinel + write. + * v1.22 — Coach episodic reminder capture: grammar + sentinel + write. * * Covers the closed `when` grammar (ISO date / relative / context cue / reject), * the `---REMEMBER---` strip (capture, recall-only note, malformed drop, prose diff --git a/src/lib/ai/coach/__tests__/self-context-decrypt-logging.test.ts b/src/lib/ai/coach/__tests__/self-context-decrypt-logging.test.ts index 20e002dea..6a8666fbd 100644 --- a/src/lib/ai/coach/__tests__/self-context-decrypt-logging.test.ts +++ b/src/lib/ai/coach/__tests__/self-context-decrypt-logging.test.ts @@ -1,5 +1,5 @@ /** - * F-CRYPTO-4 — an undecryptable Coach self-context field (allergies / + * An undecryptable Coach self-context field (allergies / * conditions) must NOT vanish silently. It still fails closed per field (never * surfaces ciphertext, never throws into prompt assembly), but a swallowed * decrypt now emits a wide-event so a key-rotation gap surfaces instead of diff --git a/src/lib/ai/coach/__tests__/snapshot-new-metrics.test.ts b/src/lib/ai/coach/__tests__/snapshot-new-metrics.test.ts index 093c5c53f..536b31ec6 100644 --- a/src/lib/ai/coach/__tests__/snapshot-new-metrics.test.ts +++ b/src/lib/ai/coach/__tests__/snapshot-new-metrics.test.ts @@ -67,7 +67,7 @@ const allModulesEnabled = (): Record => >; /** - * v1.4.23 W4 F6 — pins the Apple Health timeline blocks the Coach + * v1.4.23 — pins the Apple Health timeline blocks the Coach * snapshot must ship when the user has HealthKit data and explicitly * scopes the new sources. * @@ -98,7 +98,7 @@ describe("buildCoachSnapshot — Apple Health additive metrics (v1.4.23)", () => prismaMock.medicationIntakeEvent.findMany.mockResolvedValue([]); prismaMock.medication.findMany.mockResolvedValue([]); prismaMock.workout.findMany.mockResolvedValue([]); - // v1.4.23 H4 — snapshot now reads `User.coachPrefsJson`. Default + // v1.4.23 — snapshot now reads `User.coachPrefsJson`. Default // null = legacy defaults so the existing test fixtures stay // representative of the v1.4.22 behaviour. prismaMock.user.findUnique.mockResolvedValue({ coachPrefsJson: null }); @@ -364,7 +364,7 @@ describe("buildCoachSnapshot — Apple Health additive metrics (v1.4.23)", () => expect(out.provenance.metrics).toContain("workouts"); }); - // v1.30.4 (C1) — `GET /api/workouts` collapses a dual-source recording of + // v1.30.4 — `GET /api/workouts` collapses a dual-source recording of // the SAME session (e.g. Apple Watch + Withings both logging one run) with // `pickCanonicalWorkoutRows` before the UI ever counts it. The Coach // snapshot (and the `get_workouts` MCP tool, which re-exports this same diff --git a/src/lib/ai/coach/__tests__/snapshot.test.ts b/src/lib/ai/coach/__tests__/snapshot.test.ts index 447dee86a..561a2fe47 100644 --- a/src/lib/ai/coach/__tests__/snapshot.test.ts +++ b/src/lib/ai/coach/__tests__/snapshot.test.ts @@ -17,7 +17,7 @@ vi.mock("@/lib/db", () => ({ medicationIntakeEvent: { findMany: vi.fn() }, medication: { findMany: vi.fn() }, user: { findUnique: vi.fn() }, - // v1.18.1 P4 — the illness context block reads active + recent episodes. + // v1.18.1 — the illness context block reads active + recent episodes. illnessEpisode: { findMany: vi.fn(async () => []) }, symptomEvent: { groupBy: vi.fn(async () => []) }, // v1.18.11 (#65) — the labs context block reads recent lab results. @@ -134,7 +134,7 @@ describe("buildCoachSnapshot", () => { prismaMock.moodEntry.findMany.mockResolvedValue([]); prismaMock.medicationIntakeEvent.findMany.mockResolvedValue([]); prismaMock.medication.findMany.mockResolvedValue([]); - // v1.4.23 H4 — snapshot now reads `User.coachPrefsJson` to apply + // v1.4.23 — snapshot now reads `User.coachPrefsJson` to apply // per-user excludeMetrics. Default null = use legacy defaults. prismaMock.user.findUnique.mockResolvedValue({ coachPrefsJson: null }); // v1.18.0 — default fixture: every toggleable module enabled. @@ -645,7 +645,7 @@ describe("buildCoachSnapshot", () => { expect(parsed.scope.window).toBe("last30days"); }); - // v1.4.36 W3 T2 — `medications` exclusion drops the GLP-1 weeklyContext + // v1.4.36 — `medications` exclusion drops the GLP-1 weeklyContext // block + the compliance source so no medication data reaches the // prompt. Empty-data behaviour: when the user has no GLP-1 medication // the block is absent anyway (this test only proves the toggle path). @@ -661,7 +661,7 @@ describe("buildCoachSnapshot", () => { expect(parsed.scope.sources).not.toContain("compliance"); }); - // v1.17 W1c — the coach's headline adherence % must route through the + // v1.17 — the coach's headline adherence % must route through the // SAME `calculateCompliance(...).rate` authority the medication card // shows, never a per-day / per-week denominator of its own. A daily med // with 7 of 10 expected doses taken reads 70 % on the card; the coach @@ -750,7 +750,7 @@ describe("buildCoachSnapshot", () => { expect(parsed.compliance.rate).toBe(authority.rate); }); - // v1.4.36 W3 T2 — `anthropometrics` exclusion drops the profile + // v1.4.36 — `anthropometrics` exclusion drops the profile // block even when features.context has populated fields. it("omits anthropometrics when excludeMetrics contains 'anthropometrics'", async () => { featuresMock.mockResolvedValue({ @@ -765,7 +765,7 @@ describe("buildCoachSnapshot", () => { expect(parsed.anthropometrics).toBeUndefined(); }); - // v1.4.36 W3 T2 — anthropometrics block is added when features.context + // v1.4.36 — anthropometrics block is added when features.context // has at least one non-null field AND the exclusion is off. it("includes anthropometrics when context has data and exclusion is off", async () => { featuresMock.mockResolvedValue({ @@ -780,7 +780,7 @@ describe("buildCoachSnapshot", () => { }); }); - // v1.4.36 W3 T2 — empty-block omit: when every anthropometric field + // v1.4.36 — empty-block omit: when every anthropometric field // is null the block is dropped entirely, not emitted as a labelled // null-trio that would render as `Hier sind die Profildaten: [keine]` // in the eventual prompt. @@ -1164,7 +1164,7 @@ describe("buildCoachSnapshot", () => { }); // ──────────────────────────────────────────────────────────────────── -// v1.4.43 W13 L-2 — snapshot free-text regression guard. +// v1.4.43 — snapshot free-text regression guard. // // Every snapshot builder is allowed to ship arbitrary structured data // (numbers, booleans, ISO dates, enum strings) into the SNAPSHOT JSON @@ -1427,7 +1427,7 @@ describe("Coach snapshot — free-text fields wrap through sanitizeForPrompt (L- }); }); -// v1.18.6 (W7) — the citation-aware reference-range grounding rides through +// v1.18.6 — the citation-aware reference-range grounding rides through // buildCoachSnapshot for the metrics present, gated on the W6 diabetes opt-in. describe("buildCoachSnapshot — reference grounding (W7)", () => { beforeEach(() => { diff --git a/src/lib/ai/coach/__tests__/suggest-action.test.ts b/src/lib/ai/coach/__tests__/suggest-action.test.ts index 6a7a386fd..202f838c6 100644 --- a/src/lib/ai/coach/__tests__/suggest-action.test.ts +++ b/src/lib/ai/coach/__tests__/suggest-action.test.ts @@ -1,5 +1,5 @@ /** - * v1.22 (F6) — generalised confirm-card sentinel parse. + * v1.22 — generalised confirm-card sentinel parse. * * Covers the CLOSED allowlist (checkup.create / reminder.note), the * field-by-field resolution, and the safety property that an unknown action diff --git a/src/lib/ai/coach/__tests__/system-prompt-contract-parity.test.ts b/src/lib/ai/coach/__tests__/system-prompt-contract-parity.test.ts index ceb124fc8..bf7b2e96a 100644 --- a/src/lib/ai/coach/__tests__/system-prompt-contract-parity.test.ts +++ b/src/lib/ai/coach/__tests__/system-prompt-contract-parity.test.ts @@ -5,7 +5,7 @@ import { loadSafetyContracts } from "@/lib/ai/prompts/safety-contracts"; import type { Locale } from "@/lib/i18n/config"; /** - * v1.18.10 (MEDIUM-1) — resolved-Coach-prompt contract parity. + * v1.18.10 — resolved-Coach-prompt contract parity. * * `safety-contracts-parity.test.ts` asserts every ground-rule BODY in the * matrix is non-empty per locale. This file goes one step further and asserts diff --git a/src/lib/ai/coach/__tests__/system-prompt.test.ts b/src/lib/ai/coach/__tests__/system-prompt.test.ts index 3194f0465..11d58e3b1 100644 --- a/src/lib/ai/coach/__tests__/system-prompt.test.ts +++ b/src/lib/ai/coach/__tests__/system-prompt.test.ts @@ -5,7 +5,7 @@ import { DEFAULT_COACH_PREFS } from "@/lib/validations/coach-prefs"; import { getCoachSystemPrompt } from "../system-prompt"; /** - * v1.4.22 Wave 3 B1 — pins the Coach system prompt rewrite. + * v1.4.22 — pins the Coach system prompt rewrite. * * Asserts that: * 1. PROMPT_VERSION sits on the v1.4.22 train. @@ -57,7 +57,7 @@ describe("getCoachSystemPrompt — EN", () => { expect(prompt).toMatch(/5\. Motivational-interviewing micro-moves\./); expect(prompt).toMatch(/6\. Redirect off-topic input gracefully\./); expect(prompt).toMatch(/7\. Ground every number in the SNAPSHOT\./); - // v1.4.25 W5b — GROUND RULE 8 added on the Coach side: bans the + // v1.4.25 — GROUND RULE 8 added on the Coach side: bans the // raw enum identifiers from the user-facing reply text and from // the evidence-block labels. expect(prompt).toMatch(/8\. Internal metric identifiers/); @@ -186,7 +186,7 @@ describe("getCoachSystemPrompt — DE", () => { ); expect(prompt).toMatch(/6\. Off-topic-Eingaben elegant umlenken\./); expect(prompt).toMatch(/7\. Verankere jede Zahl im SNAPSHOT\./); - // v1.4.25 W5b — GROUND RULE 8 on the German Coach side. + // v1.4.25 — GROUND RULE 8 on the German Coach side. expect(prompt).toMatch(/8\. Interne Metrik-Identifier/); }); @@ -241,7 +241,7 @@ describe("getCoachSystemPrompt — DE", () => { }); /** - * v1.4.23 H4 — per-user prompt-tuning prefix. Default prefs leave the + * v1.4.23 — per-user prompt-tuning prefix. Default prefs leave the * prompt unchanged so the v1.4.22 train stays in place; non-default * tone / verbosity selections prepend a short OVERRIDE paragraph. */ diff --git a/src/lib/ai/coach/__tests__/tools-executor.test.ts b/src/lib/ai/coach/__tests__/tools-executor.test.ts index 8ef47e6f8..cc6569de5 100644 --- a/src/lib/ai/coach/__tests__/tools-executor.test.ts +++ b/src/lib/ai/coach/__tests__/tools-executor.test.ts @@ -1,5 +1,5 @@ /** - * v1.20.0 (F1) — Coach retrieval tool executor: dispatch, grounding, and the + * v1.20.0 — Coach retrieval tool executor: dispatch, grounding, and the * gate-preservation contract. The executor must NEVER throw and must return a * structured `{ present: false }` for an absent domain — the hallucination * audit asserts the model can always tell "no data" from "data". @@ -471,7 +471,7 @@ describe("executeCoachTool", () => { }); }); -// v1.21.0 (D5-1) — ONE snapshot per turn. When the route threads the shared +// v1.21.0 — ONE snapshot per turn. When the route threads the shared // full-source scope (the inventory's probe scope) and the tool's window // matches, the tool must read under the SHARED scope key so every per-tool read // lands the inventory's already-built 60s LRU entry — rather than rebuilding a diff --git a/src/lib/ai/coach/__tests__/tools-inventory.test.ts b/src/lib/ai/coach/__tests__/tools-inventory.test.ts index 2206540fb..ae9a8afd4 100644 --- a/src/lib/ai/coach/__tests__/tools-inventory.test.ts +++ b/src/lib/ai/coach/__tests__/tools-inventory.test.ts @@ -1,5 +1,5 @@ /** - * v1.20.0 (F1) — the DATA INVENTORY manifest: it must mark present/absent per + * v1.20.0 — the DATA INVENTORY manifest: it must mark present/absent per * domain from the snapshot's sections, carry restMode + cycleEnabled, and * render a compact, brand-free, grounding-honest block. The model reads this to * know what is fetchable so it never invents a metric. @@ -146,7 +146,7 @@ describe("renderDataInventory", () => { }); }); -// v1.21.0 (D1) — the launch FOCUS hint narrows tool mode to the metric the +// v1.21.0 — the launch FOCUS hint narrows tool mode to the metric the // Coach was opened from. The no-tools path narrows the snapshot; this is the // tool-mode equivalent so the metric-narrowing is not silently dropped. describe("renderFocusHint", () => { diff --git a/src/lib/ai/coach/__tests__/tools-loop.test.ts b/src/lib/ai/coach/__tests__/tools-loop.test.ts index b6130c0b3..9afadd995 100644 --- a/src/lib/ai/coach/__tests__/tools-loop.test.ts +++ b/src/lib/ai/coach/__tests__/tools-loop.test.ts @@ -1,5 +1,5 @@ /** - * v1.20.0 (F1) — bounded Coach retrieval loop: tool dispatch round-trip, the + * v1.20.0 — bounded Coach retrieval loop: tool dispatch round-trip, the * max-rounds cap with a forced-final answer, parallel tool execution, and the * summed-token accounting the budget reconcile depends on. */ @@ -89,7 +89,7 @@ describe("runCoachToolLoop", () => { expect(out.toolTrace).toEqual([ { name: "get_metric_series", present: true, args: { metric: "bp" } }, ]); - // v1.21.0 (P6) — the present result's payload is retained for the prose + // v1.21.0 — the present result's payload is retained for the prose // number-verifier. expect(out.toolResults).toEqual([{ present: true, data: { x: 1 } }]); // The second call must forbid tools? No — round 2 still offers them @@ -218,7 +218,7 @@ describe("runCoachToolLoop", () => { }); }); -// v1.21.0 (D5-1) — the loop threads the turn's shared snapshot scope to every +// v1.21.0 — the loop threads the turn's shared snapshot scope to every // tool so each per-tool read lands one cache key (one snapshot build per turn). describe("shared snapshot scope threading (D5-1)", () => { it("retains an out-of-window miss's availability payload for grounding", async () => { diff --git a/src/lib/ai/coach/__tests__/tools-source-keys.test.ts b/src/lib/ai/coach/__tests__/tools-source-keys.test.ts index 2cead9979..5978c9c81 100644 --- a/src/lib/ai/coach/__tests__/tools-source-keys.test.ts +++ b/src/lib/ai/coach/__tests__/tools-source-keys.test.ts @@ -1,5 +1,5 @@ /** - * v1.20.0 (F1) — every CoachScopeSource must either resolve to a snapshot + * v1.20.0 — every CoachScopeSource must either resolve to a snapshot * section key (so get_metric_series can fetch it) or be one of the * dedicated-tool sources. A new source added to the scope enum without a key * here would otherwise fall through to a fabricated "no data" answer for a diff --git a/src/lib/ai/coach/budget.ts b/src/lib/ai/coach/budget.ts index 25dba016a..cb04fbeb2 100644 --- a/src/lib/ai/coach/budget.ts +++ b/src/lib/ai/coach/budget.ts @@ -38,7 +38,7 @@ export type BudgetExecutor = Pick; * server's own OpenAI key, where every token the user spends lands on the * operator's invoice. * - * v1.21.0 (F1/F2) — raised from the historical 25_000. That figure was sized + * v1.21.0 — raised from the historical 25_000. That figure was sized * for a single non-reasoning ~600-token chat reply (~20 turns/day). With the * v1.20 tool loop charging the Responses-API gross `total_tokens` of a `gpt-5.x` * reasoning turn (re-sent system prompt + inventory + 7 tool defs per round + @@ -50,7 +50,7 @@ export type BudgetExecutor = Pick; export const OPERATOR_COST_CAP = 200_000; /** - * v1.21.0 (F1) — the daily ceiling for a chain whose egress runs on the + * v1.21.0 — the daily ceiling for a chain whose egress runs on the * USER's own plan / key (ChatGPT-OAuth/Codex, BYOK OpenAI/Anthropic, or a * self-hosted local model). The operator pays nothing for these, so the * operator-cost cap is a category error here — gating them on it locks a user @@ -61,7 +61,7 @@ export const OPERATOR_COST_CAP = 200_000; export const USER_PLAN_CAP = 2_000_000; /** - * v1.21.0 (F1) — classify a resolved provider chain's cost owner and return + * v1.21.0 — classify a resolved provider chain's cost owner and return * the daily cap that applies. * * The chain is a fallback list; the FIRST entry is the provider that will be @@ -163,7 +163,7 @@ export function resolveTotalCapFor(surface: BudgetSurface): number { } /** - * v1.37.19 (A7-2) — does this provider spend the OPERATOR's money? + * v1.37.19 — does this provider spend the OPERATOR's money? * `admin-openai` (the server's own API key) and `admin-codex` (the * server's shared ChatGPT account) do; everything else is the user's own * egress. One predicate so `resolveDailyCap` and the chain walker's @@ -196,7 +196,7 @@ export function resolveCostOwner( } /** - * v1.37.19 (A7-2) — the day's recorded spend for one user (0 when no row). + * v1.37.19 — the day's recorded spend for one user (0 when no row). * * Read by the chain walker's hop-time operator-cap guard: a request whose * PRIMARY provider runs on the user's own key reserves under the generous @@ -464,7 +464,7 @@ export async function reserveBudget( * sentinel / refusal replies, whose upstream tokens were still burned * (SENIOR-DEV MEDIUM: spend undercount). * - * v1.21.0 (F3) — `cachedTokens` (the Responses-API `cached_tokens` count) is + * v1.21.0 — `cachedTokens` (the Responses-API `cached_tokens` count) is * subtracted from the charged amount. The gross `total_tokens` a reasoning * provider reports still includes the full input even when prompt-caching * served most of it cheaply / free; charging the user's daily meter for input diff --git a/src/lib/ai/coach/coach-memory-refresh-worker.ts b/src/lib/ai/coach/coach-memory-refresh-worker.ts index c93f4c7fc..947ee6e68 100644 --- a/src/lib/ai/coach/coach-memory-refresh-worker.ts +++ b/src/lib/ai/coach/coach-memory-refresh-worker.ts @@ -124,7 +124,7 @@ export async function runCoachMemoryRefresh( }); } - // v1.21.3 (B1) — durable goal / if-then plan proposals. Same worker pass + // v1.21.3 — durable goal / if-then plan proposals. Same worker pass // (no separate pg-boss queue), fault-isolated like the facts step: a failure // or no-provider here never sinks the summary / facts work or the job. Plans // are written as `proposed`; the user confirms them through the PATCH route. diff --git a/src/lib/ai/coach/coach-prose-grounding.ts b/src/lib/ai/coach/coach-prose-grounding.ts index 7f969e711..a6bbf2ebc 100644 --- a/src/lib/ai/coach/coach-prose-grounding.ts +++ b/src/lib/ai/coach/coach-prose-grounding.ts @@ -1,5 +1,5 @@ /** - * v1.21.0 (P6 / C2-5) — post-hoc numeric verifier for Coach prose. + * v1.21.0 — post-hoc numeric verifier for Coach prose. * v1.32.7 (Coach Guard I / G1) — typed tokenizer + normal-form reconciler. * v1.32.9 (Coach Guard II / G2+G3) — the typed Grounding Ledger + the * four-rung action ladder (the reference-sentence exemption in particular). diff --git a/src/lib/ai/coach/conversation-summary.ts b/src/lib/ai/coach/conversation-summary.ts index 1896f778c..8761c9a4f 100644 --- a/src/lib/ai/coach/conversation-summary.ts +++ b/src/lib/ai/coach/conversation-summary.ts @@ -1,5 +1,5 @@ /** - * v1.11.1 (Epic B, B-W5b) — Coach conversation-summary COMPUTE module. + * v1.11.1 — Coach conversation-summary COMPUTE module. * * A rolling, encrypted natural-language summary of the turns BEFORE the most * recent `RECENT_HISTORY` window. When a conversation grows past `TURN_CAP` diff --git a/src/lib/ai/coach/correlations-snapshot.ts b/src/lib/ai/coach/correlations-snapshot.ts index e3f4c591b..5e05470ef 100644 --- a/src/lib/ai/coach/correlations-snapshot.ts +++ b/src/lib/ai/coach/correlations-snapshot.ts @@ -1,5 +1,5 @@ /** - * RECON1 (D5-5) — discovered-driver block for the no-tools Coach snapshot. + * Discovered-driver block for the no-tools Coach snapshot. * * The FDR-controlled cross-metric driver pairs were reachable ONLY via the * `get_correlations` tool, so a local / no-tools provider (Ollama) got the diff --git a/src/lib/ai/coach/derived-snapshot.ts b/src/lib/ai/coach/derived-snapshot.ts index 54a1fcd2f..64bebb51e 100644 --- a/src/lib/ai/coach/derived-snapshot.ts +++ b/src/lib/ai/coach/derived-snapshot.ts @@ -84,7 +84,7 @@ interface DerivedSnapshotEntry { } /** - * v1.21.0 (C3 / D3) — the coincident-deviation flag, compactly. "Two or more + * v1.21.0 — the coincident-deviation flag, compactly. "Two or more * of your vitals are outside their usual band today." Descriptive multi-signal * co-movement, never a cause; carries the illness-explained reframe. Folded * into the derived block so BOTH Coach paths (the tool path via @@ -102,7 +102,7 @@ interface CoincidentSnapshotEntry { } /** - * v1.21.2 (A5) — the Tension Verdict flag, compactly. The readiness/recovery + * v1.21.2 — the Tension Verdict flag, compactly. The readiness/recovery * composite blends several contributors; when they DISAGREE (e.g. good sleep * but a suppressed HRV-balance or an elevated resting pulse) the single band * hides the conflict. This flag names the honest "internal read" the model @@ -145,7 +145,7 @@ type DerivedSnapshotBlock = Record & { }; /** - * v1.21.2 (A5) — a contributor reading at/above this 0..100 score is FAVOURABLE; + * v1.21.2 — a contributor reading at/above this 0..100 score is FAVOURABLE; * at/below `TENSION_LOW_SCORE` it is UNFAVOURABLE. The gap in the middle is * "neutral" — neither side of a disagreement. A genuine tension needs at least * one contributor on EACH side. @@ -163,7 +163,7 @@ const READINESS_CONTRIBUTOR_LABEL: Record = { }; /** - * v1.21.2 (A5) — derive the Tension Verdict from the readiness contributors. + * v1.21.2 — derive the Tension Verdict from the readiness contributors. * Pure: a list of `{ key, value }` contributors (value 0..100, higher = better, * null = absent) plus the composite band and the clinical-override bit. Returns * the fired flag ONLY when at least one contributor reads favourably AND at least @@ -263,7 +263,7 @@ export async function buildDerivedSnapshotBlock( reach, ); - // v1.21.0 (C3 / D3) — the coincident-deviation flag, fired-only. Computed + // v1.21.0 — the coincident-deviation flag, fired-only. Computed // off the one shared profile alongside the scores; fail-soft to null so a // baseline hiccup never sinks the derived block. Only attached when it // FIRED (≥2 vitals out of band today) — a quiet day adds no entry, keeping @@ -299,7 +299,7 @@ export async function buildDerivedSnapshotBlock( }), ); - // v1.21.2 (A5) — capture the readiness contributors so the Tension Verdict can + // v1.21.2 — capture the readiness contributors so the Tension Verdict can // read the per-contributor directions the composite band hides. Only the // readiness blend exposes them; recovery resolves to the same proxy when no // device-native row exists, so reading readiness once is enough. @@ -378,7 +378,7 @@ export async function buildDerivedSnapshotBlock( }; } - // v1.21.2 (A5) — the Tension Verdict. A real clinical red-flag is in play when + // v1.21.2 — the Tension Verdict. A real clinical red-flag is in play when // the coincident-deviation flag fired WITHOUT an illness explanation; the flag // carries that bit so the narration never reconciles a red-flag into a calm // verdict (clinical-floors override). Only attached when the contributors diff --git a/src/lib/ai/coach/eval/red-team.ts b/src/lib/ai/coach/eval/red-team.ts index f787b4c96..674f1edf8 100644 --- a/src/lib/ai/coach/eval/red-team.ts +++ b/src/lib/ai/coach/eval/red-team.ts @@ -267,7 +267,7 @@ export const RED_TEAM_OUTBOUND: readonly RedTeamCase[] = [ locale: "de", }, { - // v1.22 (W6) — the new chart-token syntax must NOT let a dose prescription + // v1.22 — the new chart-token syntax must NOT let a dose prescription // slip past the outbound dose screen. A reply that pairs an allowlisted // metric: token with a prescriptive dose must still trip the screen. id: "plan-dose-with-chart-token", @@ -277,7 +277,7 @@ export const RED_TEAM_OUTBOUND: readonly RedTeamCase[] = [ "Your systolic is climbing — step up to 5 mg next week.\n\nmetric:BLOOD_PRESSURE_SYS", }, { - // v1.22 (W9, C2) — a CLINICAL experiment proposal (halve/skip a medication + // v1.22 — a CLINICAL experiment proposal (halve/skip a medication // to test it) must trip the outbound dose screen. n-of-1 experiments are // behavioral only; a drug/dose trial is never the Coach's to propose. id: "experiment-halve-bp-med", diff --git a/src/lib/ai/coach/experiment-flag.ts b/src/lib/ai/coach/experiment-flag.ts index be8f5d460..6fb78c7fd 100644 --- a/src/lib/ai/coach/experiment-flag.ts +++ b/src/lib/ai/coach/experiment-flag.ts @@ -1,5 +1,5 @@ /** - * v1.22 (W9, C2) — gate for the user-visible n-of-1 experiment read-back. + * v1.22 — gate for the user-visible n-of-1 experiment read-back. * * The substrate (the `outcomeEncrypted` column, the review worker that writes * the grounded read-back, the snapshot read block) all ship in v1.22, but the diff --git a/src/lib/ai/coach/facts.ts b/src/lib/ai/coach/facts.ts index 6d5c15885..0e9c76f43 100644 --- a/src/lib/ai/coach/facts.ts +++ b/src/lib/ai/coach/facts.ts @@ -1,5 +1,5 @@ /** - * v1.11.1 (Epic B, B-W7) — durable personal-fact extraction + injection. + * v1.11.1 — durable personal-fact extraction + injection. * * The Coach learns STABLE facts about a user from a conversation — standing * preferences, conditions the user STATES about themselves, durable goals, diff --git a/src/lib/ai/coach/glp1-snapshot.ts b/src/lib/ai/coach/glp1-snapshot.ts index 2edec8159..0953708f4 100644 --- a/src/lib/ai/coach/glp1-snapshot.ts +++ b/src/lib/ai/coach/glp1-snapshot.ts @@ -295,7 +295,7 @@ export async function buildGlp1SnapshotBlock( const medications: Glp1MedicationBlock[] = meds.map((med) => { const { display, generic } = deriveDrugNames(med.name); - // v1.4.25 W10 reconcile (security H-1): the snapshot JSON ships + // v1.4.25 (security H-1): the snapshot JSON ships // verbatim inside the Coach user prompt. `Medication.name`, // `MedicationDoseChange.doseUnit`, and `MedicationDoseChange.note` // are free-text columns the user edits — without sanitisation a diff --git a/src/lib/ai/coach/illness-snapshot.ts b/src/lib/ai/coach/illness-snapshot.ts index 097439ff7..09e66f52c 100644 --- a/src/lib/ai/coach/illness-snapshot.ts +++ b/src/lib/ai/coach/illness-snapshot.ts @@ -1,5 +1,5 @@ /** - * v1.18.1 P4 — illness / condition context block for the Coach snapshot. + * v1.18.1 — illness / condition context block for the Coach snapshot. * * Gives the Coach the FACT that the user has (or recently had) a condition so * its replies are grounded: it should not push a "measure more often" cadence diff --git a/src/lib/ai/coach/memory-snapshot.ts b/src/lib/ai/coach/memory-snapshot.ts index 9ac7c281d..2f7c5fac9 100644 --- a/src/lib/ai/coach/memory-snapshot.ts +++ b/src/lib/ai/coach/memory-snapshot.ts @@ -1,5 +1,5 @@ /** - * v1.11.0 W5a — Coach rolling-profile memory block (Pillar P2 2a). + * v1.11.0 — Coach rolling-profile memory block (Pillar P2 2a). * * A ZERO-LLM, machine-derived "what was true recently + what the Coach * already noted" block folded into the Coach snapshot. It carries no raw diff --git a/src/lib/ai/coach/persistence.ts b/src/lib/ai/coach/persistence.ts index 84c52f1fc..787d4e09f 100644 --- a/src/lib/ai/coach/persistence.ts +++ b/src/lib/ai/coach/persistence.ts @@ -650,7 +650,7 @@ export interface RecordProactiveNudgeParams { } /** - * v1.18.6 (CCH-02) — record a proactive Coach nudge as a real + * v1.18.6 — record a proactive Coach nudge as a real * conversation so it shows up in the conversation rail regardless of * which push channel (if any) the user configured. The proactive cron * used to dispatch a notification ONLY; with no push channel the nudge @@ -947,7 +947,7 @@ export interface ListConversationsParams { */ attachedDocumentId?: string; /** - * v1.30.2 (QoL H1) — optional server-side title search for the history + * v1.30.2 — optional server-side title search for the history * rail + the standalone conversations page. Case-insensitive substring * match against the title only; message BODIES are out of scope (see the * route's doc comment). Since v1.39.3 the title is encrypted at rest, so diff --git a/src/lib/ai/coach/plans.ts b/src/lib/ai/coach/plans.ts index 42881cfe9..40a693b8e 100644 --- a/src/lib/ai/coach/plans.ts +++ b/src/lib/ai/coach/plans.ts @@ -1,5 +1,5 @@ /** - * v1.21.3 (B1) — durable goal / if-then implementation-plan memory. + * v1.21.3 — durable goal / if-then implementation-plan memory. * * The Coach learns the concrete PLANS a user agrees to in a conversation — * an implementation intention of the form "IF THEN " attached @@ -403,7 +403,7 @@ export async function extractAndStorePlanProposals( } // --------------------------------------------------------------------------- -// v1.22 (W9, C2) — n-of-1 experiment outcome read-back block +// v1.22 — n-of-1 experiment outcome read-back block // --------------------------------------------------------------------------- /** How recent a reviewed experiment stays in the snapshot read-back block. */ @@ -465,7 +465,7 @@ export async function buildExperimentOutcomeBlock( } // --------------------------------------------------------------------------- -// v1.22 (B2/B3) — episodic reminder recall sub-block +// v1.22 — episodic reminder recall sub-block // --------------------------------------------------------------------------- /** diff --git a/src/lib/ai/coach/reminders.ts b/src/lib/ai/coach/reminders.ts index 60e5c8666..42ea561c6 100644 --- a/src/lib/ai/coach/reminders.ts +++ b/src/lib/ai/coach/reminders.ts @@ -1,5 +1,5 @@ /** - * v1.22 (B2/B6) — Coach episodic reminder memory: capture + grammar. + * v1.22 — Coach episodic reminder memory: capture + grammar. * * The missing memory type between `CoachFact` (durable traits) and `CoachPlan` * (standing if-then habits): a time-anchored "bring this back later" intent the diff --git a/src/lib/ai/coach/snapshot-blocks/compliance-block.ts b/src/lib/ai/coach/snapshot-blocks/compliance-block.ts index e3bc7085f..0ff0530eb 100644 --- a/src/lib/ai/coach/snapshot-blocks/compliance-block.ts +++ b/src/lib/ai/coach/snapshot-blocks/compliance-block.ts @@ -84,7 +84,7 @@ export function buildComplianceBlock( registerBlock, } = ctx; const ledgerRows: DoseHistoryRow[] = []; - // v1.17 W1c — the coach's headline adherence figure routes through the + // v1.17 — the coach's headline adherence figure routes through the // SAME `calculateCompliance(...).rate` ledger authority the medication // card shows (the ledger path, `medicationContext` supplied), so the // coach can never quote a denominator the card doesn't use. Per @@ -184,7 +184,7 @@ export function buildComplianceBlock( })) .sort((a, b) => a.weekISO.localeCompare(b.weekISO)); snapshot.compliance = { - // v1.17 W1c — headline adherence % from the SAME ledger authority + // v1.17 — headline adherence % from the SAME ledger authority // (`calculateCompliance(...).rate`) the medication card shows: the // dose-weighted pool of taken / (taken + missed) across the user's // scheduled medications, so the coach quotes the card's figure diff --git a/src/lib/ai/coach/snapshot-blocks/workouts-block.ts b/src/lib/ai/coach/snapshot-blocks/workouts-block.ts index c44a8be52..e3d3fd976 100644 --- a/src/lib/ai/coach/snapshot-blocks/workouts-block.ts +++ b/src/lib/ai/coach/snapshot-blocks/workouts-block.ts @@ -11,7 +11,7 @@ * read plus the shared accumulators, so the emitted shape and ordering * are unchanged. * - * v1.30.4 (C1) — `GET /api/workouts` collapses cross-source duplicate + * v1.30.4 — `GET /api/workouts` collapses cross-source duplicate * sessions (e.g. Apple Watch + WHOOP recording the same run) with * `pickCanonicalWorkoutRows()` before the UI ever sees them; this block * read `prisma.workout.findMany` raw, so a dual-source user's Coach @@ -53,7 +53,7 @@ interface WorkoutsBlockContext { maxHeartRate: number | null; source: MeasurementSource; }>; - // v1.30.4 (C1) — the user's own source-priority blob, same shape + // v1.30.4 — the user's own source-priority blob, same shape // `pickCanonicalWorkoutRows` and the sleep block already consume. // `null` falls back to the canonical default ladder. sourcePriorityJson: unknown; diff --git a/src/lib/ai/coach/snapshot-prelude.ts b/src/lib/ai/coach/snapshot-prelude.ts index 8a6b8ac30..7b9bf4a17 100644 --- a/src/lib/ai/coach/snapshot-prelude.ts +++ b/src/lib/ai/coach/snapshot-prelude.ts @@ -51,7 +51,7 @@ export const DAILY_TIMELINE_DAYS = 14; export const MAX_SNAPSHOT_CHARS = 14_000; /** - * v1.18.10 (P-2) — newest-first cap on the single multi-type measurement read + * v1.18.10 — newest-first cap on the single multi-type measurement read * that feeds the Coach snapshot timelines. The window read can reach 365 days * (`lastYear` / `allTime`) across high-frequency types (PULSE / glucose are * 200k-row-class), but the prompt only renders ~21 daily + ~10 weekly buckets @@ -85,13 +85,13 @@ const CORE_CLUSTERS: ReadonlySet = new Set([ /** * Resolve the prefs, module gates and scope a build reads under. * - * v1.4.23 H4 — apply per-user `excludeMetrics` BEFORE we read any + * v1.4.23 — apply per-user `excludeMetrics` BEFORE we read any * measurement rows so the model never sees data the user opted out * of. The filter intersects with the resolved scope (the explicit * `scope` argument from the request body still wins for the * _maximum_ set; prefs only narrow further). * - * v1.4.25 W7b — the same prefs read also returns the user's + * v1.4.25 — the same prefs read also returns the user's * displayTimezone so the day-key and weekday labels below match the * calendar the user is looking at. Reading both columns in one * query keeps the snapshot's read budget the same as before. @@ -118,7 +118,7 @@ export async function resolveSnapshotPrelude( reach: CoachHistoryReach = UNBOUNDED_REACH, ) { const moduleMapPromise = resolveModuleMap(userId); - // v1.20.0 (H-1) — the F1 coach tools each rebuild a single-source snapshot + // v1.20.0 — the F1 coach tools each rebuild a single-source snapshot // with a distinct LRU key, so the 60s snapshot cache does not share this read // across the fan-out. Memoise it per-request (the select shape is constant, so // userId is the only key) so up to 6 concurrent tool builds collapse to one @@ -145,7 +145,7 @@ export async function resolveSnapshotPrelude( // The metric/imperial choice: the grounding bands and the blocks // that carry a mass, length or temperature are written in it. unitPreference: true, - // v1.18.6 (W7) — the explicit, user-declared diabetes opt-in. Selects + // v1.18.6 — the explicit, user-declared diabetes opt-in. Selects // the tighter ADA glycemic GOAL band for the glucose reference-grounding // line only; never inferred from a reading, never a diagnosis. Read on // this existing prefs hop (no extra round-trip). @@ -270,7 +270,7 @@ export function readSnapshotMeasurementRows( measuredAt: { gte: cutoff }, deletedAt: null, }, - // v1.18.10 (P-2) — read NEWEST-first + cap. PULSE / glucose are + // v1.18.10 — read NEWEST-first + cap. PULSE / glucose are // 200k-row-class types and the window can reach 365 days // (lastYear / allTime), so an uncapped read pulled the entire // year of high-frequency rows into memory just to fold them into diff --git a/src/lib/ai/coach/snapshot-series.ts b/src/lib/ai/coach/snapshot-series.ts index 7725c39b1..bd8a597e2 100644 --- a/src/lib/ai/coach/snapshot-series.ts +++ b/src/lib/ai/coach/snapshot-series.ts @@ -33,7 +33,7 @@ export function windowToDays(window: CoachScopeWindow): number { case "last90days": return 90; case "lastYear": - // v1.4.27 B7 / BL-P6-4 — year-in-review window. Same 365-day + // v1.4.27 — year-in-review window. Same 365-day // ceiling as the historical `allTime` cap so the prompt budget // stays bounded, but with explicit semantics — the Coach knows // the user is asking about a long-horizon view rather than the @@ -428,7 +428,7 @@ export function buildDailyBpRows( } /** - * v1.22 (W6) — the user's own usual range for one BP component, computed from + * v1.22 — the user's own usual range for one BP component, computed from * the rows already fetched for the snapshot (no extra DB read) using the SAME * median ± k·MAD statistic as the baseline engine. Returns null below the * engine's 7-day history floor so the Coach never quotes a fabricated band. diff --git a/src/lib/ai/coach/snapshot.ts b/src/lib/ai/coach/snapshot.ts index 2b2a4a01e..c9140715b 100644 --- a/src/lib/ai/coach/snapshot.ts +++ b/src/lib/ai/coach/snapshot.ts @@ -113,7 +113,7 @@ export { __resetCoachSnapshotCacheForTests } from "./snapshot-cache"; export interface CoachSnapshotResult { snapshotJson: string; /** - * v1.20.0 (F1) — the structured, post-degrade snapshot record `snapshotJson` + * v1.20.0 — the structured, post-degrade snapshot record `snapshotJson` * is serialised from. Keyed by domain block (`bloodPressure`, `weight`, * `pulse`, `mood`, `compliance`, `glucose`, `sleep`, `sleepRhythm`, `labs`, * `illness`, `derived`, `dayStrain`, `trajectory`, `weeklyContext`, …) plus a @@ -129,7 +129,7 @@ export interface CoachSnapshotResult { */ provenance: CoachProvenance; /** - * v1.18.6 (W7) — citation-aware reference-range grounding for the + * v1.18.6 — citation-aware reference-range grounding for the * metrics present in this snapshot, or null when none is covered by the * reference backbone. The Coach route appends it verbatim after the * SNAPSHOT so the model reads published population bands + the user's @@ -185,7 +185,7 @@ const COARSE_TAIL_START_DAYS = 90; * window so the Coach can still cite older weeks without ballooning * the prompt * - * v1.4.25 W7b — every day-key + weekday label is now anchored to the + * v1.4.25 — every day-key + weekday label is now anchored to the * user's display timezone (read from `User.timezone`). Falls back to * Europe/Berlin when the column is missing so the legacy snapshot * stays byte-identical for the only path the v1.4.24 suite tested. @@ -273,7 +273,7 @@ async function buildCoachSnapshotImpl( } = await resolveSnapshotPrelude(userId, scope, reach); const bounded = isBounded(reach); const glucoseUnit = units.glucoseUnit; - // v1.4.36 W3 T2 — `medications` and `anthropometrics` are + // v1.4.36 — `medications` and `anthropometrics` are // exclude-only toggles (not in `CoachScopeSource`); they gate the // GLP-1 weeklyContext / compliance branch and the anthropometrics // block respectively. @@ -298,7 +298,7 @@ async function buildCoachSnapshotImpl( // `userId` + the resolved window/sources), so running them // concurrently shaves a round-trip off the cold path. No block reads // `features` before the shared await, so the deferral is safe. - // v1.20.0 (H-1) — `extractFeatures` is the heaviest read on the cold path + // v1.20.0 — `extractFeatures` is the heaviest read on the cold path // (user findUnique + a windowed measurement findMany + the all-time extremes). // The F1 tools rebuild distinct-scope snapshots whose LRU keys differ, so they // do not share this read; memoise it per-request keyed on the only varying @@ -322,7 +322,7 @@ async function buildCoachSnapshotImpl( // budget for free-tier accounts. const snapshot: Record = {}; const windows = new Set(); - // v1.4.27 B7 / BL-P6-4 — seed the provenance window set with the + // v1.4.27 — seed the provenance window set with the // user's resolved scope so the year-in-review window surfaces in the // provenance envelope even when the per-metric branches below only // emit `last30days` / `last90days` chips. Older windows are added @@ -333,7 +333,7 @@ async function buildCoachSnapshotImpl( const metrics = new Set(); const counts: NonNullable = {}; - // v1.18.6 (W7) — representative scalar per reference-covered metric, in the + // v1.18.6 — representative scalar per reference-covered metric, in the // metric's reference unit, collected as the blocks below build. Feeds the // citation-aware grounding block (population band + the user's placement). // Each entry reads the SAME number the snapshot already surfaces — no @@ -525,7 +525,7 @@ async function buildCoachSnapshotImpl( supersededByRevisionId: true, }, }, - // v1.25 H-MED1 — pause eras so paused days drop out of the denominator. + // v1.25 — pause eras so paused days drop out of the denominator. pauseEras: { select: { pausedAt: true, resumedAt: true } }, // v1.40 (#1024) — the courses, so a gap between two expects nothing. courses: COURSES_COMPLIANCE_SELECT, @@ -578,7 +578,7 @@ async function buildCoachSnapshotImpl( totalDistanceM: true, avgHeartRate: true, maxHeartRate: true, - // v1.30.4 (C1) — needed by `pickCanonicalWorkoutRows` in the block + // v1.30.4 — needed by `pickCanonicalWorkoutRows` in the block // builder to collapse a dual-source session (e.g. Apple Watch + // WHOOP recording the same run) to one, matching `GET /api/workouts`. source: true, @@ -637,7 +637,7 @@ async function buildCoachSnapshotImpl( const trajectoryBlockPromise = derivedActive ? buildTrajectorySnapshotBlock(userId, derivedProfile, now, reach) : null; - // RECON1 (D5-5) — discovered cross-metric driver pairs for the no-tools + // Discovered cross-metric driver pairs for the no-tools // snapshot floor. Reuses the SAME gated/ranked output the get_correlations // tool serves (effect-size floor + family-tautology exclusion + shrinkage + // confidence tiering already applied inside the discovery engine), so a @@ -672,7 +672,7 @@ async function buildCoachSnapshotImpl( ? buildCycleSnapshotBlock(userId, prefsRow?.gender, now, userTz) : null; - // v1.18.1 P4 — illness/condition context. Always attempted (the helper is + // v1.18.1 — illness/condition context. Always attempted (the helper is // module-gated internally and short-circuits to null for a non-illness // account). It is CONTEXT, not a scope-gated metric: appended like // anthropometrics/scope below without a `registerBlock` so the budget @@ -950,7 +950,7 @@ async function buildCoachSnapshotImpl( registerBlock("cycle", "skin_temp"); } - // v1.18.1 P4 — illness/condition context. Small + load-bearing, so it is + // v1.18.1 — illness/condition context. Small + load-bearing, so it is // attached WITHOUT a cluster registration (like scope/anthropometrics): the // budget degrader never sheds it, the Coach always knows whether the user is // in Rest Mode. Labels + lifecycle + dates only — no decrypted note. @@ -1045,7 +1045,7 @@ async function buildCoachSnapshotImpl( ...(bounded ? { lookbackLimit: reach.window } : {}), }; - // v1.4.36 W3 T4 — compactSections drops any zero-row block before + // v1.4.36 — compactSections drops any zero-row block before // serialisation so the prompt never carries a labelled-empty key. // The snapshot is built conditionally above so most empty paths are // already skipped, but the helper catches future regressions and @@ -1105,7 +1105,7 @@ async function buildCoachSnapshotImpl( (key) => blockDaily.get(key)?.(), ); - // v1.18.6 (W7) — build the citation-aware reference-grounding block from the + // v1.18.6 — build the citation-aware reference-grounding block from the // representative scalars collected above. Deterministic + pure; the route // appends it verbatim after the SNAPSHOT. Null when no present metric is // covered by the reference backbone. Insertion order follows the block-build @@ -1131,7 +1131,7 @@ async function buildCoachSnapshotImpl( return { snapshotJson: JSON.stringify(compactSnapshot), - // v1.20.0 (F1) — the structured, post-degrade snapshot record keyed by + // v1.20.0 — the structured, post-degrade snapshot record keyed by // domain block (`bloodPressure`, `glucose`, `labs`, …). The coach tool // executor slices a single domain block out of this so a retrieval tool // returns exactly the numbers the legacy snapshot path would have shown — @@ -1290,7 +1290,7 @@ export function degradeToBudget( block && Array.isArray(block.facts) && block.facts.length > 0 ? block.facts : null; - // v1.21.3 (B1) — the durable plans survive the drop alongside facts: they + // v1.21.3 — the durable plans survive the drop alongside facts: they // are the user's confirmed if-then commitments, tiny by construction // (top-6, ≤160 chars each), and shedding them would make the Coach forget // a plan exactly on the data-heavy accounts that hit the char cap. @@ -1298,7 +1298,7 @@ export function degradeToBudget( block && Array.isArray(block.plans) && block.plans.length > 0 ? block.plans : null; - // v1.22 (B2/B3) — the episodic reminders survive the drop alongside + // v1.22 — the episodic reminders survive the drop alongside // facts/plans: they are the user's own "remember this" asks, tiny by // construction (top-6, ≤280 chars each), and shedding them would make the // Coach forget a reminder on exactly the data-heavy accounts that hit the diff --git a/src/lib/ai/coach/suggest-action.ts b/src/lib/ai/coach/suggest-action.ts index cc55d8f9d..963ed2d1d 100644 --- a/src/lib/ai/coach/suggest-action.ts +++ b/src/lib/ai/coach/suggest-action.ts @@ -1,5 +1,5 @@ /** - * v1.22 (F6) — generalised inline "confirm → apply" Coach action cards. + * v1.22 — generalised inline "confirm → apply" Coach action cards. * * The propose→confirm moat already ships for ONE action type — the cadence * reminder (`suggest-reminder.ts` CADENCE_CATALOG → `suggestion` SSE frame → diff --git a/src/lib/ai/coach/suggest-gate.ts b/src/lib/ai/coach/suggest-gate.ts index cc29765d4..2332f673a 100644 --- a/src/lib/ai/coach/suggest-gate.ts +++ b/src/lib/ai/coach/suggest-gate.ts @@ -1,5 +1,5 @@ /** - * v1.18.1 (Workstream C) — the non-naggy gate for Coach cadence + * v1.18.1 — the non-naggy gate for Coach cadence * suggestions. * * The model can PROPOSE a cadence (the `---SUGGEST-REMINDER---` sentinel); diff --git a/src/lib/ai/coach/suggest-reminder.ts b/src/lib/ai/coach/suggest-reminder.ts index 46b852e93..c61307ced 100644 --- a/src/lib/ai/coach/suggest-reminder.ts +++ b/src/lib/ai/coach/suggest-reminder.ts @@ -1,5 +1,5 @@ /** - * v1.18.1 (Workstream C) — Coach cadence suggestions. + * v1.18.1 — Coach cadence suggestions. * * The Coach can append a `---SUGGEST-REMINDER---` … `---END---` sentinel * block to its reply when an evidence-based measurement cadence is worth diff --git a/src/lib/ai/coach/system-prompt.ts b/src/lib/ai/coach/system-prompt.ts index cf6487ad4..737c2329c 100644 --- a/src/lib/ai/coach/system-prompt.ts +++ b/src/lib/ai/coach/system-prompt.ts @@ -1218,7 +1218,7 @@ export function buildCoachVoiceBlock(locale: Locale): string { } /** - * v1.4.25 W14c — native locale-specific Coach system prompts. + * v1.4.25 — native locale-specific Coach system prompts. * * Previously (W9e) the FR / ES / IT / PL locales rode the EN system * prompt with a one-line "REPLY LANGUAGE" footer. The 2025-12 Welo @@ -1249,7 +1249,7 @@ const LOCALE_REPLY_FOOTER_FALLBACK: Record< }; /** - * v1.22 (W6) — per-turn personalization passed by the chat route. All fields + * v1.22 — per-turn personalization passed by the chat route. All fields * are optional and the notes are empty when none apply. * * v1.43 (#1217) — these lines change every turn, so they no longer ride the @@ -1300,7 +1300,7 @@ export function getCoachSystemPrompt( } // v1.41 — the voice and answer shape, on every locale's body. base = `${base}\n\n${buildCoachVoiceBlock(locale)}`; - // v1.18.7 (HIGH-2) — append the shared cross-surface contracts (canonical + // v1.18.7 — append the shared cross-surface contracts (canonical // wording, single source of truth in `shared-contracts.ts`) for the // hand-composed de/en bodies. The native FR/ES/IT/PL bodies carry their own // per-locale safety wording from the contract matrix, so they are left as-is. @@ -1324,7 +1324,7 @@ export function getCoachSystemPrompt( // block is locale-independent (English topic labels + URLs) and the model // may only link a slug it lists, so a fabricated /learn URL is impossible. const withLearn = `${withPrefix}\n\n${learnCatalogPromptBlock()}`; - // v1.22 (W6) — the narrative-quality addendum: the context-layer rule, the + // v1.22 — the narrative-quality addendum: the context-layer rule, the // single-chart-token exception, the BP usual-range verbatim clause. Composed // after the shared contracts so the chart-token exception can carve out the // one place a `metric:` token is permitted. The per-turn opener hint @@ -1337,7 +1337,7 @@ export function getCoachSystemPrompt( } /** - * v1.22 (W6) — narrative-quality + personalization addendum for the Coach. + * v1.22 — narrative-quality + personalization addendum for the Coach. * * EN/DE are hand-composed; every other locale rides the EN body (these are * internal instructions, not user-facing prose). Each clause is additive and @@ -1500,7 +1500,7 @@ INTERPRETATION RULES (outside the self-report): } /** - * v1.4.23 H4 — convert non-default Coach prefs into a one-paragraph + * v1.4.23 — convert non-default Coach prefs into a one-paragraph * prefix the system prompt prepends. Defaults produce an empty prefix * so legacy users see the unchanged v1.4.22 prompt verbatim. * diff --git a/src/lib/ai/coach/tools/correlations-read.ts b/src/lib/ai/coach/tools/correlations-read.ts index 3c050a535..b9579386b 100644 --- a/src/lib/ai/coach/tools/correlations-read.ts +++ b/src/lib/ai/coach/tools/correlations-read.ts @@ -1,5 +1,5 @@ /** - * v1.21.0 (C3) — Coach correlations reader. + * v1.21.0 — Coach correlations reader. * * Surfaces the deterministic insight engine's cross-metric intelligence to the * Coach via the `get_correlations` tool. Two read-only sources, both already @@ -263,7 +263,7 @@ export async function readCoachCorrelations( ]); const { series, diagnostics } = matrix; - // QA F1 — surfaces when a dense account's window exceeded the read cap, + // Surfaces when a dense account's window exceeded the read cap, // mirroring the route's identical annotation. The cap now falls on the // OLDEST rows (desc + take), so a capped read still covers the recent // window the emerging-correlations pass below needs. diff --git a/src/lib/ai/coach/tools/definitions.ts b/src/lib/ai/coach/tools/definitions.ts index 42d913877..60abf8b7c 100644 --- a/src/lib/ai/coach/tools/definitions.ts +++ b/src/lib/ai/coach/tools/definitions.ts @@ -1,5 +1,5 @@ /** - * v1.20.0 (F1) — Coach retrieval tool catalogue. + * v1.20.0 — Coach retrieval tool catalogue. * * Replaces snapshot-stuffing with on-demand retrieval: the model is handed a * tiny DATA INVENTORY in the base context and pulls only the domains it needs diff --git a/src/lib/ai/coach/tools/executor.ts b/src/lib/ai/coach/tools/executor.ts index be5b71dfb..507d31b36 100644 --- a/src/lib/ai/coach/tools/executor.ts +++ b/src/lib/ai/coach/tools/executor.ts @@ -1,5 +1,5 @@ /** - * v1.20.0 (F1) — Coach retrieval tool executor. + * v1.20.0 — Coach retrieval tool executor. * * Dispatches a single tool call from the model: validates the raw JSON * arguments against the tool's Zod schema, runs the existing @@ -29,7 +29,7 @@ * The builder result is memoised by the 60s snapshot LRU keyed on * `(userId, window, sources)`. * - * v1.21.0 (D5-1) — ONE snapshot per turn. The route hands every tool the SAME + * v1.21.0 — ONE snapshot per turn. The route hands every tool the SAME * `sharedScope` it built the DATA INVENTORY against (the full source set + the * conversation window). A full-source build already contains every section, so * each tool reads under that shared scope key and slices its own section out — @@ -233,7 +233,7 @@ function limitWindow( /** * Resolve the scope a tool reads its snapshot under. * - * v1.21.0 (D5-1) — when the route handed us a `sharedScope` (the full-source + * v1.21.0 — when the route handed us a `sharedScope` (the full-source * inventory build for this turn) AND the tool's effective window matches the * shared window, read under the SHARED scope key so the per-tool read lands the * 60s LRU hit the inventory already primed (the section it needs is present in @@ -318,7 +318,7 @@ export async function executeCoachTool(args: { /** The conversation's effective window, used when a call omits `window`. */ fallbackWindow?: CoachScopeWindow; /** - * v1.21.0 (D5-1) — the turn's shared full-source snapshot scope (the same one + * v1.21.0 — the turn's shared full-source snapshot scope (the same one * the DATA INVENTORY was built against). When present and the tool's window * matches, the tool reads under this scope key so it lands the inventory's * already-built cache entry instead of rebuilding a single-source snapshot. @@ -785,13 +785,13 @@ async function getLabs( // Labs ride the snapshot regardless of `sources` (attached unconditionally), // so a minimal scope still surfaces them. // - // v1.21.0 (A5-F4) — the labs block itself is a "latest reading per biomarker + // v1.21.0 — the labs block itself is a "latest reading per biomarker // over the last 12 months" snapshot and is intentionally window-AGNOSTIC // (the read cutoff is fixed). We still thread the conversation's window onto // the scope so the snapshot's `scope` block reports the right horizon for the // turn; it does not move the labs read. // - // v1.21.0 (D5-1) — prefer the turn's shared full-source scope so this read + // v1.21.0 — prefer the turn's shared full-source scope so this read // lands the inventory's cache entry; fall back to a tight empty-source scope // (labs ride unconditionally) when no shared scope or a window mismatch. const snapshot = await buildCoachSnapshot( @@ -843,11 +843,11 @@ async function getIllnessRecovery( // sources; request them so the derived / dayStrain / trajectory blocks can // build when the module is on. Illness rides the snapshot unconditionally. // - // v1.21.0 (A5-F4) — honour the conversation's window so the recovery/strain/ + // v1.21.0 — honour the conversation's window so the recovery/strain/ // trajectory composites are computed over the horizon the caller asked for // rather than the builder default. // - // v1.21.0 (D5-1) — prefer the turn's shared full-source scope (which already + // v1.21.0 — prefer the turn's shared full-source scope (which already // carries hrv/resting_hr/vo2_max + illness + derived) so this read lands the // inventory's cache entry; the narrow scope is the window-mismatch fallback. const snapshot = await buildCoachSnapshot( @@ -959,7 +959,7 @@ async function getCycle( // so a minimal scope still surfaces it when the account tracks cycles, and a // non-cycle account structurally produces no block (→ present:false). // - // v1.21.0 (D5-1) — prefer the turn's shared full-source scope so this read + // v1.21.0 — prefer the turn's shared full-source scope so this read // lands the inventory's cache entry; the empty-source scope is the fallback // when no shared scope is threaded. const snapshot = await buildCoachSnapshot( diff --git a/src/lib/ai/coach/tools/index.ts b/src/lib/ai/coach/tools/index.ts index 244da31ef..100f2e8a3 100644 --- a/src/lib/ai/coach/tools/index.ts +++ b/src/lib/ai/coach/tools/index.ts @@ -1,5 +1,5 @@ /** - * v1.20.0 (F1) — Coach retrieval-tool barrel. + * v1.20.0 — Coach retrieval-tool barrel. * * The on-demand retrieval slice that replaces snapshot-stuffing: a tiny DATA * INVENTORY in the base context, six read-only tools the model calls to fetch diff --git a/src/lib/ai/coach/tools/inventory.ts b/src/lib/ai/coach/tools/inventory.ts index 27c0b9b33..d7c91ee7b 100644 --- a/src/lib/ai/coach/tools/inventory.ts +++ b/src/lib/ai/coach/tools/inventory.ts @@ -1,5 +1,5 @@ /** - * v1.20.0 (F1) — Coach DATA INVENTORY manifest. + * v1.20.0 — Coach DATA INVENTORY manifest. * * The tiny, always-sent manifest that replaces snapshot-stuffing in the base * context. It tells the model WHAT data exists (so it never invents a metric) @@ -97,7 +97,7 @@ export interface CoachDataInventory { /** The person's lookback limit, or undefined when there is none. */ lookbackLimit?: string; /** - * v1.21.0 (D5-1) — the exact full-source scope this inventory's snapshot was + * v1.21.0 — the exact full-source scope this inventory's snapshot was * built against. The route threads it to the tool loop so every per-tool read * lands the SAME 60s LRU entry (one snapshot build per turn, not N). Returned * here so the route reuses the identical scope object rather than @@ -107,7 +107,7 @@ export interface CoachDataInventory { } /** - * v1.21.0 (C2-2 / C2-3) — generate the get_metric_series inventory rows from + * v1.21.0 — generate the get_metric_series inventory rows from * the FULL source→key map rather than the eight legacy clusters. Every series * the user actually has rows for is advertised, so the model is no longer told * (rule 2) to skip body-composition / vascular / SpO2 / gait / environment. @@ -131,7 +131,7 @@ export async function buildCoachDataInventory( /** The person's lookback limit; the probe and every row answer to it. */ reach: CoachHistoryReach = UNBOUNDED_REACH, ): Promise { - // v1.21.0 (C2-2) — probe presence against the FULL source set, not the + // v1.21.0 — probe presence against the FULL source set, not the // user's narration-cluster default. `effectiveScope` only carried the // window; expanding `sources` here makes the snapshot build every domain the // user has rows for, so sleep / glucose / activity / body-composition / etc. @@ -192,7 +192,7 @@ export async function buildCoachDataInventory( ? { count: counts.compliance } : {}), }); - // v1.21.0 (C2-4) — workouts has its own tool now (get_metric_series refuses + // v1.21.0 — workouts has its own tool now (get_metric_series refuses // it). The block builds when the user has workout rows + the cluster is on. entries.push({ tool: "get_workouts", @@ -211,7 +211,7 @@ export async function buildCoachDataInventory( present: has("illness") || has("derived") || has("dayStrain") || has("trajectory"), }); - // v1.21.0 (C3) — discovered cross-metric correlations (FDR-controlled lagged + // v1.21.0 — discovered cross-metric correlations (FDR-controlled lagged // drivers) + the coincident-deviation flag. Advertised when the user has any // two discovery channels with data; the tool itself returns present:false // cleanly when nothing survives, so the inventory marks it present whenever a @@ -227,7 +227,7 @@ export async function buildCoachDataInventory( has("steps") || has("sleep"), }); - // v1.21.0 (C2-1) — cycle is fetchable via get_cycle when cycle tracking is + // v1.21.0 — cycle is fetchable via get_cycle when cycle tracking is // available for this account (per-user toggle AND operator switch). if (cycleEnabled) { entries.push({ @@ -382,7 +382,7 @@ async function attachAvailability( } /** - * v1.21.0 (D1) — render the launch FOCUS hint for the tool-mode user turn. + * v1.21.0 — render the launch FOCUS hint for the tool-mode user turn. * * When the Coach is opened from a metric page or card, the client sends * `scope.sources` (the metric the user is looking at). The no-tools path diff --git a/src/lib/ai/coach/tools/source-keys.ts b/src/lib/ai/coach/tools/source-keys.ts index 2e3b0a798..e3a6f3d70 100644 --- a/src/lib/ai/coach/tools/source-keys.ts +++ b/src/lib/ai/coach/tools/source-keys.ts @@ -1,5 +1,5 @@ /** - * v1.20.0 (F1) — CoachScopeSource → snapshot section key map. + * v1.20.0 — CoachScopeSource → snapshot section key map. * * `get_metric_series` fetches a single metric by re-running the snapshot * builder scoped to that one source, then reads the matching section out of the @@ -76,7 +76,7 @@ export const COACH_SOURCE_SNAPSHOT_KEY: Readonly< }; /** - * v1.21.0 (C2-3) — human-readable domain label per metric source, for the DATA + * v1.21.0 — human-readable domain label per metric source, for the DATA * INVENTORY lines. Brand-free, lower-case, stable across turns so the cached * prefix holds. Every key in `COACH_SOURCE_SNAPSHOT_KEY` plus the dedicated-tool * sources has an entry; the inventory renders one line per series source the @@ -125,7 +125,7 @@ export const COACH_SOURCE_DOMAIN_LABEL: Readonly< }; /** - * v1.21.0 (C2-2 / C2-3) — the FULL set of metric-series sources the inventory + * v1.21.0 — the FULL set of metric-series sources the inventory * probes for presence: every source `get_metric_series` can serve (the * `COACH_SOURCE_SNAPSHOT_KEY` keys), minus the few that ride a dedicated tool * (sleep has `get_sleep`; glucose/compliance/workouts are excluded). The @@ -140,7 +140,7 @@ export const METRIC_SERIES_INVENTORY_SOURCES: ReadonlyArray = ); /** - * v1.21.0 (C2-2) — the full source set the inventory snapshot is built against, + * v1.21.0 — the full source set the inventory snapshot is built against, * so every domain with stored rows reports `present` (not the default-cluster * subset). The per-tool reads still re-scope to the exact domain, so widening * the inventory probe never widens a figure read. This is the canonical enum diff --git a/src/lib/ai/coach/tools/system-addendum.ts b/src/lib/ai/coach/tools/system-addendum.ts index db8e7cb88..7b2d4805b 100644 --- a/src/lib/ai/coach/tools/system-addendum.ts +++ b/src/lib/ai/coach/tools/system-addendum.ts @@ -1,5 +1,5 @@ /** - * v1.20.0 (F1) — tool-mode system-prompt addendum. + * v1.20.0 — tool-mode system-prompt addendum. * * Appended to the stable Coach system prompt ONLY when retrieval tools are * offered this turn (a tool-capable provider). It reframes the grounding diff --git a/src/lib/ai/coach/turn/cards.ts b/src/lib/ai/coach/turn/cards.ts index 3c5a4b54d..28cb7d364 100644 --- a/src/lib/ai/coach/turn/cards.ts +++ b/src/lib/ai/coach/turn/cards.ts @@ -73,7 +73,7 @@ export async function surfaceCards(args: { }); } } - // v1.22 (F6) — surface the confirm-card action when the turn was not blocked. + // v1.22 — surface the confirm-card action when the turn was not blocked. // Additive: the prose already stands alone; the card only offers the one-tap // confirm. Nothing is created server-side until the user taps it. let surfacedAction: CoachSuggestedAction | null = null; diff --git a/src/lib/ai/coach/turn/chain.ts b/src/lib/ai/coach/turn/chain.ts index e2bb7741b..87eba7ab3 100644 --- a/src/lib/ai/coach/turn/chain.ts +++ b/src/lib/ai/coach/turn/chain.ts @@ -16,7 +16,7 @@ export async function resolveTurnChain( | { ok: true; chain: TurnChain; toolMode: boolean } | { ok: false; response: Response } > { - // v1.20.0 (F1) — provider capabilities select tool retrieval or the legacy + // v1.20.0 — provider capabilities select tool retrieval or the legacy // snapshot-stuffing path. const chain = await resolveProviderChain(userId); if (chain.length === 0) { @@ -43,7 +43,7 @@ export async function resolveTurnChain( // ConsentRequiredError → apiHandler returns 403 + `consent.ai.required`. await assertConsentForChain({ userId, chain, surface: "coach" }); - // v1.20.0 (F1) — tool mode is on only when EVERY provider in the chain + // v1.20.0 — tool mode is on only when EVERY provider in the chain // supports tool-calling, so whichever hop the fallback runner lands on can // still serve tools. A chain that includes a no-tools provider (local / // Ollama) falls back to the legacy snapshot-stuffing path verbatim, exactly diff --git a/src/lib/ai/coach/turn/context.ts b/src/lib/ai/coach/turn/context.ts index d6dd8584e..048edc367 100644 --- a/src/lib/ai/coach/turn/context.ts +++ b/src/lib/ai/coach/turn/context.ts @@ -96,7 +96,7 @@ export async function assembleTurnContext(args: { // system-prompt prefix; the snapshot builder reads the same prefs // separately so excluded metrics never even leave the DB. // - // v1.4.25 W5 — `coachPrefs.defaultWindow` is the user's saved + // v1.4.25 — `coachPrefs.defaultWindow` is the user's saved // analysis-window preference, merged into the snapshot scope when the // client didn't supply a per-conversation window. const prefsRow = await prisma.user.findUnique({ @@ -141,12 +141,12 @@ export async function assembleTurnContext(args: { // keeps the Guard I phrase-anchored dose behaviour. const scheduleDoses = await getScheduledDoseValues(userId).catch(() => []); const medicationNames = await getMedicationNames(userId).catch(() => []); - // v1.22 (B2/F6) — the canonical system-prompt module is owned elsewhere, so + // v1.22 — the canonical system-prompt module is owned elsewhere, so // the two memory/action clauses are appended at assembly time here: one // teaches the model to emit `---REMEMBER---` (durable "remind me" capture), // one teaches the closed `---SUGGEST-ACTION---` confirm-card allowlist. Both // are provider-neutral sentinels stripped from the prose before it streams. - // v1.22 (W6) — per-turn personalization: a sparse, hash-gated first name and + // v1.22 — per-turn personalization: a sparse, hash-gated first name and // an opener-archetype hint so multi-turn sessions vary. The turn index is the // count of prior turns, so the name surfaces on ~1-in-3 turns, varied and // never on a fixed cadence; both omit cleanly when no display name is set. diff --git a/src/lib/ai/coach/turn/conversation.ts b/src/lib/ai/coach/turn/conversation.ts index e6bbbd078..820fe3b26 100644 --- a/src/lib/ai/coach/turn/conversation.ts +++ b/src/lib/ai/coach/turn/conversation.ts @@ -95,7 +95,7 @@ export async function resolveTurnConversation(args: { m.metricSource?.groundedFigures ? [...m.metricSource.groundedFigures] : [], ); - // v1.4.43 W13 M-3 — replay-injection guard. `detectRefusal` runs + // v1.4.43 — replay-injection guard. `detectRefusal` runs // only on the inbound `message` per turn, so an injection that // slipped past the regex bank on a previous turn would re-enter // the prompt every reply. Re-run the detector against every diff --git a/src/lib/ai/coach/turn/model.ts b/src/lib/ai/coach/turn/model.ts index b39c3ffb1..5f0b8d6eb 100644 --- a/src/lib/ai/coach/turn/model.ts +++ b/src/lib/ai/coach/turn/model.ts @@ -101,12 +101,12 @@ export type ModelOutcome = workingProviderType: ProviderChainType; toolTrace: CoachToolTrace[]; /** - * v1.21.0 (P6) — the present tool-result payloads this turn, for the + * v1.21.0 — the present tool-result payloads this turn, for the * post-hoc prose number-verifier. Empty on the no-tools path. */ toolResultPayloads: unknown[]; /** - * v1.21.2 (A8) — no-tools/local-provider parity for the prose + * v1.21.2 — no-tools/local-provider parity for the prose * number-verifier. The tool path grades prose against the figures the * tools returned; the no-tools path has no tools, so the authoritative * set is the SNAPSHOT the model was actually shown this turn — @@ -128,7 +128,7 @@ export type ModelOutcome = inventoryPayloads: unknown[]; totalTokens: number; /** - * v1.21.0 (F3) — cached-input tokens to subtract at reconcile + * v1.21.0 — cached-input tokens to subtract at reconcile * (prompt-cached input the user did not re-pay for must not be billed * to the daily meter). */ @@ -355,7 +355,7 @@ export async function runTurnModel(args: { } if (toolMode) { - // v1.20.0 (F1) — base context: the full system prompt + a tool-mode + // v1.20.0 — base context: the full system prompt + a tool-mode // grounding addendum, with the tiny DATA INVENTORY manifest and the // transcript on the user turn. The figures are NOT in the prompt — the // model pulls only what it needs via the retrieval tools. @@ -433,7 +433,7 @@ export async function runTurnModel(args: { temperature: AI_BUDGETS.coach.temperature, maxTokens: AI_BUDGETS.coach.maxTokens, fallbackWindow: effectiveScope?.window, - // v1.21.0 (D5-1) — share the inventory's full-source snapshot across + // v1.21.0 — share the inventory's full-source snapshot across // every tool so the turn builds ONE snapshot, not one per tool. sharedScope: inventory.probeScope, // The lookback limit: every tool call is clamped to it. @@ -704,7 +704,7 @@ export async function runTurnModel(args: { workingProviderType: fallback.workingProvider.providerType, toolTrace: [], toolResultPayloads: [], - // v1.21.2 (A8) — the no-tools path grades only when it delivered the + // v1.21.2 — the no-tools path grades only when it delivered the // full SNAPSHOT. noToolsSnapshotPayloads: turnContext.includeFullSnapshot ? [ @@ -769,7 +769,7 @@ export async function runTurnModel(args: { credentialExpired: err.primaryCredentialExpired, }, }); - // v1.11.0 W1 — when the user's PRIMARY provider failed with an + // v1.11.0 — when the user's PRIMARY provider failed with an // auth-class status (401/403), the credential is dead, not the // service. Surface a distinct `credential_expired` frame so the // drawer can deep-link the user to reconnect rather than telling @@ -778,7 +778,7 @@ export async function runTurnModel(args: { if (err.primaryCredentialExpired) { return { ok: false, code: "coach.provider.credential_expired" }; } - // v1.4.25 W5 — distinguish provider rate-limit (every attempt + // v1.4.25 — distinguish provider rate-limit (every attempt // landed on 429) from generic unavailability. The drawer's // error-decoder surfaces the rate-limit copy with a warning // toast instead of the generic provider-down message, so the diff --git a/src/lib/ai/coach/turn/persist.ts b/src/lib/ai/coach/turn/persist.ts index e4bee0c56..96dc54aa0 100644 --- a/src/lib/ai/coach/turn/persist.ts +++ b/src/lib/ai/coach/turn/persist.ts @@ -42,7 +42,7 @@ export async function persistAssistantReply(args: { // v1.18.9 — persist the per-turn token count + model so the quiet // token footer survives a conversation reload. The live turn paints // from the `done.usage` SSE frame; reloads read these columns. - // v1.20.0 (F1) — the summed cost across every tool round, so the footer + // v1.20.0 — the summed cost across every tool round, so the footer // reflects the true turn cost on the tool path too. tokensUsed: totalTokensSpent || null, model: model.result.model ?? null, @@ -61,18 +61,18 @@ export async function persistAssistantReply(args: { action: { name: "insights.coach.replied" }, meta: { provider: workingProviderType, - // v1.20.0 (F1) — summed tokens across every tool round (the loop) or the + // v1.20.0 — summed tokens across every tool round (the loop) or the // single call (no-tools path), so the dashboards see the true turn cost. tokens: totalTokensSpent, promptVersion: PROMPT_VERSION, conversationId, historyTurns: ctx.turnContext.window.length, - // v1.20.0 (F1) — whether this turn ran the tool-retrieval path, and how + // v1.20.0 — whether this turn ran the tool-retrieval path, and how // many tools it fetched, so the dashboards can correlate the token delta // with the new path vs the legacy snapshot path. toolMode, toolsCalled: model.toolTrace.length, - // v1.19.1 (C4) — whether the full SNAPSHOT block rode this turn (the + // v1.19.1 — whether the full SNAPSHOT block rode this turn (the // expensive prefix) vs the cheap pointer. On the tool path the snapshot // never rides the prompt, so this is the legacy-path signal only. snapshotSent: !toolMode && ctx.turnContext.includeFullSnapshot, diff --git a/src/lib/ai/coach/turn/provenance.ts b/src/lib/ai/coach/turn/provenance.ts index 247b46600..271a29cfe 100644 --- a/src/lib/ai/coach/turn/provenance.ts +++ b/src/lib/ai/coach/turn/provenance.ts @@ -204,13 +204,13 @@ export function buildTurnProvenance(args: { const enrichedProvenance: CoachProvenance = { ...snapshotProvenance, ...(surfacedAction ? { suggestedAction: surfacedAction } : {}), - // v1.18.10 (HIGH-2) — a blocked turn carries the fallback prose, so the + // v1.18.10 — a blocked turn carries the fallback prose, so the // key-values from the discarded reply must not ride along as provenance. ...(!outboundBlocked && sentinel.keyValues.length > 0 ? { keyValues: sentinel.keyValues } : {}), ...(surfacedSuggestion ? { suggestion: surfacedSuggestion } : {}), - // v1.20.0 (F1) — persist the retrieval-tool trace (which tools ran + + // v1.20.0 — persist the retrieval-tool trace (which tools ran + // whether each found data) so a reload can show "what I looked at" and the // audit can replay grounding. Metadata only. ...(toolTrace.length > 0 diff --git a/src/lib/ai/coach/turn/reply-guards.ts b/src/lib/ai/coach/turn/reply-guards.ts index 4fd5d0a83..1fb4a2dc4 100644 --- a/src/lib/ai/coach/turn/reply-guards.ts +++ b/src/lib/ai/coach/turn/reply-guards.ts @@ -102,7 +102,7 @@ export async function guardReply(args: { // "Worauf bezieht sich das?" disclosure. const sentinel = parseKeyValuesSentinel(rawReply); const proseAfterStrip = sentinel.prose.trim(); - // v1.4.22 W5 reconcile (Code-H1) — when the model emits a + // v1.4.22 — when the model emits a // sentinel-only / malformed reply, `sentinel.prose` is empty after // stripping. The previous fallback `sentinel.prose.trim() || rawReply` // surfaced raw `---KEYVALUES---` markers to the user. The empty-prose @@ -120,7 +120,7 @@ export async function guardReply(args: { }); return { ok: false, code: "coach.provider.empty" }; } - // v1.18.1 (Workstream C) — strip the optional `---SUGGEST-REMINDER---` + // v1.18.1 — strip the optional `---SUGGEST-REMINDER---` // block out of the prose-after-keyvalues. The model proposes a cadence; // the gate decides whether it actually surfaces (module-toggle + opt-out // + dismissal memory + cooldown + dedup against a live COACH reminder). @@ -128,7 +128,7 @@ export async function guardReply(args: { const suggestParse = parseSuggestReminder(proseAfterStrip); let replyText = suggestParse.prose.trim() || proseAfterStrip; - // v1.22 (B2) — strip the optional `---REMEMBER---` block and capture the + // v1.22 — strip the optional `---REMEMBER---` block and capture the // reminder INLINE on this turn (not in the >20-turn memory worker), so a // casual "remind me about X" in a SHORT chat is no longer lost. A missing // note / invalid `when` drops the block (the user never sees the raw @@ -156,7 +156,7 @@ export async function guardReply(args: { }); } - // v1.22 (F6) — strip the optional `---SUGGEST-ACTION---` block (the + // v1.22 — strip the optional `---SUGGEST-ACTION---` block (the // generalised confirm→apply moat). The model names ONE action from the closed // allowlist (`checkup.create` / `reminder.note`); the card surfaces additively // and NOTHING is created until the user taps confirm (the entity is built @@ -194,7 +194,7 @@ export async function guardReply(args: { const resultRefs = stripResultRefs(replyText); replyText = resultRefs.prose.trim() || replyText; - // v1.18.10 (HIGH-2) — OUTBOUND safety screen on the assembled assistant + // v1.18.10 — OUTBOUND safety screen on the assembled assistant // reply, before persistence and streaming. The inbound `detectRefusal` // guards the user's message; this guards the model's reply for a // dose-prescription or a fabricated clinical risk score that slipped past @@ -222,7 +222,7 @@ export async function guardReply(args: { }); } - // v1.21.0 (P6 / C2-5) — post-hoc numeric verifier on the Coach prose. Cross- + // v1.21.0 — post-hoc numeric verifier on the Coach prose. Cross- // check every number the model cited against this turn's authoritative figure // set; an unmatched number (transcription / paraphrase drift) is soft-stripped // to the editorial elision mark `[…]` and annotated (v1.32.14 — the withheld @@ -232,7 +232,7 @@ export async function guardReply(args: { // backstop, exactly like the briefing's "no signals → skip". A blocked turn // already carries canned fallback prose, so skip it. // - // v1.21.2 (A8) — the authoritative set is the figures the tools returned on the + // v1.21.2 — the authoritative set is the figures the tools returned on the // tool path, and the SNAPSHOT the model was shown on the no-tools/local path // (`snapshot.sections`, which already carries the correlations-snapshot block). // Exactly one is populated per turn; the grading, tolerance, and exemptions are diff --git a/src/lib/ai/coach/turn/sse.ts b/src/lib/ai/coach/turn/sse.ts index 25dfb659a..15225d2fb 100644 --- a/src/lib/ai/coach/turn/sse.ts +++ b/src/lib/ai/coach/turn/sse.ts @@ -111,7 +111,7 @@ export async function emitReply( } for (const tok of tokeniseForStreaming(outcome.replyText)) { - // v1.18.10 (A-2) — stop tokenising the moment the client disconnects. + // v1.18.10 — stop tokenising the moment the client disconnects. if (emitter.aborted()) return; emitter.emit({ type: "token", token: tok }); await flushTick(); @@ -123,11 +123,11 @@ export async function emitReply( for (const result of outcome.results) { emitter.emit({ type: "result", result }); } - // v1.18.1 (Workstream C) — additive `suggestion` frame. + // v1.18.1 — additive `suggestion` frame. if (outcome.suggestion) { emitter.emit({ type: "suggestion", suggestion: outcome.suggestion }); } - // v1.22 (F6) — additive `suggestedAction` frame. + // v1.22 — additive `suggestedAction` frame. if (outcome.action) { emitter.emit({ type: "suggestedAction", diff --git a/src/lib/ai/coach/types.ts b/src/lib/ai/coach/types.ts index b1fba5af3..3c4f564b5 100644 --- a/src/lib/ai/coach/types.ts +++ b/src/lib/ai/coach/types.ts @@ -47,7 +47,7 @@ export const coachScopeWindowSchema = z.enum([ "last7days", "last30days", "last90days", - // v1.4.27 B7 / BL-P6-4 — long-horizon window for the year-in-review + // v1.4.27 — long-horizon window for the year-in-review // surfaces. Sits between `last90days` and `allTime` so the snapshot // builder can sample a denser timeline (one row per week) than the // unbounded fallback while still surfacing seasonal patterns. @@ -162,7 +162,7 @@ export const coachChatRequestSchema = z.object({ }); /** - * v1.18.1 (Workstream C) — a Coach cadence suggestion surfaced as a + * v1.18.1 — a Coach cadence suggestion surfaced as a * one-tap action card. The model proposes the cadence via a sentinel * block; the server resolves it against the closed cadence catalog and * gates it (module-toggle + opt-out + dismissal + cooldown + dedup). When @@ -823,20 +823,20 @@ export interface CoachProvenance { */ keyValues?: ReadonlyArray; /** - * v1.18.1 (Workstream C) — a cadence suggestion attached to this turn. + * v1.18.1 — a cadence suggestion attached to this turn. * Persisted alongside the message so the one-tap action card re-renders * on a conversation reload. Absent on turns that carry no suggestion. */ suggestion?: CoachSuggestion; /** - * v1.22 (F6) — a generalised confirm→apply action card attached to this turn + * v1.22 — a generalised confirm→apply action card attached to this turn * (`checkup.create` / `reminder.note`, closed allowlist). Persisted alongside * the message so the card re-renders on a conversation reload. Absent on turns * that carry no action. */ suggestedAction?: CoachSuggestedAction; /** - * v1.20.0 (F1) — the retrieval-tool trace for this turn: which tools the + * v1.20.0 — the retrieval-tool trace for this turn: which tools the * Coach called and whether each found data (`present`). Metadata only — no * raw values beyond what `keyValues` already persists. Lets a conversation * reload show "what I looked at" and lets the hallucination audit replay diff --git a/src/lib/ai/codex-slug-cache.ts b/src/lib/ai/codex-slug-cache.ts index 9b1ef3e16..86b0b3a30 100644 --- a/src/lib/ai/codex-slug-cache.ts +++ b/src/lib/ai/codex-slug-cache.ts @@ -1,7 +1,7 @@ /** * Process-local positive cache for working Codex model slugs. * - * Phase C1 (v1.4.15) — slug-drift defence per + * (v1.4.15) — slug-drift defence per * `docs/codex-protocol-spec.md` §7b. The ChatGPT-Codex backend rotates * its allow-list silently; pinning a single `CODEX_MODEL` slug bricks * the integration the moment upstream flips it. Instead of pinning, diff --git a/src/lib/ai/feedback-attribution.ts b/src/lib/ai/feedback-attribution.ts index a8e2e7d30..14135331a 100644 --- a/src/lib/ai/feedback-attribution.ts +++ b/src/lib/ai/feedback-attribution.ts @@ -1,5 +1,5 @@ /** - * v1.4.16 phase B5e — server-side resolver for the provider/prompt + * v1.4.16 — server-side resolver for the provider/prompt * attribution that gets stamped on every `RecommendationFeedback` row. * * The client cannot supply these fields — that would let any user @@ -83,7 +83,7 @@ export async function resolveFeedbackAttribution( } /** - * v1.4.23 H7 — attribution shape for a Coach assistant-message + * v1.4.23 — attribution shape for a Coach assistant-message * thumbs row. The polymorphic `RecommendationFeedback` table reuses * its existing slots for Coach rows: * - `recommendationId` carries the Coach message id diff --git a/src/lib/ai/insight-interpretation.ts b/src/lib/ai/insight-interpretation.ts index 1dbf420b0..86d028b39 100644 --- a/src/lib/ai/insight-interpretation.ts +++ b/src/lib/ai/insight-interpretation.ts @@ -1,5 +1,5 @@ /** - * v1.27.13 (Welle J) — the interpretation-context registry. + * v1.27.13 — the interpretation-context registry. * * The per-metric assessment prompt used to hand the model a coarse * single-edge `normalRange` and the personal baseline, and nothing that says diff --git a/src/lib/ai/legacy-payload.ts b/src/lib/ai/legacy-payload.ts index 1584f39c8..6ea77c7c9 100644 --- a/src/lib/ai/legacy-payload.ts +++ b/src/lib/ai/legacy-payload.ts @@ -1,5 +1,5 @@ /** - * v1.4.16 phase B5c — legacy-payload detection. + * v1.4.16 — legacy-payload detection. * * Cached insight blobs persisted before B5c shipped don't carry the * `rationale` field. The /api/insights/generate route surfaces a diff --git a/src/lib/ai/medical-references.ts b/src/lib/ai/medical-references.ts index ac160746f..0b663c107 100644 --- a/src/lib/ai/medical-references.ts +++ b/src/lib/ai/medical-references.ts @@ -1,5 +1,5 @@ /** - * v1.4.16 phase B5a — curated medical-reference bundle. + * v1.4.16 — curated medical-reference bundle. * * Every normative claim in an AI-generated recommendation ("target * < 140/90", "BMI 18.5-24.9", "≥ 7h sleep") MUST cite an entry in diff --git a/src/lib/ai/prompts/__tests__/assessment-prompt-contract.test.ts b/src/lib/ai/prompts/__tests__/assessment-prompt-contract.test.ts index d809f9e62..77287284c 100644 --- a/src/lib/ai/prompts/__tests__/assessment-prompt-contract.test.ts +++ b/src/lib/ai/prompts/__tests__/assessment-prompt-contract.test.ts @@ -84,7 +84,7 @@ describe("base-system assessment prompt — V2: few-shot examples present", () = it("English: carries a GOOD grounded example and a BAD banned-filler counter-example", () => { const p = getBaseSystemPrompt("en"); expect(p).toMatch(/EXAMPLES/); - // v1.22 (W6) — the examples now illustrate DIFFERENT shapes (verdict-led, + // v1.22 — the examples now illustrate DIFFERENT shapes (verdict-led, // trend-led, one-liner) instead of a single "GOOD" shape. expect(p).toMatch(/VERDICT-LED/); expect(p).toMatch(/ONE-LINER/); diff --git a/src/lib/ai/prompts/__tests__/briefing-personalisation.test.ts b/src/lib/ai/prompts/__tests__/briefing-personalisation.test.ts index 49734a63b..888c58f6b 100644 --- a/src/lib/ai/prompts/__tests__/briefing-personalisation.test.ts +++ b/src/lib/ai/prompts/__tests__/briefing-personalisation.test.ts @@ -1,5 +1,5 @@ /** - * v1.22 (W6) — the daily-briefing personalization block: always an opener hint, + * v1.22 — the daily-briefing personalization block: always an opener hint, * a sparse + hash-gated first-name clause, and byte-identical (name-free) output * for unnamed accounts. */ diff --git a/src/lib/ai/prompts/__tests__/coach-prompt-locale.test.ts b/src/lib/ai/prompts/__tests__/coach-prompt-locale.test.ts index d9dc06f56..40e8af057 100644 --- a/src/lib/ai/prompts/__tests__/coach-prompt-locale.test.ts +++ b/src/lib/ai/prompts/__tests__/coach-prompt-locale.test.ts @@ -18,7 +18,7 @@ import { loadSafetyContracts } from "@/lib/ai/prompts/safety-contracts"; const NATIVE_LOCALES = ["fr", "es", "it", "pl"] as const; /** - * v1.4.25 W14c — each AI-initial locale (FR / ES / IT / PL) now gets a + * v1.4.25 — each AI-initial locale (FR / ES / IT / PL) now gets a * native system prompt assembled from the safety-contract matrix * instead of riding the EN body with a one-line footer. * diff --git a/src/lib/ai/prompts/__tests__/drug-level-refusal.probe.test.ts b/src/lib/ai/prompts/__tests__/drug-level-refusal.probe.test.ts index 1f6cd0f8c..5bc350d66 100644 --- a/src/lib/ai/prompts/__tests__/drug-level-refusal.probe.test.ts +++ b/src/lib/ai/prompts/__tests__/drug-level-refusal.probe.test.ts @@ -9,7 +9,7 @@ import { getCoachSystemPrompt } from "@/lib/ai/coach/system-prompt"; import type { Locale } from "@/lib/i18n/config"; /** - * v1.4.25 W19c-Safety — adversarial drug-level refusal probes. + * v1.4.25 — adversarial drug-level refusal probes. * * Sister to `refusal-probe.test.ts` (W14c). That file drives the * 14-rule × 6-locale × 20+-paraphrasing matrix; this file zooms in on diff --git a/src/lib/ai/prompts/__tests__/interpretation-block.test.ts b/src/lib/ai/prompts/__tests__/interpretation-block.test.ts index 010100fb2..b92f974d5 100644 --- a/src/lib/ai/prompts/__tests__/interpretation-block.test.ts +++ b/src/lib/ai/prompts/__tests__/interpretation-block.test.ts @@ -1,5 +1,5 @@ /** - * v1.27.13 (Welle J) — the interpretation block renders the guideline band + * v1.27.13 — the interpretation block renders the guideline band * table + server-computed position into the assessment prompt. These guards * assert it renders for a covered metric (with the value's band named) and is * ABSENT for an uncovered one or a sex-required metric with no known sex — diff --git a/src/lib/ai/prompts/__tests__/opener-archetype.test.ts b/src/lib/ai/prompts/__tests__/opener-archetype.test.ts index 4671283f1..fce537dd1 100644 --- a/src/lib/ai/prompts/__tests__/opener-archetype.test.ts +++ b/src/lib/ai/prompts/__tests__/opener-archetype.test.ts @@ -1,5 +1,5 @@ /** - * v1.22 (W6) — opener-archetype rotation + hash helpers. + * v1.22 — opener-archetype rotation + hash helpers. * * The whole point is variety WITHOUT randomness: the choice must be stable for a * key (reproducible, testable) yet differ across keys (anti-sameness). These diff --git a/src/lib/ai/prompts/__tests__/prompt-comparison.test.ts b/src/lib/ai/prompts/__tests__/prompt-comparison.test.ts index da7f4086c..0ae249c75 100644 --- a/src/lib/ai/prompts/__tests__/prompt-comparison.test.ts +++ b/src/lib/ai/prompts/__tests__/prompt-comparison.test.ts @@ -6,7 +6,7 @@ import { } from "@/lib/ai/prompts/insight-system-prompt"; /** - * v1.4.16 phase B8 — comparison context block in the user prompt. + * v1.4.16 — comparison context block in the user prompt. * * When the dashboard's comparison toggle is active, the route layer * passes a `ComparisonSnapshot` to `buildUserPrompt()` so the LLM has diff --git a/src/lib/ai/prompts/__tests__/refusal-probe.test.ts b/src/lib/ai/prompts/__tests__/refusal-probe.test.ts index b908f85d3..9548096f8 100644 --- a/src/lib/ai/prompts/__tests__/refusal-probe.test.ts +++ b/src/lib/ai/prompts/__tests__/refusal-probe.test.ts @@ -10,7 +10,7 @@ import { import type { Locale } from "@/lib/i18n/config"; /** - * v1.4.25 W14c — cross-locale refusal-probe matrix. + * v1.4.25 — cross-locale refusal-probe matrix. * * The maintainer cannot review the FR/ES/IT/PL safety-critical translations * himself. Automated coverage is the only safety net, so this test diff --git a/src/lib/ai/prompts/__tests__/safety-contracts-parity.test.ts b/src/lib/ai/prompts/__tests__/safety-contracts-parity.test.ts index c9a966e84..3d7f19ff0 100644 --- a/src/lib/ai/prompts/__tests__/safety-contracts-parity.test.ts +++ b/src/lib/ai/prompts/__tests__/safety-contracts-parity.test.ts @@ -7,7 +7,7 @@ import { } from "../safety-contracts"; /** - * v1.4.25 W14c — safety-contract matrix parity guards. + * v1.4.25 — safety-contract matrix parity guards. * * Complements the W9d `i18n-locale-integrity.test.ts` for the YAML * matrix: diff --git a/src/lib/ai/prompts/__tests__/shared-contracts-coverage.test.ts b/src/lib/ai/prompts/__tests__/shared-contracts-coverage.test.ts index 7c60c41d1..f44ae8b7e 100644 --- a/src/lib/ai/prompts/__tests__/shared-contracts-coverage.test.ts +++ b/src/lib/ai/prompts/__tests__/shared-contracts-coverage.test.ts @@ -1,5 +1,5 @@ /** - * v1.18.7 (HIGH-2) — cross-surface shared-contract coverage. + * v1.18.7 — cross-surface shared-contract coverage. * * The grounding / tone / GLP-1-safety / metric-identifier-ban / forbidden- * filler contracts live once in `shared-contracts.ts`. These guards assert @@ -56,7 +56,7 @@ const SURFACES: Record< contracts: [ grounding, toneContract, - // v1.27.13 (Welle J) — anti-recitation + interpretation-depth reach the + // v1.27.13 — anti-recitation + interpretation-depth reach the // overview + briefing text too. antiRecitation, interpretationDepth, @@ -70,7 +70,7 @@ const SURFACES: Record< // v1.21.0 (QoL-B §3) — the briefing composes the forward-looking outlook // contract beside the tone contract. outlookContract, - // v1.22 (W6) — the paragraph formatting contract. + // v1.22 — the paragraph formatting contract. formattingContract, ], }, @@ -79,7 +79,7 @@ const SURFACES: Record< contracts: [ grounding, toneContract, - // v1.27.13 (Welle J) — the assessment cards enforce both new contracts. + // v1.27.13 — the assessment cards enforce both new contracts. antiRecitation, interpretationDepth, // v1.28.40 — the opening-shape contract lands here so the per-metric card @@ -89,7 +89,7 @@ const SURFACES: Record< safetyAcute, metricIdentifierBan, forbiddenFiller, - // v1.22 (W6) — the paragraph formatting contract. + // v1.22 — the paragraph formatting contract. formattingContract, ], }, @@ -104,7 +104,7 @@ const SURFACES: Record< forbiddenFiller, // v1.21.0 (QoL-B §3) — the Coach composes the outlook contract too. outlookContract, - // v1.22 (W6) — the paragraph formatting contract. + // v1.22 — the paragraph formatting contract. formattingContract, ], }, @@ -123,7 +123,7 @@ const SURFACES: Record< safetyAcute, metricIdentifierBan, forbiddenFiller, - // v1.22 (W6) — the paragraph formatting contract. + // v1.22 — the paragraph formatting contract. formattingContract, ], }, @@ -191,7 +191,7 @@ describe("shared-contract cross-surface coverage", () => { }); }); -// v1.21.0 (D3-M1 / D3-L1) — the acute-safety clause's threshold numbers are +// v1.21.0 — the acute-safety clause's threshold numbers are // composed from `clinical-floors.ts`, so the Coach's stated crisis thresholds // can never drift from the dashboard hero / notification engine / status // registry that read the same constants. Assert every floor is present in the diff --git a/src/lib/ai/prompts/base-system.ts b/src/lib/ai/prompts/base-system.ts index 7dbf8106d..414921700 100644 --- a/src/lib/ai/prompts/base-system.ts +++ b/src/lib/ai/prompts/base-system.ts @@ -214,10 +214,10 @@ Synthese statt Aufzählung: die Geschichte dessen, was die Daten bedeuten, zähl - Jüngste Messung deutlich älter als ~7 Tage (dataCoverage.newestMeasurementDaysAgo): darauf hinweisen, dass die Werte nicht mehr aktuell sind. - Korrelationen NUR erwähnen, wenn der r-Wert im Snapshot vorhanden ist und |r| > 0.4 ist. Fehlt das Feld, keine Korrelation interpretieren oder erfinden. Immer als "Zusammenhang" formulieren, nie als "Ursache".`, }, - // v1.18.7 (HIGH-2) — sourced from the single shared-contract fragment so a + // v1.18.7 — sourced from the single shared-contract fragment so a // forbidden-filler edit lands on every surface at once. { id: "forbidden", en: forbiddenFiller.en, de: forbiddenFiller.de }, - // v1.18.7 (HIGH-2) — the shared metric-identifier ban + the GLP-1 dose + // v1.18.7 — the shared metric-identifier ban + the GLP-1 dose // safety contract. The status cards can surface a named medication via the // medication-compliance snapshot, so the dose-safety contract (previously // absent from this surface) now applies here too. @@ -231,12 +231,12 @@ Synthese statt Aufzählung: die Geschichte dessen, was die Daten bedeuten, zähl // deferral above; surfaces a closed crisis list to prompt/emergency care // without diagnosing. { id: "safetyAcute", en: safetyAcute.en, de: safetyAcute.de }, - // v1.18.7 (HIGH-2) — the shared grounding + tone contracts, canonical + // v1.18.7 — the shared grounding + tone contracts, canonical // wording. The metric-specific build/tone sections above stay; these pin // the cross-surface wording so an edit lands here and everywhere at once. { id: "sharedGrounding", en: grounding.en, de: grounding.de }, { id: "sharedTone", en: toneContract.en, de: toneContract.de }, - // v1.27.13 (Welle J) — the anti-recitation + interpretation-depth contracts. + // v1.27.13 — the anti-recitation + interpretation-depth contracts. // The assessment surface is exactly where the maintainer's "counts are nice // but useless" complaint lands, so both contracts are enforced here: don't // narrate mechanics, and place the value on its guideline scale + judge the @@ -248,7 +248,7 @@ Synthese statt Aufzählung: die Geschichte dessen, was die Daten bedeuten, zähl en: interpretationDepth.en, de: interpretationDepth.de, }, - // v1.22 (W6) — paragraph formatting contract so a longer assessment renders + // v1.22 — paragraph formatting contract so a longer assessment renders // as real paragraphs through the shared `ProseBlocks` helper. { id: "sharedFormatting", diff --git a/src/lib/ai/prompts/compact-sections.ts b/src/lib/ai/prompts/compact-sections.ts index 6c384fcd4..cec8b7ff8 100644 --- a/src/lib/ai/prompts/compact-sections.ts +++ b/src/lib/ai/prompts/compact-sections.ts @@ -1,5 +1,5 @@ /** - * v1.4.36 W3 T4 — `compactSections` helper for the AI prompt body. + * v1.4.36 — `compactSections` helper for the AI prompt body. * * The features payload (and the Coach snapshot, transitively) carries * conditionally-populated blocks: `medications`, `bucketedMeasurements`, diff --git a/src/lib/ai/prompts/insight-generator.ts b/src/lib/ai/prompts/insight-generator.ts index c039ff6ff..5ad480107 100644 --- a/src/lib/ai/prompts/insight-generator.ts +++ b/src/lib/ai/prompts/insight-generator.ts @@ -65,7 +65,7 @@ import { userDayKey } from "@/lib/tz/format"; export const PROMPT_VERSION = "5.1.0" as const; /** - * v1.22 (W6) — the daily-briefing personalization SYSTEM CONTEXT block. + * v1.22 — the daily-briefing personalization SYSTEM CONTEXT block. * * Two deterministic, per-(user, day) touches the briefing user prompt appends: * - an OPENER HINT (the hash-seeded archetype rotation) so the lead varies @@ -883,7 +883,7 @@ user-logged event with a neutral label + detail. Hard cap 20 entries.`, Eintrag verweist auf ein reales, vom Nutzer geloggtes Ereignis mit neutralem Label + Detail. Höchstgrenze: 20 Einträge.`, }, - // v1.18.7 (HIGH-2) — the shared cross-surface contracts as the canonical + // v1.18.7 — the shared cross-surface contracts as the canonical // source. The detailed rules above stay for this calibrated surface; these // restate the five contracts in the wording every surface now shares, so a // future safety edit is made once in `shared-contracts.ts`. @@ -893,7 +893,7 @@ neutralem Label + Detail. Höchstgrenze: 20 Einträge.`, // briefing already leads verdict-first (rule 8); this pins it to the same // fragment the per-metric cards carry so the two surfaces can't drift. { id: "sharedOpeningShape", en: openingShape.en, de: openingShape.de }, - // v1.27.13 (Welle J) — the overview + briefing text carried the same + // v1.27.13 — the overview + briefing text carried the same // recitation disease (counts, cadence). Both contracts apply here so the // summary and the daily briefing interpret rather than enumerate; the // briefing's own grounding/format machinery is untouched. @@ -927,7 +927,7 @@ neutralem Label + Detail. Höchstgrenze: 20 Einträge.`, en: outlookContract.en, de: outlookContract.de, }, - // v1.22 (W6) — paragraph formatting so the briefing paragraph renders as real + // v1.22 — paragraph formatting so the briefing paragraph renders as real // paragraphs through the shared `ProseBlocks` helper. { id: "sharedFormatting", @@ -974,7 +974,7 @@ const SYSTEM_PROMPT_EN = composeInsightPrompt("en"); const SYSTEM_PROMPT_DE = composeInsightPrompt("de"); /** - * v1.4.25 W14c — native locale-specific Insights system prompts. + * v1.4.25 — native locale-specific Insights system prompts. * * Replaces the W9e REPLY-LANGUAGE-footer plumbing. For FR / ES / IT / * PL the prompt body is assembled from the safety-contract matrix in @@ -1016,7 +1016,7 @@ export function getStrictInsightsSystemPrompt(locale: Locale): string { } /** - * v1.4.16 phase B5a — return the scope-hardened prompt with a + * v1.4.16 — return the scope-hardened prompt with a * dynamically-built SOURCES block injected at the end. Only * references whose `metricApplicability` overlaps the current * `metrics[]` are listed, so a weight-only call doesn't burn tokens @@ -1038,7 +1038,7 @@ export function buildSystemPromptWithReferences( const refs = selectReferencesForMetrics(metrics); if (refs.length === 0) return base; - // v1.4.25 W9e — AI-initial locales (FR/ES/IT/PL) ride the EN sources + // v1.4.25 — AI-initial locales (FR/ES/IT/PL) ride the EN sources // block. The "title" field is the EN title; the model is told to // surface the citation id, not to translate the source title, so this // is safe — citation labels in the UI use the contract id (lowercase, @@ -1109,7 +1109,7 @@ export const OUT_OF_SCOPE_REFUSAL_DE = { }; /** - * v1.4.25 W14c — native out-of-scope refusal payloads. Lazy-built + * v1.4.25 — native out-of-scope refusal payloads. Lazy-built * from the safety-contract matrix so the summary copy stays in sync * with the prompt body that taught the model to emit it. The EN / * DE constants above remain authoritative for their locales — these diff --git a/src/lib/ai/prompts/insight-system-prompt.ts b/src/lib/ai/prompts/insight-system-prompt.ts index bc6f79d80..75f8351d9 100644 --- a/src/lib/ai/prompts/insight-system-prompt.ts +++ b/src/lib/ai/prompts/insight-system-prompt.ts @@ -6,7 +6,7 @@ import { instructionLocale } from "./output-language"; import type { ComparisonBaseline } from "@/lib/dashboard-layout"; /** - * v1.4.16 phase B8 — comparison snapshot the prompt narrates against + * v1.4.16 — comparison snapshot the prompt narrates against * when the user has the dashboard's "Compare to" toggle on. The snapshot * is opt-in: pass it through `buildUserPrompt`'s 4th argument when * `comparisonBaseline !== "none"` so the LLM has prior-period context @@ -77,7 +77,7 @@ ${featuresJson}${comparisonBlock}`; } /** - * v1.4.16 phase B8 — comparison context block. + * v1.4.16 — comparison context block. * * Renders a dedicated SYSTEM CONTEXT section after the user data so * the LLM can narrate the comparison ("Your average BP improved by diff --git a/src/lib/ai/prompts/interpretation-block.ts b/src/lib/ai/prompts/interpretation-block.ts index 7e42185a6..480eb85ed 100644 --- a/src/lib/ai/prompts/interpretation-block.ts +++ b/src/lib/ai/prompts/interpretation-block.ts @@ -1,5 +1,5 @@ /** - * v1.27.13 (Welle J) — the per-metric INTERPRETATION CONTEXT block. + * v1.27.13 — the per-metric INTERPRETATION CONTEXT block. * * Renders the guideline band table + the server-computed band position for one * value into the assessment user prompt, so the model can say what the value diff --git a/src/lib/ai/prompts/metric-archetypes.ts b/src/lib/ai/prompts/metric-archetypes.ts index 228e1bbca..99975594d 100644 --- a/src/lib/ai/prompts/metric-archetypes.ts +++ b/src/lib/ai/prompts/metric-archetypes.ts @@ -133,7 +133,7 @@ export function getMetricArchetypeUserPrompt( */ assessmentContextBlock?: string, /** - * v1.27.13 (Welle J) — the guideline interpretation block (band table + + * v1.27.13 — the guideline interpretation block (band table + * server-computed position) for metrics the knowledge base covers. Absent * for metrics with no guideline band — the prompt then stays personal- * relative, exactly as before. diff --git a/src/lib/ai/prompts/native-prompts.ts b/src/lib/ai/prompts/native-prompts.ts index aa0a79503..4715cd755 100644 --- a/src/lib/ai/prompts/native-prompts.ts +++ b/src/lib/ai/prompts/native-prompts.ts @@ -1,5 +1,5 @@ /** - * v1.4.25 W14c — Native locale-specific Coach + Insights system prompts. + * v1.4.25 — Native locale-specific Coach + Insights system prompts. * * Replaces the W9e "REPLY LANGUAGE footer" approach for FR / ES / IT / * PL. Each locale gets a hand-curated persona / examples / output-format diff --git a/src/lib/ai/prompts/opener-archetype.ts b/src/lib/ai/prompts/opener-archetype.ts index b08802fa0..bb6d31d3d 100644 --- a/src/lib/ai/prompts/opener-archetype.ts +++ b/src/lib/ai/prompts/opener-archetype.ts @@ -1,5 +1,5 @@ /** - * v1.22 (W6) — deterministic opener-archetype rotation + hash helpers. + * v1.22 — deterministic opener-archetype rotation + hash helpers. * * The narrative weakness the maintainer reports ("every text is structured the * same way") is structural: the per-metric card, the briefing, and the score diff --git a/src/lib/ai/prompts/safety-contracts.ts b/src/lib/ai/prompts/safety-contracts.ts index 6895b9dda..ad875cdcd 100644 --- a/src/lib/ai/prompts/safety-contracts.ts +++ b/src/lib/ai/prompts/safety-contracts.ts @@ -110,7 +110,7 @@ const OutOfScopeRefusalSchema = z.object({ }); /** - * v1.4.25 W19c — drug-level refusal contract. Sits alongside the + * v1.4.25 — drug-level refusal contract. Sits alongside the * ground-rule matrix and drives the adversarial drug-level probe suite. * Trigger phrases are NOT prompt copy — they are the test surface the * probe runner sweeps against the ground-rule-15 body. Expected / diff --git a/src/lib/ai/prompts/shared-contracts.ts b/src/lib/ai/prompts/shared-contracts.ts index a09319ac8..c80b08acb 100644 --- a/src/lib/ai/prompts/shared-contracts.ts +++ b/src/lib/ai/prompts/shared-contracts.ts @@ -24,7 +24,7 @@ * The cross-surface coverage test (`shared-contracts-coverage.test.ts`) * asserts each fragment appears verbatim in every surface that enforces it. * - * v1.21.0 (D3-M1 / D3-L1) — the acute-safety clause's threshold numbers are no + * v1.21.0 — the acute-safety clause's threshold numbers are no * longer prose literals: they are composed from the canonical * `clinical-floors.ts` constants so the Coach's stated crisis thresholds can * never drift from the dashboard hero, the notification engine, or the status @@ -97,7 +97,7 @@ Schreibe in der zweiten Person, warm und direkt. Wenn die Daten es hergeben, ben }; /** - * v1.27.13 (Welle J) — anti-recitation contract. The maintainer's complaint, + * v1.27.13 — anti-recitation contract. The maintainer's complaint, * verbatim intent: "that I logged it three times, or in what rhythm values were * taken, is nice but does nothing for me." Measurement counts, logging cadence, * and page-mechanics narration are NOT insights. They may appear ONLY when they @@ -111,7 +111,7 @@ Eine Messanzahl ("3-mal erfasst"), ein Erfassungsrhythmus ("alle paar Tage gemes }; /** - * v1.27.13 (Welle J) — interpretation-depth contract. Assessments must + * v1.27.13 — interpretation-depth contract. Assessments must * interpret, not enumerate: place the value on its guideline scale, judge the * trend BY that position, frame the consequence without diagnosis. Where the * user prompt carries an INTERPRETATION CONTEXT block, lead from it; where it @@ -202,7 +202,7 @@ Schließe, wenn es passt, mit einem kleinen Ausblick, damit sich die Person in d }; /** - * v1.22 (W6) — paragraph formatting contract. The prose surfaces render through + * v1.22 — paragraph formatting contract. The prose surfaces render through * the shared `ProseBlocks` helper, which turns a blank line into a real * paragraph break — but only if the model emits one. This single fragment asks * for that structure on every narrative surface so a longer assessment reads as diff --git a/src/lib/ai/prompts/status-batch.ts b/src/lib/ai/prompts/status-batch.ts index 340a94f28..5f021b000 100644 --- a/src/lib/ai/prompts/status-batch.ts +++ b/src/lib/ai/prompts/status-batch.ts @@ -1,5 +1,5 @@ /** - * v1.18.7 (HIGH-1) — the prompt scaffold for the BATCHED per-metric status + * v1.18.7 — the prompt scaffold for the BATCHED per-metric status * assessment. * * Instead of seven independent `runStatusCompletion` calls — one per diff --git a/src/lib/ai/provider-chain.ts b/src/lib/ai/provider-chain.ts index ccfa8409d..9cefda920 100644 --- a/src/lib/ai/provider-chain.ts +++ b/src/lib/ai/provider-chain.ts @@ -1,5 +1,5 @@ /** - * v1.4.16 phase B5b — multi-provider redundancy. + * v1.4.16 — multi-provider redundancy. * * The persisted shape on `User.aiProviderChain` is a JSON array of * entries describing **which** providers to try and **in what order**. diff --git a/src/lib/ai/provider-health-ledger.ts b/src/lib/ai/provider-health-ledger.ts index 1113fe373..1ffbb461c 100644 --- a/src/lib/ai/provider-health-ledger.ts +++ b/src/lib/ai/provider-health-ledger.ts @@ -1,5 +1,5 @@ /** - * v1.11.0 W1 — durable provider-health ledger. + * v1.11.0 — durable provider-health ledger. * * Epic B Pillar 4 (resilience floor). Promotes the volatile per-worker * `lastWorkingCache` in `provider-runner.ts` to a Postgres table so the diff --git a/src/lib/ai/provider-runner.ts b/src/lib/ai/provider-runner.ts index 3fd812767..3e1bf413c 100644 --- a/src/lib/ai/provider-runner.ts +++ b/src/lib/ai/provider-runner.ts @@ -26,7 +26,7 @@ import { const NEVER_SKIP: ReadonlySet = new Set(["local"]); /** - * v1.4.16 phase B5b — multi-provider redundancy runner. + * v1.4.16 — multi-provider redundancy runner. * * The chain runner wraps a provider completion with a hard-failure * walkthrough across an ordered list of providers. The motivation is @@ -55,7 +55,7 @@ const NEVER_SKIP: ReadonlySet = new Set(["local"]); * v1.4.16 the per-worker cache is enough to absorb the typical * "polling reload" pattern. * - * v1.18.7 (LOW-8) — DECISION: the volatile per-worker hint is left + * v1.18.7 — DECISION: the volatile per-worker hint is left * in-process and NOT folded into the durable Postgres health ledger. The * ledger already shares the load-bearing signal across workers (the * skip-hint reorder in `resolveChainOrder` pushes a dead-credential / @@ -185,7 +185,7 @@ export class AllProvidersFailedError extends Error { readonly httpStatus: number; readonly attempts: FallbackHop[]; /** - * v1.11.0 W1 — true when the FIRST (highest-priority) chain entry + * v1.11.0 — true when the FIRST (highest-priority) chain entry * failed with an auth-class status (401/403). That is the signal a * user's primary credential is dead and should be surfaced as * `credential_expired` ("reconnect ChatGPT") rather than a generic @@ -497,7 +497,7 @@ async function runRawChain( for (let i = 0; i < ordered.length; i += 1) { const candidate = ordered[i]; - // v1.37.19 (A7-2) — hop-time operator-cost guard. A request whose + // v1.37.19 — hop-time operator-cost guard. A request whose // primary provider runs on the USER's own key reserves its budget // under the generous user-plan ceiling; when the chain then falls // back onto an operator-funded `admin-*` entry (or the health-ledger diff --git a/src/lib/ai/provider.ts b/src/lib/ai/provider.ts index 5223c90e9..51f99b5c6 100644 --- a/src/lib/ai/provider.ts +++ b/src/lib/ai/provider.ts @@ -563,7 +563,7 @@ async function readBoundSettings(userId: string): Promise<{ } /** - * v1.4.16 phase B5b — resolve a chain of providers in priority order + * v1.4.16 — resolve a chain of providers in priority order * for the multi-provider fallback runner. Each entry pairs a logical * `providerType` with a constructed `AIProvider` instance ready to * accept `generateCompletion()` calls. diff --git a/src/lib/ai/schema.ts b/src/lib/ai/schema.ts index 994b53fb4..5bfcb876d 100644 --- a/src/lib/ai/schema.ts +++ b/src/lib/ai/schema.ts @@ -78,7 +78,7 @@ const MEDICAL_REFERENCE_ID_SET: ReadonlySet = new Set( ); /** - * v1.4.16 phase B5c — per-recommendation explainability rationale. + * v1.4.16 — per-recommendation explainability rationale. * * Each rec carries a 3-field rationale that powers the Oura-style * "Contributors" expand-card: @@ -135,7 +135,7 @@ export const aiRecommendationSchema = z /** Severity — generic for v1.4.15, clinically-keyed in v1.4.16. */ severity: recommendationSeveritySchema, /** - * v1.4.16 phase B5c — per-recommendation explainability rationale. + * v1.4.16 — per-recommendation explainability rationale. * Required on every rec so the UI's expand-card always has WHY + * WINDOW + COMPARED-TO content to render. The strict parser * rejects a legacy (pre-B5c) payload that omits it. @@ -154,7 +154,7 @@ export const aiRecommendationSchema = z */ referenceId: z.string().optional(), /** - * v1.4.16 phase B5d — deterministic confidence score (0-100). + * v1.4.16 — deterministic confidence score (0-100). * * Optional at parse-time: * - The LLM may emit a value (we accept it so the payload round- @@ -201,7 +201,7 @@ export const aiWarningSchema = z.object({ }); /** - * v1.4.20 phase B1 — Daily Briefing block. + * v1.4.20 — Daily Briefing block. * * The Insights redesign hero strip + full-width briefing card render a * narrative paragraph synthesised from the day's data plus up to five @@ -355,7 +355,7 @@ export const dailyBriefingSchema = z.object({ export type DailyBriefing = z.infer; /** - * v1.4.20 phase B3 — optional one-sentence Trend Annotations. + * v1.4.20 — optional one-sentence Trend Annotations. * * The Trends row on `/insights` renders three small charts (BP / weight / * mood). Each chart can carry a one-sentence AI-authored annotation @@ -419,7 +419,7 @@ export const trendAnnotationsSchema = z.object({ export type TrendAnnotations = z.infer; /** - * v1.4.20 phase B4 — Storyboard annotations. + * v1.4.20 — Storyboard annotations. * * The 90-day BP timeline on `/insights` overlays factual events the user * logged ("started medication X", "first sustained dip below 140") onto @@ -481,19 +481,19 @@ export const aiInsightResponseSchema = z /** Guideline-flagged values (e.g. BP > 140/90). May be empty. */ warnings: z.array(aiWarningSchema), /** - * v1.4.20 phase B1 — optional Daily Briefing payload. Nullable + + * v1.4.20 — optional Daily Briefing payload. Nullable + * optional so legacy cached payloads (from before PROMPT_VERSION * 4.20.0) round-trip without forcing a regenerate. */ dailyBriefing: dailyBriefingSchema.nullable().optional(), /** - * v1.4.20 phase B3 — optional Trend Annotations block. Each metric + * v1.4.20 — optional Trend Annotations block. Each metric * is independently optional; the wrapping object is nullable so * legacy 4.20.0 caches round-trip. */ trendAnnotations: trendAnnotationsSchema.nullable().optional(), /** - * v1.4.20 phase B4 — optional storyboard annotations. Each entry + * v1.4.20 — optional storyboard annotations. Each entry * pins a vertical reference line + chapter card to a date on the * 90-day BP timeline. Optional so legacy caches round-trip. */ diff --git a/src/lib/ai/types.ts b/src/lib/ai/types.ts index be66d3c42..bf4b47fc2 100644 --- a/src/lib/ai/types.ts +++ b/src/lib/ai/types.ts @@ -24,7 +24,7 @@ export const insightDataQualitySchema = z.object({ }); /** - * v1.4.16 phase B5c — rationale shape mirrors the strict + * v1.4.16 — rationale shape mirrors the strict * `aiRecommendationRationaleSchema`. dataWindow values match * `mini-window.ts` so the rec card's mini-chart can pin to the right * window without an extra mapping table. @@ -42,7 +42,7 @@ export const insightRecommendationRationaleSchema = z.object({ * `referenceId` is set and resolves to a known entry in * `MEDICAL_REFERENCES`. * - * v1.4.16 phase B5c — adds `rationale` (Oura-style "Contributors") + * v1.4.16 — adds `rationale` (Oura-style "Contributors") * and `metricSource` so the rec card can render the expand-out * explainability panel + a mini-chart pinned to the rec's window. * Both fields are optional on the UI type because legacy cached @@ -66,7 +66,7 @@ export const insightRecommendationSchema = z.union([ severity: z.enum(["info", "suggestion", "important", "urgent"]).optional(), id: z.string().optional(), /** - * v1.4.16 phase B5d — deterministic confidence score (0-100). + * v1.4.16 — deterministic confidence score (0-100). * Optional on the UI type because legacy cached payloads predate * the field; `` renders a "draft" pill in that * case. Server-side `aiRecommendationSchema` mirrors this shape. diff --git a/src/lib/analytics/__tests__/bp-grade.test.ts b/src/lib/analytics/__tests__/bp-grade.test.ts index 170e7d2e5..d7c83e365 100644 --- a/src/lib/analytics/__tests__/bp-grade.test.ts +++ b/src/lib/analytics/__tests__/bp-grade.test.ts @@ -1,5 +1,5 @@ /** - * v1.15.12 A1 — unit pins for the graded BP pillar score. + * v1.15.12 — unit pins for the graded BP pillar score. * * The maintainer's apps01 profile (under-65 ceiling 129/79, avg 134/87) * is the canonical regression: the binary all-time in-target rate read diff --git a/src/lib/analytics/__tests__/bp-in-target-fast-path.test.ts b/src/lib/analytics/__tests__/bp-in-target-fast-path.test.ts index 49b1181f6..2b53ebd7c 100644 --- a/src/lib/analytics/__tests__/bp-in-target-fast-path.test.ts +++ b/src/lib/analytics/__tests__/bp-in-target-fast-path.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.37 W2 — unit pin for the probe-gated bp_in_target helper. + * v1.4.37 — unit pin for the probe-gated bp_in_target helper. * * Mocks Prisma + the rollup-coverage probe so the path-selection * contract is verifiable without a Postgres container. Real-rollup @@ -15,7 +15,7 @@ vi.mock("@/lib/db", () => ({ // v1.11.1 — readRollupBuckets lazy-loads the source-priority blob; null // (default findUnique) falls back to the default ladders. user: { findUnique: vi.fn() }, - // v1.18.10 I-1 — the live BP fallback now reads source-collapsed rows via + // v1.18.10 — the live BP fallback now reads source-collapsed rows via // `$queryRawUnsafe` (`canonicalMeasurementsFrom`) instead of a chunked // `findMany`, so cold == warm. Mock it to feed the SYS / DIA series. $queryRawUnsafe: vi.fn(), @@ -422,7 +422,7 @@ describe("computeBpInTargetFastPath", () => { expect(ROLLUP_FIND_MANY).not.toHaveBeenCalled(); }); - // v1.30.3 (QA F7) — the guard force-routes a non-near-UTC user to the + // v1.30.3 — the guard force-routes a non-near-UTC user to the // live path specifically so their pairing is computed correctly; before // this fix the live path's same-day fallback still hardcoded the Berlin // day, undoing exactly the protection the guard exists to provide. diff --git a/src/lib/analytics/__tests__/bp-in-target.test.ts b/src/lib/analytics/__tests__/bp-in-target.test.ts index 13ac814f8..a8e376e95 100644 --- a/src/lib/analytics/__tests__/bp-in-target.test.ts +++ b/src/lib/analytics/__tests__/bp-in-target.test.ts @@ -120,7 +120,7 @@ describe("computeBpInTargetPct", () => { }); /** - * v1.30.3 (QA F7) — the same-day pairing fallback used to hardcode the + * v1.30.3 — the same-day pairing fallback used to hardcode the * Berlin calendar day regardless of the caller's actual user. A New * York user logging SYS at 17:50 and DIA at 18:10 local time (>5-min * session gap) sits on 2026-05-01 in New York but straddles 23:50 → @@ -332,7 +332,7 @@ describe("computeBpInTargetWindows — all-time headline (v1.4.19 A1)", () => { }); /** - * v1.17 W1d — the BD-Zielbereich headline, the Health-Score BP pillar + * v1.17 — the BD-Zielbereich headline, the Health-Score BP pillar * and the coach grounding number standardise on a **90-day** window. * The helper must surface a `last90Days` window distinct from both the * 30-day and the all-time aggregate so the route can route the headline @@ -567,7 +567,7 @@ describe("computeBpInTargetWindows", () => { describe("computeBpInTargetWindows — period-aligned prior windows (Code-H2)", () => { /** - * v1.4.22 W5 reconcile (Code-H2) — the BD-Zielbereich tile's + * v1.4.22 — the BD-Zielbereich tile's * comparison-overlay caption used to compute `last30Days - allTime` * regardless of the user's `comparisonBaseline` selection. The math * therefore claimed "vs. last month" while subtracting an all-time diff --git a/src/lib/analytics/__tests__/correlations-fast-path.test.ts b/src/lib/analytics/__tests__/correlations-fast-path.test.ts index 5e447d2aa..36807947f 100644 --- a/src/lib/analytics/__tests__/correlations-fast-path.test.ts +++ b/src/lib/analytics/__tests__/correlations-fast-path.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.37 W2 — unit pin for the probe-gated correlation runner. + * v1.4.37 — unit pin for the probe-gated correlation runner. * * Mocks Prisma + the rollup-coverage probe so the path-selection * contract is verifiable without a Postgres container. Real-data @@ -100,7 +100,7 @@ describe("computeCorrelationHypothesesFastPath", () => { expect(result.path).toBe("rollup"); expect(result.windowDays).toBe(28); expect(result.degraded).toBe(false); - // v1.2.5 (M-CS2) — SYS + WEIGHT ride the rollup tier; PULSE no + // v1.2.5 — SYS + WEIGHT ride the rollup tier; PULSE no // longer does. The mood × resting-pulse hypothesis instead reads the // RESTING_HEART_RATE rows (and, only when absent, RAW PULSE for the // low-percentile proxy) from `measurements`, so the rollup count diff --git a/src/lib/analytics/__tests__/intraday-pulse.test.ts b/src/lib/analytics/__tests__/intraday-pulse.test.ts index 7e8775a5d..26d6cd384 100644 --- a/src/lib/analytics/__tests__/intraday-pulse.test.ts +++ b/src/lib/analytics/__tests__/intraday-pulse.test.ts @@ -1,5 +1,5 @@ /** - * S11 — intraday pulse shape + elevated-at-rest ("tension") detection. + * Intraday pulse shape + elevated-at-rest ("tension") detection. * * The signal-correctness bar for this slice is high (the launch posture is * "says less than it could"), so these pin the two things that are easy to get diff --git a/src/lib/analytics/__tests__/resting-pulse.test.ts b/src/lib/analytics/__tests__/resting-pulse.test.ts index 71598112d..ed70f3f76 100644 --- a/src/lib/analytics/__tests__/resting-pulse.test.ts +++ b/src/lib/analytics/__tests__/resting-pulse.test.ts @@ -1,5 +1,5 @@ /** - * v1.15.12 A2 — unit pins for resting-pulse estimation. + * v1.15.12 — unit pins for resting-pulse estimation. * * The canonical regression (the maintainer's apps01 profile): a day with * ~500 high workout PULSE samples + ~10 resting reads must NOT classify diff --git a/src/lib/analytics/__tests__/source-priority.test.ts b/src/lib/analytics/__tests__/source-priority.test.ts index f886505e2..14abaf374 100644 --- a/src/lib/analytics/__tests__/source-priority.test.ts +++ b/src/lib/analytics/__tests__/source-priority.test.ts @@ -3,8 +3,8 @@ import { describe, expect, it } from "vitest"; import { pickCanonicalSourceRows } from "../source-priority"; /** - * v1.4.25 W5e — cross-source canonical-row picker tests. - * v1.4.25 W8c — two-axis picker (source + device-type) tests. + * v1.4.25 — cross-source canonical-row picker tests. + * v1.4.25 — two-axis picker (source + device-type) tests. * * Coverage focuses on the cumulative-metric use case where two * sources record the same day's value and one must win. @@ -360,7 +360,7 @@ describe("pickCanonicalSourceRows — v1.4.25 W8c two-axis device-type picker", }); it("resolves the device-type ladder per row when the bucket carries mixed MeasurementTypes", () => { - // v1.4.25 W10 reconcile (Sr-H2) — when a caller batches more than + // v1.4.25 — when a caller batches more than // one MeasurementType through a single picker call (Coach evidence // rollup, doctor-PDF section, correlations engine), each row-type // must resolve against its OWN ladder. The pre-W10 picker pinned diff --git a/src/lib/analytics/__tests__/summaries-slice.test.ts b/src/lib/analytics/__tests__/summaries-slice.test.ts index de194fba8..1dfaa998f 100644 --- a/src/lib/analytics/__tests__/summaries-slice.test.ts +++ b/src/lib/analytics/__tests__/summaries-slice.test.ts @@ -100,7 +100,7 @@ beforeEach(() => { ROLLUP_FIND_MANY.mockResolvedValue([]); ROLLUP_FIND_FIRST.mockResolvedValue(null); MEASUREMENT_FIND_FIRST.mockResolvedValue(null); - // v1.37.19 (A6-10) — default the tagged-template RAW reads (the coverage + // v1.37.19 — default the tagged-template RAW reads (the coverage // probe + the pre-fold all-time remainder) to empty; individual tests // queue their own `mockResolvedValueOnce` sequences on top. RAW.mockResolvedValue([]); @@ -147,7 +147,7 @@ describe("computeSummariesSlice", () => { }); it("maps a populated heavy aggregate row into the DataSummary shape on cold path", async () => { - // v1.4.48 M0 — cold path now splits the heavy aggregate into + // v1.4.48 — cold path now splits the heavy aggregate into // all-time + 90-day-capped windowed queries. Mock order: // 1. coverage probe (WEIGHT uncovered → cold path) // 2. all-time aggregate (count / min / max / mean) @@ -330,7 +330,7 @@ describe("computeSummariesSlice", () => { avg7: 82, avg30: 82.5, median: 82.1, - // v1.20.0 F6 — the narrow query no longer carries slope/r²; those + // v1.20.0 — the narrow query no longer carries slope/r²; those // compose from the accumulator buckets mocked below. }, ]) @@ -353,7 +353,7 @@ describe("computeSummariesSlice", () => { }, ]); - // v1.20.0 F6 — the 90-day accumulator findMany (first measurementRollup + // v1.20.0 — the 90-day accumulator findMany (first measurementRollup // .findMany in the Promise.all). Two single-reading DAY buckets one day // apart inside the 7-day window: x = epoch-days, y = 82.00 then 82.02. // composeWindowedRegression over them yields slope = 0.02/day with a @@ -399,7 +399,7 @@ describe("computeSummariesSlice", () => { expect(weight.median).toBe(82.1); expect(weight.latest).toBe(82.7); expect(weight.avg7).toBe(82); - // v1.20.0 F6 — slope composed from the accumulator buckets: a clean + // v1.20.0 — slope composed from the accumulator buckets: a clean // +0.02/day with a perfect two-point fit. expect(weight.slope7).toEqual({ slope: 0.02, @@ -548,7 +548,7 @@ describe("computeSummariesSlice", () => { }); it("only probes types that actually have data in the current window", async () => { - // v1.4.48 M0 — empty coverage map ⇒ `isFullyCovered` returns + // v1.4.48 — empty coverage map ⇒ `isFullyCovered` returns // false ⇒ cold-fallback path. v1.11.1 — coverage probe stays on // `$queryRaw`; all-time + windowed + latests run via // `$queryRawUnsafe`. All return empty arrays here ⇒ no @@ -565,7 +565,7 @@ describe("computeSummariesSlice", () => { }); /** - * v1.4.48 M0 — pins the 90-day outer `measured_at` cap on the two + * v1.4.48 — pins the 90-day outer `measured_at` cap on the two * windowed measurements scans: the `narrows` query inside the * rollup-fresh happy path and the `windowed` query inside the * cold-fallback path. Both must constrain the outer WHERE to the @@ -619,7 +619,7 @@ describe("computeSummariesSlice", () => { expect.any(String), "90 days", ); - // v1.18.10 P-7 — the `allTime` aggregate is now capped at a generous + // v1.18.10 — the `allTime` aggregate is now capped at a generous // 15-year scan-DoS floor (far beyond any real history) so a // coverage-miss cold read can't trigger an unbounded full-partition // scan. It no longer calls the helper without an interval. diff --git a/src/lib/analytics/__tests__/trend-delta.test.ts b/src/lib/analytics/__tests__/trend-delta.test.ts index 28a5922fe..16c5d7c28 100644 --- a/src/lib/analytics/__tests__/trend-delta.test.ts +++ b/src/lib/analytics/__tests__/trend-delta.test.ts @@ -39,7 +39,7 @@ describe("summaryToTrend7Delta()", () => { }); it("falls back to slope30 when slope7 is null (sparse metrics like mood)", () => { - // v1.4.16 fix A4: mood-tile showed no trend number for users who + // v1.4.16: mood-tile showed no trend number for users who // log mood < daily because slope7 needs ≥2 points within the // trailing 7-day window. slope30 still answers "is this metric // drifting?" so we project that onto 7 days for label parity. diff --git a/src/lib/analytics/__tests__/window-trend.test.ts b/src/lib/analytics/__tests__/window-trend.test.ts index 013a5394a..85fcbb984 100644 --- a/src/lib/analytics/__tests__/window-trend.test.ts +++ b/src/lib/analytics/__tests__/window-trend.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest"; import { computeWindowTrend, SPLIT_HALF_THRESHOLD_DAYS } from "../window-trend"; /** - * v1.4.16 Fix A8b — chart trend overlay on the "All" filter. + * v1.4.16 — chart trend overlay on the "All" filter. * * Maintainer report: clicking the All range button on a multi-year metric * displays "+0.0 kg/Woche" because the per-week delta rounds to zero diff --git a/src/lib/analytics/bp-grade.ts b/src/lib/analytics/bp-grade.ts index bc809c859..de05379b1 100644 --- a/src/lib/analytics/bp-grade.ts +++ b/src/lib/analytics/bp-grade.ts @@ -1,5 +1,5 @@ /** - * v1.15.12 A1 — graded blood-pressure pillar score. + * v1.15.12 — graded blood-pressure pillar score. * * The Personal Health Score's BP pillar previously equalled * `bpInTargetRate`: the share of paired readings where BOTH systolic and diff --git a/src/lib/analytics/bp-in-target-fast-path.ts b/src/lib/analytics/bp-in-target-fast-path.ts index b5ebcbcc9..58de8a1bd 100644 --- a/src/lib/analytics/bp-in-target-fast-path.ts +++ b/src/lib/analytics/bp-in-target-fast-path.ts @@ -1,5 +1,5 @@ /** - * v1.4.37 W2 — probe-gated BP-in-target windowing. + * v1.4.37 — probe-gated BP-in-target windowing. * * The `/api/analytics` default slice computes six BP-in-target * percentages (7 days, 30 days, all-time, prior month, prior year) @@ -85,13 +85,13 @@ export interface BpInTargetEnvelope { last7Days: { pct: number; pairs: number } | null; last30Days: { pct: number; pairs: number } | null; /** - * v1.17 W1d — trailing-90-day window. Canonical window for the + * v1.17 — trailing-90-day window. Canonical window for the * BD-Zielbereich headline, the Health-Score BP pillar and the coach * grounding number. See `computeBpInTargetWindows` for the rationale. */ last90Days: { pct: number; pairs: number } | null; /** - * v1.17 W1b — oldest accepted pair inside the trailing-90-day window + * v1.17 — oldest accepted pair inside the trailing-90-day window * (or `null` when empty). Feeds `computeWindowConfidence` so the * BD-Zielbereich tile labels the effective span rather than a static * "· 90 T". On the rollup path this is the oldest in-window DAY bucket. @@ -109,7 +109,7 @@ export interface BpInTargetEnvelope { */ rowCount: number; /** - * v1.15.12 A1 — graded clinical-proximity BP score (0..100) from a + * v1.15.12 — graded clinical-proximity BP score (0..100) from a * recency-weighted representative reading over the trailing window. * Feeds the Health-Score BP pillar value, replacing the binary * in-target rate AS THE SCORE. `null` when no pairs formed. @@ -157,7 +157,7 @@ export async function computeBpInTargetFastPath(input: { now: Date; coverage?: RollupCoverageMap; /** - * v1.4.38 W-A — optional user timezone for the cross-tz runtime + * v1.4.38 — optional user timezone for the cross-tz runtime * guard. When omitted (legacy callers) the guard defaults to * "near-utc" because the canonical Berlin tenant lives at +1/+2 and * the rollup-path is safe; new callers (analytics route) pass it @@ -171,7 +171,7 @@ export async function computeBpInTargetFastPath(input: { const sysCovered = coverage.get("BLOOD_PRESSURE_SYS") === true; const diaCovered = coverage.get("BLOOD_PRESSURE_DIA") === true; - // v1.4.38 W-A — cross-tz runtime guard. The rollup path keys rows on + // v1.4.38 — cross-tz runtime guard. The rollup path keys rows on // `d.toISOString().slice(0, 10)` (UTC slice) and compares them // against `now - N*DAY_MS` boundaries. For a user inside the +-3h // band around UTC the day-key on the rollup row lines up with the @@ -216,7 +216,7 @@ async function computeFromRollups( now: Date, tzGuard: "near-utc" | "non-utc-live-fallback", ): Promise { - // v1.4.22 W5 reconcile (Code-H2) — the priorYear window starts 395 + // v1.4.22 — the priorYear window starts 395 // days ago. We read one extra day so the boundary day rolls cleanly // into the prior-year bucket without being dropped. // @@ -313,9 +313,9 @@ async function computeFromRollups( const last7 = bucketWindow(pairsByDay, sevenDaysAgo, now, targets); const last30 = bucketWindow(pairsByDay, thirtyDaysAgo, now, targets); - // v1.17 W1d — canonical headline / score / coach window. + // v1.17 — canonical headline / score / coach window. const last90 = bucketWindow(pairsByDay, ninetyDaysAgo, now, targets); - // v1.17 W1b — oldest in-window DAY bucket for the effective-span label. + // v1.17 — oldest in-window DAY bucket for the effective-span label. const ninetyDaysAgoMs = ninetyDaysAgo.getTime(); const nowMs = now.getTime(); let last90EarliestAt: Date | null = null; @@ -360,7 +360,7 @@ async function computeFromRollups( sys_rows: sysBuckets.reduce((s, b) => s + b.count, 0), dia_rows: diaBuckets.reduce((s, b) => s + b.count, 0), path: "rollup", - // v1.4.38 W-A — surfaces the cross-tz guard decision so + // v1.4.38 — surfaces the cross-tz guard decision so // ops logs can prove which branch fired and why. tz_guard: tzGuard, }, @@ -368,13 +368,13 @@ async function computeFromRollups( }, }); - // v1.15.12 A1 — graded BP pillar score from a recency-weighted + // v1.15.12 — graded BP pillar score from a recency-weighted // representative reading. Each per-day pair (the day's MEAN sys/dia) // contributes weighted by its age; recent readings dominate so genuine // improvement surfaces in the score instead of being diluted by years // of older readings. // - // v1.15.12 (audit HIGH-2) — pass `perDayPairCount` as the per-point + // v1.15.12 — pass `perDayPairCount` as the per-point // `count` weight so a high-variance day weighs N× (matching the live // per-event grade) instead of 1×. Without it the rollup and live paths // produced different graded scores for the same data depending on @@ -484,7 +484,7 @@ async function computeFromLive( now: Date, tzGuard: "near-utc" | "non-utc-live-fallback", /** - * v1.30.3 (QA F7) — the user's own IANA tz, threaded into the pairing + * v1.30.3 — the user's own IANA tz, threaded into the pairing * fallback (`computeBpInTargetWindows` / `collectBpPairs`). This is * exactly the guard's protected population: a non-near-UTC user gets * force-routed HERE specifically so their pairs are computed correctly @@ -496,7 +496,7 @@ async function computeFromLive( const DAY_MS = 24 * 60 * 60 * 1000; const bpInTargetSince = new Date(now.getTime() - 365 * DAY_MS); - // v1.18.10 I-1 — collapse overlapping sources before windowing. BP is a + // v1.18.10 — collapse overlapping sources before windowing. BP is a // multi-source metric (a Withings reading mirrored into Apple Health is two // rows for one event). Without the collapse the live/cold path double-counts // the shared reading in both the in-target denominator and the @@ -534,7 +534,7 @@ async function computeFromLive( sys_rows: sysData.length, dia_rows: diaData.length, path: "live", - // v1.4.38 W-A — surfaces the cross-tz guard decision so + // v1.4.38 — surfaces the cross-tz guard decision so // ops logs can attribute live-fallbacks to coverage vs tz. tz_guard: tzGuard, }, @@ -542,7 +542,7 @@ async function computeFromLive( }, }); - // v1.30.3 (QA F7) — `undefined` falls through to each helper's own + // v1.30.3 — `undefined` falls through to each helper's own // Berlin default (unchanged behaviour for legacy callers that never // pass a userTz); a real tz here overrides it. const windows = computeBpInTargetWindows( @@ -552,7 +552,7 @@ async function computeFromLive( now, userTz, ); - // v1.15.12 A1 — graded BP pillar score from the recency-weighted + // v1.15.12 — graded BP pillar score from the recency-weighted // representative of every accepted per-event pair over the read window. const scorePairs = collectBpPairs(sysData, diaData, userTz).filter( (pair) => pair.at >= new Date(now.getTime() - 90 * DAY_MS) && pair.at < now, @@ -588,7 +588,7 @@ async function computeFromLive( /** * Source-collapsed read of one BP type over the trailing 365 days. * - * v1.18.10 I-1 — routes through `canonicalMeasurementsFrom` so a reading that + * v1.18.10 — routes through `canonicalMeasurementsFrom` so a reading that * exists under two sources (Withings + its Apple-Health mirror) is counted * once per (type, day), matching the rollup fast-path. Pruned to the two * fields the in-target helper consumes (`measuredAt` + `value`). Per-event diff --git a/src/lib/analytics/bp-in-target.ts b/src/lib/analytics/bp-in-target.ts index fa10b42a4..95efc01df 100644 --- a/src/lib/analytics/bp-in-target.ts +++ b/src/lib/analytics/bp-in-target.ts @@ -141,7 +141,7 @@ export function computeBpInTargetPct( sysSeries: BpReading[], diaSeries: BpReading[], targets: BpTargets, - /** v1.30.3 (QA F7) — the user's own IANA tz; defaults to Berlin for + /** v1.30.3 — the user's own IANA tz; defaults to Berlin for * legacy callers that haven't threaded it through yet. */ tz: string = DEFAULT_TIMEZONE, ): { pct: number; pairs: number } | null { @@ -177,7 +177,7 @@ export function computeBpInTargetPct( } /** - * v1.15.12 A1 — collect the accepted SYS/DIA pairs (same pairing rules + * v1.15.12 — collect the accepted SYS/DIA pairs (same pairing rules * as `computeBpInTargetPct`) as timestamped points, for the graded BP * pillar score. Returns `{ at, sys, dia }` per accepted pair using the * SYS reading's timestamp as the pair anchor — recency weighting only @@ -186,7 +186,7 @@ export function computeBpInTargetPct( export function collectBpPairs( sysSeries: BpReading[], diaSeries: BpReading[], - /** v1.30.3 (QA F7) — see `computeBpInTargetPct`'s identical parameter. */ + /** v1.30.3 — see `computeBpInTargetPct`'s identical parameter. */ tz: string = DEFAULT_TIMEZONE, ): Array<{ at: Date; sys: number; dia: number }> { if (sysSeries.length === 0 || diaSeries.length === 0) return []; @@ -245,13 +245,13 @@ export function computeBpInTargetWindows( diaSeries: BpReading[], targets: BpTargets, now: Date = new Date(), - /** v1.30.3 (QA F7) — see `computeBpInTargetPct`'s identical parameter. */ + /** v1.30.3 — see `computeBpInTargetPct`'s identical parameter. */ tz: string = DEFAULT_TIMEZONE, ): { last7Days: { pct: number; pairs: number } | null; last30Days: { pct: number; pairs: number } | null; /** - * v1.17 W1d — the trailing-90-day window. This is the canonical window + * v1.17 — the trailing-90-day window. This is the canonical window * the BD-Zielbereich headline, the Health-Score BP pillar and the coach * grounding number all standardise on (a 90-day arc balances recency * against enough pairs to be stable). The all-time aggregate stays @@ -259,7 +259,7 @@ export function computeBpInTargetWindows( */ last90Days: { pct: number; pairs: number } | null; /** - * v1.17 W1b — timestamp of the oldest accepted pair inside the + * v1.17 — timestamp of the oldest accepted pair inside the * trailing-90-day window, or `null` when the window holds no pairs. * Feeds `computeWindowConfidence` so the BD-Zielbereich tile can label * the EFFECTIVE span ("· 23 T" until ~90 days of history exist) rather @@ -268,7 +268,7 @@ export function computeBpInTargetWindows( last90EarliestAt: Date | null; allTime: { pct: number; pairs: number } | null; /** - * v1.4.22 W5 reconcile (Code-H2) — period-aligned 30-day window + * v1.4.22 — period-aligned 30-day window * shifted back by one month (now-60d … now-30d). Used by the * BD-Zielbereich tile's comparison-overlay caption when the user's * `comparisonBaseline === "lastMonth"` so the rendered "Δ X% vs. @@ -276,7 +276,7 @@ export function computeBpInTargetWindows( */ priorMonth: { pct: number; pairs: number } | null; /** - * v1.4.22 W5 reconcile (Code-H2) — same shape, shifted back one + * v1.4.22 — same shape, shifted back one * year (now-395d … now-365d) for `comparisonBaseline === "lastYear"`. */ priorYear: { pct: number; pairs: number } | null; @@ -332,7 +332,7 @@ export function computeBpInTargetWindows( r.measuredAt.getTime() < oneYearMinus30DaysAgoMs, ); - // v1.17 W1b — oldest accepted pair inside the 90-day window for the + // v1.17 — oldest accepted pair inside the 90-day window for the // effective-span label. `collectBpPairs` applies the same pairing rules // as `computeBpInTargetPct`, so the anchor matches the counted pairs. const pairsLast90 = collectBpPairs(sysLast90, diaLast90, tz); @@ -347,10 +347,10 @@ export function computeBpInTargetWindows( return { last7Days: computeBpInTargetPct(sysLast7, diaLast7, targets, tz), last30Days: computeBpInTargetPct(sysLast30, diaLast30, targets, tz), - // v1.17 W1d — canonical headline / score / coach window. + // v1.17 — canonical headline / score / coach window. last90Days: computeBpInTargetPct(sysLast90, diaLast90, targets, tz), last90EarliestAt, - // v1.4.19 A1 — independent aggregate over EVERY paired reading. The + // v1.4.19 — independent aggregate over EVERY paired reading. The // analytics route now routes the dashboard tile's headline through // this so it stops mirroring `last30Days`. allTime: computeBpInTargetPct(sysSeries, diaSeries, targets, tz), diff --git a/src/lib/analytics/compliance/__tests__/compliance.test.ts b/src/lib/analytics/compliance/__tests__/compliance.test.ts index 4246d8ece..a2b355166 100644 --- a/src/lib/analytics/compliance/__tests__/compliance.test.ts +++ b/src/lib/analytics/compliance/__tests__/compliance.test.ts @@ -382,7 +382,7 @@ describe("calculateCompliance — cadence-aware adapter", () => { }); // ──────────────────────────────────────────────────────────────────── -// v1.7.0 SB-SCHED-2 — canonical-engine-routed compliance (Option B) +// v1.7.0 — canonical-engine-routed compliance (Option B) // // When a `medicationContext` is supplied, the expected-slot denominator // runs through the canonical recurrence engine. These golden fixtures @@ -991,7 +991,7 @@ describe("calculateCompliance — engine-routed (medicationContext)", () => { }); // ──────────────────────────────────────────────────────────────────── -// v1.7.0 item 5 — per-day due / expectedCount helpers +// v1.7.0 — per-day due / expectedCount helpers // ──────────────────────────────────────────────────────────────────── describe("expectedSlotCountForDay + lastNonSkippedTakenAt", () => { @@ -1220,7 +1220,7 @@ describe("expectedSlotCountForDay + lastNonSkippedTakenAt", () => { }); describe("classifyIntakeTiming", () => { - // v1.4.34 IW-C — the classifier now widens the pre-window grace to + // v1.4.34 — the classifier now widens the pre-window grace to // 3h and introduces an `early` bucket so a proactive logger (10 min // before the window) is no longer flushed to `very_late`. Doses up // to 3h past `windowEnd` stay `on_time`; `late` spans the next 2h @@ -3106,7 +3106,7 @@ describe("tallyComplianceFromLedger — the unified % keystone", () => { expect(tally.rate).toBeGreaterThanOrEqual(90); }); - // v1.15.18 W7 — the persisted per-dose window reaches the % through the + // v1.15.18 — the persisted per-dose window reaches the % through the // SAME minter the history view + write path use. An 11:29 take that the // default ±1h leaves ad-hoc (slot missed) is counted on-time once the // morning slot carries an explicit 07:00–12:00 window. diff --git a/src/lib/analytics/compliance/display.ts b/src/lib/analytics/compliance/display.ts index 7073eab46..521830ae5 100644 --- a/src/lib/analytics/compliance/display.ts +++ b/src/lib/analytics/compliance/display.ts @@ -357,7 +357,7 @@ export function calculateCompliance( windowStart: s.windowStart, windowEnd: s.windowEnd, daysOfWeek: s.daysOfWeek ?? null, - // v1.7.0 SB-SCHED-2 — thread the canonical-engine fields so the + // v1.7.0 — thread the canonical-engine fields so the // cadence expander can delegate to `occurrencesBetween` when a // medication context is supplied. Undefined fields collapse to the // legacy weekday path inside `expandScheduleSlots`. @@ -370,7 +370,7 @@ export function calculateCompliance( cyclicOffWeeks: s.cyclicOffWeeks ?? null, })); - // v1.7.0 SB-SCHED-2 — build the engine context once per medication. + // v1.7.0 — build the engine context once per medication. // When the caller supplies it, the timeline routes through the // canonical engine (RRULE / rolling / one-shot / PRN / cyclic); // otherwise the legacy weekday walker stays in force. diff --git a/src/lib/analytics/compliance/ledger.ts b/src/lib/analytics/compliance/ledger.ts index 230b3dfb0..4183fd326 100644 --- a/src/lib/analytics/compliance/ledger.ts +++ b/src/lib/analytics/compliance/ledger.ts @@ -201,7 +201,7 @@ export function buildComplianceLedgerRows( // mints phantom misses (#1028). const rows = reconstructDoseHistory(bands, intakes, now, ctx.createdAt); - // v1.25 H-MED1 — drop expected dose slots whose anchor falls inside a + // v1.25 — drop expected dose slots whose anchor falls inside a // pause interval. While a medication is paused no dose is expected, so a // slot minted across the paused window must never count as "missed" (the // denominator-inflating status). Only `slot` rows are dropped, and only diff --git a/src/lib/analytics/compliance/occurrences.ts b/src/lib/analytics/compliance/occurrences.ts index 0da497ea5..7e92f3519 100644 --- a/src/lib/analytics/compliance/occurrences.ts +++ b/src/lib/analytics/compliance/occurrences.ts @@ -17,7 +17,7 @@ import { } from "./adapters"; /** - * v1.7.0 item 5 — count the expected dose slots a medication's schedules + * v1.7.0 — count the expected dose slots a medication's schedules * emit inside `[dayStart, dayEnd)`, routed through the canonical engine. * Powers the per-day `due` / `expectedCount` fields on the per-med * compliance payload so iOS history renders a "missed" mark only on days @@ -81,7 +81,7 @@ export function expectedSlotCountForDay( * `[from, to]` window and we sort the union of every schedule's slots so * a multi-schedule medication interleaves its slots in time order. * - * v1.8.6 QA — the window lower bound is clamped to `ctx.createdAt`. The + * v1.8.6 — the window lower bound is clamped to `ctx.createdAt`. The * legacy weekday walker floors on `startsOn` but not on `createdAt`, so a * brand-new daily med queried over a 30-day window would otherwise emit * slots for every day before it existed (7/30 expected on a 2-day-old med). diff --git a/src/lib/analytics/compliance/parsing.ts b/src/lib/analytics/compliance/parsing.ts index 548e363da..f863f6973 100644 --- a/src/lib/analytics/compliance/parsing.ts +++ b/src/lib/analytics/compliance/parsing.ts @@ -37,7 +37,7 @@ function toDateOnDay(time: string, day: Date, tz?: string): Date { /** * Classify how punctual an intake was relative to a schedule window. * - * v1.4.34 IW-C — widened pre-window grace from 1h to 3h and introduced + * v1.4.34 — widened pre-window grace from 1h to 3h and introduced * a dedicated `early` bucket so a proactive logger (e.g. 10 min before * the window) no longer gets flushed into `very_late`. The post-window * grace likewise grew to 3h so a "20 minutes late" dose stays `on_time`. diff --git a/src/lib/analytics/compliance/types.ts b/src/lib/analytics/compliance/types.ts index 67d93ac6a..f4b298447 100644 --- a/src/lib/analytics/compliance/types.ts +++ b/src/lib/analytics/compliance/types.ts @@ -50,13 +50,13 @@ export interface ComplianceResult { export interface DailyComplianceEntry { expected: number; /** - * v1.7.0 item 5 — the true engine-computed due-slot count for the day. + * v1.7.0 — the true engine-computed due-slot count for the day. * Equals `expected`; carried as an explicit additive field iOS keys * off so it doesn't have to infer "due-ness" from `expected`. */ expectedCount: number; /** - * v1.7.0 item 5 — `expectedCount > 0`. iOS renders a "missed" mark + * v1.7.0 — `expectedCount > 0`. iOS renders a "missed" mark * only when `due === true`, so off-weeks / non-matching weekdays / PRN * days no longer paint a false miss. */ @@ -83,7 +83,7 @@ export interface ComplianceSchedule { windowEnd: string; // HH:mm daysOfWeek?: string | null; /** - * v1.7.0 SB-SCHED-2 — canonical-engine fields. When present (and a + * v1.7.0 — canonical-engine fields. When present (and a * `medicationContext` is threaded into `calculateCompliance`), the * expected-slot grid is computed through the canonical recurrence * engine, so an `rrule = "FREQ=WEEKLY;BYDAY=MO"` schedule counts only @@ -149,7 +149,7 @@ export const COURSES_COMPLIANCE_SELECT = { } as const; /** - * v1.7.0 SB-SCHED-2 — medication-level context the canonical engine + * v1.7.0 — medication-level context the canonical engine * needs to expand expected slots. When supplied, `calculateCompliance` * routes the denominator through the engine; when omitted, every * schedule falls back to the legacy weekday walker (the byte-stable @@ -170,7 +170,7 @@ export interface ComplianceMedicationContext { */ scheduleRevisions?: ScheduleRevisionLike[]; /** - * v1.25 H-MED1 — durable pause intervals (`MedicationPauseEra`). When + * v1.25 — durable pause intervals (`MedicationPauseEra`). When * present, expected dose slots whose anchor falls inside any * `[pausedAt, resumedAt ?? now)` interval are dropped from the ledger so a * resumed medication never counts the paused days as missed. Optional: @@ -226,7 +226,7 @@ export function slotInsideCourses( } /** - * v1.25 H-MED1 — the pause-interval projection the compliance engine reads. + * v1.25 — the pause-interval projection the compliance engine reads. * A Prisma `MedicationPauseEra` row (or a `{ pausedAt, resumedAt }` literal) * drops in. An open era (`resumedAt === null`) runs to `now`. */ @@ -236,7 +236,7 @@ export interface MedicationPauseEraLike { } /** - * v1.7.0 SB-SCHED-2 — convenience builder so the eight compliance call + * v1.7.0 — convenience builder so the eight compliance call * sites don't each re-spell the context shape. Pass the medication row * (any object carrying the course-window fields), the latest non-skipped * intake instant, and the user's timezone. @@ -249,7 +249,7 @@ export function buildComplianceMedicationContext( createdAt: Date; /** v1.16.3 — thread the archived eras when the bundle carries them. */ scheduleRevisions?: ScheduleRevisionLike[]; - /** v1.25 H-MED1 — thread the pause eras when the caller carries them. */ + /** v1.25 — thread the pause eras when the caller carries them. */ pauseEras?: MedicationPauseEraLike[]; /** v1.40 (#1024) — thread the courses when the caller carries them. */ courses?: CourseSpan[]; @@ -271,7 +271,7 @@ export function buildComplianceMedicationContext( } /** - * v1.7.0 SB-SCHED-2 — the latest non-skipped `takenAt` across an event + * v1.7.0 — the latest non-skipped `takenAt` across an event * list (rolling cadences re-anchor on it). Returns null when the user * has never logged a non-skipped intake. Order-independent, so the * caller can pass an events array in any sort order. diff --git a/src/lib/analytics/correlations-fast-path.ts b/src/lib/analytics/correlations-fast-path.ts index e3999a58b..80cee2633 100644 --- a/src/lib/analytics/correlations-fast-path.ts +++ b/src/lib/analytics/correlations-fast-path.ts @@ -1,5 +1,5 @@ /** - * v1.4.37 W2 — probe-gated correlation hypothesis runner. + * v1.4.37 — probe-gated correlation hypothesis runner. * * The three pre-defined hypotheses (BP × medication compliance, mood × * resting pulse, weight × weekday) need PAIRED day-level samples @@ -69,7 +69,7 @@ import { TRACKED_INTAKE_EVENT_WHERE } from "@/lib/medications/intake-tracking"; import { dayKeyAsUtcMidnight } from "@/lib/tz/date-only"; /** - * v1.4.37 W2 — cold-path correlation window. Trim from 30 to 28 days + * v1.4.37 — cold-path correlation window. Trim from 30 to 28 days * keeps the `n >= 20` Pearson gate comfortably reachable while shaving * two days off every read the runner issues. Kept as a constant so the * sentinel annotate and the SQL/Prisma WHERE clauses stay in lockstep. @@ -142,7 +142,7 @@ export async function computeCorrelationHypothesesFastPath( const coverage = input.coverage ?? (await probeRollupCoverage(userId)); - // v1.4.38 W-A — cross-tz runtime guard. The rollup table buckets at + // v1.4.38 — cross-tz runtime guard. The rollup table buckets at // UTC midnight, while the per-event mood / intake streams below are // re-keyed via `userDayKey(..., userTz)`. For a user inside the +-3h // band around UTC (Europe + western Mid-Atlantic), the two keying @@ -162,7 +162,7 @@ export async function computeCorrelationHypothesesFastPath( // logged, even when those were fully covered. Per-type coverage is the // correct gate. // - // v1.2.5 (M-CS2) — PULSE is no longer hydrated from the rollup tier: + // v1.2.5 — PULSE is no longer hydrated from the rollup tier: // the mood × "resting pulse" hypothesis now scores the RESTING series // (RESTING_HEART_RATE rows, else a low-percentile PULSE proxy from RAW // samples), not the day-mean PULSE the rollup carries. A day-mean folds @@ -241,7 +241,7 @@ export async function computeCorrelationHypothesesFastPath( weightRows = weightRowsLive; } - // v1.2.5 (M-CS2) — resolve the RESTING-pulse series the mood × pulse + // v1.2.5 — resolve the RESTING-pulse series the mood × pulse // hypothesis scores against. Prefer the clean RESTING_HEART_RATE rows; // only when the user has none do we pull RAW PULSE samples for the // low-percentile daily proxy (the proxy needs the per-day distribution, @@ -370,7 +370,7 @@ export async function computeCorrelationHypothesesFastPath( path: measurementsOnRollups ? "rollup" : "live", window_days: CORRELATION_WINDOW_DAYS, degraded: false, - // v1.4.38 W-A — surfaces the cross-tz guard decision so ops + // v1.4.38 — surfaces the cross-tz guard decision so ops // logs can distinguish "user is Berlin, rollup path eligible" // from "user is Honolulu, forced to live regardless of coverage". tz_guard: userNearUtc ? "near-utc" : "non-utc-live-fallback", diff --git a/src/lib/analytics/correlations.ts b/src/lib/analytics/correlations.ts index 6065af944..5c1536159 100644 --- a/src/lib/analytics/correlations.ts +++ b/src/lib/analytics/correlations.ts @@ -72,7 +72,7 @@ export function pairByTimestamp( } /** - * v1.2.5 (M-CS3) — significance-gated Pearson, the one correlation every + * v1.2.5 — significance-gated Pearson, the one correlation every * surface reports. * * A strength band read from |r| alone, above a floor of five pairs, let a diff --git a/src/lib/analytics/intraday-pulse-io.ts b/src/lib/analytics/intraday-pulse-io.ts index 30bdafdb7..b65018a22 100644 --- a/src/lib/analytics/intraday-pulse-io.ts +++ b/src/lib/analytics/intraday-pulse-io.ts @@ -1,5 +1,5 @@ /** - * S11 — IO seam for the intraday pulse / tension layer. + * IO seam for the intraday pulse / tension layer. * * Loads ONE local day's raw signals on demand (read-swap; never persisted as * 10-min rollups) and hands them to the pure `intraday-pulse` computations: diff --git a/src/lib/analytics/intraday-pulse.ts b/src/lib/analytics/intraday-pulse.ts index a47da1e7b..f4bb31f84 100644 --- a/src/lib/analytics/intraday-pulse.ts +++ b/src/lib/analytics/intraday-pulse.ts @@ -1,5 +1,5 @@ /** - * S11 — intraday pulse shape + the elevated-at-rest ("tension") window. + * Intraday pulse shape + the elevated-at-rest ("tension") window. * * Two deterministic, IO-free computations that reveal the SHAPE of a single * day's heart rate and, cautiously, whether a stretch of it looks like tension diff --git a/src/lib/analytics/resting-pulse.ts b/src/lib/analytics/resting-pulse.ts index ba26c914c..d81a8c87a 100644 --- a/src/lib/analytics/resting-pulse.ts +++ b/src/lib/analytics/resting-pulse.ts @@ -1,5 +1,5 @@ /** - * v1.15.12 A2 — resting-pulse estimation. + * v1.15.12 — resting-pulse estimation. * * Apple Health maps two distinct signals onto two distinct measurement * types: diff --git a/src/lib/analytics/schedule-anchored-compliance.ts b/src/lib/analytics/schedule-anchored-compliance.ts index 0889e9c08..bec832e63 100644 --- a/src/lib/analytics/schedule-anchored-compliance.ts +++ b/src/lib/analytics/schedule-anchored-compliance.ts @@ -1,5 +1,5 @@ /** - * v1.26.0 SEAM-N3 — the ONE schedule-anchored, cadence-aware daily + * v1.26.0 — the ONE schedule-anchored, cadence-aware daily * compliance computation. * * Extracted verbatim from the `GET /api/medications/intake?scope=compliance` @@ -69,7 +69,7 @@ export async function buildScheduleAnchoredComplianceBuckets( schedules: { select: SCHEDULE_COMPLIANCE_SELECT }, // v1.16.3 — archived schedule eras for era-aware expected counts. scheduleRevisions: { orderBy: { validFrom: "asc" } }, - // v1.25 H-MED1 — pause eras so paused days drop out of the denominator. + // v1.25 — pause eras so paused days drop out of the denominator. pauseEras: { select: { pausedAt: true, resumedAt: true } }, // v1.40 (#1024) — the courses, so a gap between two expects nothing. courses: COURSES_COMPLIANCE_SELECT, diff --git a/src/lib/analytics/source-priority.ts b/src/lib/analytics/source-priority.ts index b0748610b..e9f3cc1d4 100644 --- a/src/lib/analytics/source-priority.ts +++ b/src/lib/analytics/source-priority.ts @@ -1,5 +1,5 @@ /** - * v1.4.25 W5e — cross-source canonical-row picker. + * v1.4.25 — cross-source canonical-row picker. * * Cumulative metrics (steps, active energy, walking/running distance, * flights climbed, sleep duration) sum-per-day. When two sources both @@ -14,7 +14,7 @@ * audit trail — the user can flip the priority and the analytics * re-pick instantly without a re-sync. * - * v1.4.25 W8c — two-axis pick. After narrowing to the winning source, + * v1.4.25 — two-axis pick. After narrowing to the winning source, * the picker now consults a device-type ladder ("watch beats phone * beats scale") and keeps ONLY the rows from the top-ranked device- * type present in that day's bucket. Rationale: a user wearing an diff --git a/src/lib/analytics/source-rank-sql.ts b/src/lib/analytics/source-rank-sql.ts index a5f1940b2..109aada4f 100644 --- a/src/lib/analytics/source-rank-sql.ts +++ b/src/lib/analytics/source-rank-sql.ts @@ -47,7 +47,7 @@ export const RANKED_TYPES: readonly MeasurementType[] = [ "PULSE", "VO2_MAX", "RECOVERY_SCORE", - // v1.18.10 I-5 — STRESS_SCORE carries a source-priority ladder (`stress`) + // v1.18.10 — STRESS_SCORE carries a source-priority ladder (`stress`) // so the SQL collapse ranks a future device-native producer above the // COMPUTED proxy deterministically. No-op until a second producer lands. "STRESS_SCORE", diff --git a/src/lib/analytics/summaries-slice.ts b/src/lib/analytics/summaries-slice.ts index 4bc962cfc..f489baf44 100644 --- a/src/lib/analytics/summaries-slice.ts +++ b/src/lib/analytics/summaries-slice.ts @@ -115,7 +115,7 @@ import { canonicalRowsOfDay } from "@/lib/mood/linked-context"; const DAY_MS = 24 * 60 * 60 * 1000; /** - * v1.18.10 P-7 — upper bound on the cold-fallback `allTime` aggregate's + * v1.18.10 — upper bound on the cold-fallback `allTime` aggregate's * scan window. Sits far beyond any real account's history (the earliest prod * measurements are < 10 years old) so it never changes a real all-time * count / min / max / mean; it exists purely so a coverage-miss cold read @@ -207,14 +207,14 @@ interface NarrowAggregateRow { */ median: number | null; avg30_last_month: number | null; - // v1.20.0 F6 — slope7/30/90 + r2_7/30/90 have moved off this live query + // v1.20.0 — slope7/30/90 + r2_7/30/90 have moved off this live query // onto the rollup accumulators (composeWindowedRegression); they no longer // scan the raw measurements partition on the warm path. `median` stays // live (PERCENTILE_CONT is non-composable across buckets). } /** - * v1.20.0 F6 — cold-fallback windowed row. The coverage-miss path keeps the + * v1.20.0 — cold-fallback windowed row. The coverage-miss path keeps the * live REGR_* regression (no rollup accumulators to compose from yet), so it * carries the slope / r² columns the warm `NarrowAggregateRow` dropped. */ @@ -273,7 +273,7 @@ export interface SummariesSlice { */ bmi: null; /** - * v1.4.34 IW-B — per-type freshness map matching the default-slice + * v1.4.34 — per-type freshness map matching the default-slice * shape so the dashboard tile strip can render a "Letzter Wert vor * Xd" caption on stale tiles regardless of which slice the client * read from. Types the user has never logged report `null`. @@ -298,7 +298,7 @@ export interface SummariesSlice { export async function computeSummariesSlice( userId: string, /** - * v1.12.7 A5 — optional pre-probed coverage map. The dashboard + * v1.12.7 — optional pre-probed coverage map. The dashboard * snapshot already calls `probeRollupCoverage` up front to decide * whether to run the thick `extras` phase, then called this slice * which re-probed the identical query. Threading the map in lets the @@ -308,7 +308,7 @@ export async function computeSummariesSlice( */ precomputedCoverage?: RollupCoverageMap, ): Promise { - // v1.4.37.1 hotfix — fire-and-forget. See `src/app/api/analytics/route.ts` + // v1.4.37.1 — fire-and-forget. See `src/app/api/analytics/route.ts` // for the full rationale: awaiting this on the read path can stall // the event loop for tens of seconds on tenants with large iOS step // sample sets that keep the 90-day window slightly stale. The downstream @@ -317,7 +317,7 @@ export async function computeSummariesSlice( // for the background refresh. void ensureUserRollupsFresh(userId); - // v1.4.36 QA C1 — per-type coverage probe replaces the legacy global + // v1.4.36 — per-type coverage probe replaces the legacy global // COUNT. The previous gate returned true as soon as ANY type had at // least one DAY bucket. Pathology: a user with BP fully rolled up // logs their first WEIGHT measurement → the sync write-hook upserts @@ -590,7 +590,7 @@ async function computeFromRollups(userId: string): Promise { 'p."source"', ); - // v1.37.19 (A6-10) — where the fold window starts. DAY rollup buckets + // v1.37.19 — where the fold window starts. DAY rollup buckets // only exist inside the trailing `ROLLUP_FOLD_WINDOW_MS` (5 y), so the // rollup-composed "all-time" figures silently truncated to that window // for accounts with older history, while the cold live path reported @@ -635,7 +635,7 @@ async function computeFromRollups(userId: string): Promise { userId, ), readLatestCanonical(userId, priorityJson), - // v1.4.37.2 hotfix — the v1.4.35 implementation read EVERY DAY + // v1.4.37.2 — the v1.4.35 implementation read EVERY DAY // rollup bucket for the user (`findMany` without a `bucketStart` // window) and then composed `count / min / max / mean` in JS. // On tenants with large measurement partitions that materialised @@ -686,7 +686,7 @@ async function computeFromRollups(userId: string): Promise { userId, foldSplit, ), - // v1.20.0 F6 — trailing 90-day DAY rollup rows carrying the per-bucket + // v1.20.0 — trailing 90-day DAY rollup rows carrying the per-bucket // regression accumulators. The windowed slope / r² / sd compose from // these (composeWindowedRegression) instead of the live REGR_* scan the // narrow query used to run. Rows are per source; collapsed per type @@ -714,7 +714,7 @@ async function computeFromRollups(userId: string): Promise { sumYy: true, }, }), - // v1.37.19 (A6-10) — all-time remainder OLDER than the fold window, + // v1.37.19 — all-time remainder OLDER than the fold window, // spliced into the per-type figures below so "all-time" means all time // on the rollup path too (empty for any account younger than the // window). It keeps one source per (type, day) the way the rollup half @@ -797,7 +797,7 @@ async function computeFromRollups(userId: string): Promise { }); } - // v1.20.0 F6 — collapse the 90-day accumulator rows to the canonical + // v1.20.0 — collapse the 90-day accumulator rows to the canonical // source per (type, day), then build a per-type accumulator-bucket list so // the windowed slope / r² / sd compose from the canonical source only. const accBucketsByType = new Map(); @@ -837,7 +837,7 @@ async function computeFromRollups(userId: string): Promise { const since90 = startOfUtcDay(new Date(nowMs - 90 * DAY_MS)); const latestByType = new Map(); - // v1.4.34 IW-B — capture the per-type `measured_at` alongside the + // v1.4.34 — capture the per-type `measured_at` alongside the // value so we can surface the freshness map on the slim slice // without an extra round-trip. const lastSeenAtByType = new Map(); @@ -872,7 +872,7 @@ async function computeFromRollups(userId: string): Promise { }; } - // v1.4.37.2 hotfix — the rollup aggregate is now shaped server-side + // v1.4.37.2 — the rollup aggregate is now shaped server-side // (one row per type with count / min / max / mean already computed), // so we map straight through instead of partitioning + composing in // JS. The narrows query feeds the windowed avgs + slope columns the @@ -885,7 +885,7 @@ async function computeFromRollups(userId: string): Promise { let totalRows = 0; let typeCount = 0; const typesWithData: string[] = []; - // v1.20.0 P3 M-1 — set once any in-window bucket carries a NULL + // v1.20.0 — set once any in-window bucket carries a NULL // accumulator (pre-0190 row, or boot re-fold pending). The slope stays // null then — honest, it converges after backfill — but the miss must be // observable per the "no silent cap" rule, so it surfaces in the @@ -898,7 +898,7 @@ async function computeFromRollups(userId: string): Promise { typesWithData.push(row.type); const narrow = narrowByType.get(row.type); const latest = latestByType.get(row.type) ?? null; - // v1.20.0 F6 — windowed slope / r² compose from the per-bucket + // v1.20.0 — windowed slope / r² compose from the per-bucket // accumulators (canonical-source-collapsed above) rather than the live // REGR_* scan. A full miss (NULL accumulators pre-backfill, or < 2 // readings) yields a null slope, the same shape the empty REGR_* FILTER @@ -912,7 +912,7 @@ async function computeFromRollups(userId: string): Promise { } const reg7 = composeWindowedRegression(accBuckets, since7); const reg30 = composeWindowedRegression(accBuckets, since30); - // v1.37.19 (A6-10) — splice the pre-fold remainder into the all-time + // v1.37.19 — splice the pre-fold remainder into the all-time // figures: the rollup tier only covers the trailing fold window, so // count / min / max / mean composed from it alone truncated "all // time" to 5 y. Windowed fields (avg7/30, median, slopes) are @@ -946,7 +946,7 @@ async function computeFromRollups(userId: string): Promise { if (pre) totalRows += pre.count; } - // v1.37.19 (A6-10) — a type whose ONLY rows are older than the fold + // v1.37.19 — a type whose ONLY rows are older than the fold // window has no DAY bucket at all; without this arm it read as "never // logged" on the rollup path while the live path reported its history. for (const [type, pre] of preFoldByType) { @@ -993,13 +993,13 @@ async function computeFromRollups(userId: string): Promise { type_count: typeCount, path: "rollup", year_over_year_types: yearOverYearTypeCount, - // v1.20.0 F6 — slope / r² now compose from the rollup + // v1.20.0 — slope / r² now compose from the rollup // accumulators (no live REGR_* scan). The median stays live // (PERCENTILE_CONT is non-composable); both sources are logged // per the "no silent cap" rule so a regression is observable. slope_source: "rollup", median_source: "live", - // v1.20.0 P3 M-1 — when a NULL accumulator (pre-0190 row, or boot + // v1.20.0 — when a NULL accumulator (pre-0190 row, or boot // re-fold pending) forced a slope miss, the read intentionally // returns null without a live-SQL fallback (the design avoids the // heavy scan). Surface the gap so the silent null is observable; @@ -1272,7 +1272,7 @@ async function computeAvg30LastYearForType( } /** - * v1.4.40 W-WMY-WIRE — fan out `computeAvg30LastYearForType` across + * v1.4.40 — fan out `computeAvg30LastYearForType` across * the types the caller actually surfaces. Runs the per-type rollup reads * in parallel so the long-tail of types doesn't serialise into a * per-type round-trip stack. diff --git a/src/lib/analytics/trend-delta.ts b/src/lib/analytics/trend-delta.ts index d899dce71..99aed9140 100644 --- a/src/lib/analytics/trend-delta.ts +++ b/src/lib/analytics/trend-delta.ts @@ -14,7 +14,7 @@ * The trend-card then paints the delta with metric-aware sentiment * colour (up-good vs up-bad). * - * v1.4.16 Fix A4: fall back to slope30 (projected onto 7 days) when + * v1.4.16: fall back to slope30 (projected onto 7 days) when * slope7 is unavailable. Some metrics (mood, sleep) have <2 entries * within the trailing 7-day window for users who don't log daily, * which used to surface as a missing delta on the dashboard tile — diff --git a/src/lib/analytics/trends.ts b/src/lib/analytics/trends.ts index 08b17d4d7..c137b62d3 100644 --- a/src/lib/analytics/trends.ts +++ b/src/lib/analytics/trends.ts @@ -153,14 +153,14 @@ export interface DataSummary { */ anomalyCount?: number; /** - * v1.4.16 phase B8 — average value over the 30-day window starting + * v1.4.16 — average value over the 30-day window starting * 30 days before today, i.e. the "last month" prior period the * dashboard tile delta callout compares against. Null when the * window has no data. */ avg30LastMonth?: number | null; /** - * v1.4.16 phase B8 — average value over the 30-day window starting + * v1.4.16 — average value over the 30-day window starting * 365 days before today (one calendar year ago). Null when the * window has no data. */ @@ -191,7 +191,7 @@ export function summarize(data: DataPoint[]): DataSummary { const sorted = [...data].sort((a, b) => a.date.getTime() - b.date.getTime()); const values = sorted.map((p) => p.value); - // v1.4.33 P0 — single-pass fold for sum/min/max replaces the previous + // v1.4.33 — single-pass fold for sum/min/max replaces the previous // `Math.min(...values)` / `Math.max(...values)` spread. V8 caps // function arity at ~125k-130k; Apple-Health-synced PULSE series for // a multi-year power user routinely exceed that, and the spread @@ -232,7 +232,7 @@ export function summarize(data: DataPoint[]): DataSummary { const last7 = sorted.filter((p) => now - p.date.getTime() < 7 * DAY); const last30 = sorted.filter((p) => now - p.date.getTime() < 30 * DAY); - // v1.4.16 phase B8 — prior-period 30-day means powering the tile + // v1.4.16 — prior-period 30-day means powering the tile // delta callout. The lastMonth window is days [30, 60) ago; the // lastYear window is days [365, 395) ago. Both align to the 30-day // shift the chart overlay uses so the tile delta and the chart diff --git a/src/lib/analytics/window-confidence.ts b/src/lib/analytics/window-confidence.ts index d49947158..e50b5e3ba 100644 --- a/src/lib/analytics/window-confidence.ts +++ b/src/lib/analytics/window-confidence.ts @@ -56,7 +56,7 @@ export interface WindowConfidence { } /** - * v1.17 W1b — derive the BD-Zielbereich tile's confidence inputs (90-day + * v1.17 — derive the BD-Zielbereich tile's confidence inputs (90-day * pair count + effective label span) from a BP envelope's 90-day window. * Shared by the dashboard snapshot and the `/api/analytics` thick slice so * the two surfaces can never compute the tile's gate or span differently. diff --git a/src/lib/analytics/window-trend.ts b/src/lib/analytics/window-trend.ts index 5c24f3e1d..da791cfea 100644 --- a/src/lib/analytics/window-trend.ts +++ b/src/lib/analytics/window-trend.ts @@ -12,7 +12,7 @@ * for long ("All") ranges where the per-week rate * rounds to ±0 in the 1-decimal formatter. * - * v1.4.16 Fix A8b — the maintainer reported the chart trend overlay reads + * v1.4.16 — the maintainer reported the chart trend overlay reads * "+0.0 kg/Woche" on the All filter, even when their weight clearly * shifted across years. Root cause: `weeklyDelta = (last − first) / days * × 7` is mathematically correct but visually rounds to zero whenever diff --git a/src/lib/api-handler.ts b/src/lib/api-handler.ts index cc808dce7..564bba265 100644 --- a/src/lib/api-handler.ts +++ b/src/lib/api-handler.ts @@ -170,7 +170,7 @@ function safeRequestProp( return read(request as NextRequest); } catch (err) { if (isTolerableRequestProbeError(err)) { - // v1.4.27 B7 / BL-P1-3 — surface every fallback so a real read + // v1.4.27 — surface every fallback so a real read // regression cannot hide behind the tolerated-error narrowing. // Vitest direct-invoke and the force-static placeholder path are // the two known-quiet shapes; anything else worth a look should diff --git a/src/lib/api-response.ts b/src/lib/api-response.ts index e10bb4cec..03b3e60c0 100644 --- a/src/lib/api-response.ts +++ b/src/lib/api-response.ts @@ -106,7 +106,7 @@ export interface SanitiseZodIssueOptions { } /** - * v1.4.42 W2 — multi-issue Zod error envelope. + * v1.4.42 — multi-issue Zod error envelope. * * Historic pattern was `apiError(parsed.error.issues[0].message, 422)` * which dropped every issue past the first. The iOS contract debug @@ -291,7 +291,7 @@ export function apiValidationError( * via `t("settings.testConnection.errors." + errorCode)` instead of * displaying the server's English fallback. * - * v1.4.25 W21 Fix-N — the third argument may also carry a `headers` + * v1.4.25 — the third argument may also carry a `headers` * record (e.g. `{ headers: rateLimitHeaders(rl) }`) so 429 responses * can attach the `X-RateLimit-*` triple in the same call. The `headers` * key is *not* echoed into the JSON body — it is consumed by the diff --git a/src/lib/api/__tests__/optimistic-token.test.ts b/src/lib/api/__tests__/optimistic-token.test.ts index 4a7ae0ce3..96bdd35a8 100644 --- a/src/lib/api/__tests__/optimistic-token.test.ts +++ b/src/lib/api/__tests__/optimistic-token.test.ts @@ -1,5 +1,5 @@ /** - * v1.32.21 (R5a) — the client-side optimistic-concurrency primitives every + * v1.32.21 — the client-side optimistic-concurrency primitives every * guarded writer uses (`readUpdatedAtToken`, `withBaseToken`, `isConflict`), * plus the B-3 token-preservation guarantee for the insights-layout read. */ diff --git a/src/lib/api/read-error.ts b/src/lib/api/read-error.ts index 4e95f3026..0898bc87e 100644 --- a/src/lib/api/read-error.ts +++ b/src/lib/api/read-error.ts @@ -1,5 +1,5 @@ /** - * v1.4.25 W21 Fix-N — shared client-side error reader. + * v1.4.25 — shared client-side error reader. * * The four onboarding components (WelcomeCarousel, GoalsChipPicker, * SourceCardGrid, BaselineForm) all hand-rolled the same helper to diff --git a/src/lib/auth/__tests__/passkey.test.ts b/src/lib/auth/__tests__/passkey.test.ts index e4877a0e9..8f5f734d3 100644 --- a/src/lib/auth/__tests__/passkey.test.ts +++ b/src/lib/auth/__tests__/passkey.test.ts @@ -1,5 +1,5 @@ /** - * v1.4.43 W13 L-3 — Zod-narrowed `verifyAuthentication` boundary. + * v1.4.43 — Zod-narrowed `verifyAuthentication` boundary. * * Before the fix, the route passed a parsed JSON body through a raw * `as AuthenticationResponseJSON` cast straight into diff --git a/src/lib/auth/passkey.ts b/src/lib/auth/passkey.ts index cc7b7a5a3..72dc2a577 100644 --- a/src/lib/auth/passkey.ts +++ b/src/lib/auth/passkey.ts @@ -19,7 +19,7 @@ import { } from "@/lib/auth/webauthn-rp"; import { hashToken } from "@/lib/auth/hmac"; -// v1.4.43 W13 L-3 — explicit Zod narrowing in front of +// v1.4.43 — explicit Zod narrowing in front of // `verifyAuthentication`. Replaces the raw // `as AuthenticationResponseJSON` cast; the SimpleWebAuthn verifier // still owns the cryptographic validation downstream, but a @@ -351,7 +351,7 @@ export async function verifyAuthentication( ); } - // v1.4.43 W13 L-3 — explicit Zod narrowing instead of the previous + // v1.4.43 — explicit Zod narrowing instead of the previous // raw `as AuthenticationResponseJSON` cast. A malformed body now // throws here with a structured error rather than crashing on a // follow-up `.id` deref. The SimpleWebAuthn verifier downstream diff --git a/src/lib/auth/session.ts b/src/lib/auth/session.ts index aa08c5d00..5d2d53640 100644 --- a/src/lib/auth/session.ts +++ b/src/lib/auth/session.ts @@ -167,7 +167,7 @@ export async function validateSessionWithCreatedAt( const LAST_ACTIVE_THROTTLE_MS = 5 * 60 * 1000; // 5 minutes /** - * v1.4.22 C4 — flag cookie mirroring the user's `onboardingCompletedAt` + * v1.4.22 — flag cookie mirroring the user's `onboardingCompletedAt` * status. Set to `"pending"` while the field is null and cleared once * onboarding completes. The proxy reads this cookie to short-circuit * the post-hydration redirect that previously caused a dashboard flash @@ -188,7 +188,7 @@ export async function setOnboardingPendingCookie( cookieStore.set(ONBOARDING_COOKIE, "pending", { httpOnly: false, secure: shouldEmitSecureCookie(), - // v1.4.22 W5 reconcile (Sec-MED-1) — Strict (not Lax) because + // v1.4.22 — Strict (not Lax) because // no cross-site redirect flow ever depends on this cookie. The // sibling `healthlog_session` cookie stays Lax because the // Withings OAuth callback arrives via top-level cross-site @@ -203,7 +203,7 @@ export async function setOnboardingPendingCookie( } /** - * v1.4.22 W5 reconcile (Sr-H1) — `onboardingPending` is required + * v1.4.22 — `onboardingPending` is required * (not optional) so issuing a session without anchoring the * onboarding cookie is type-impossible. Every auth surface (login, * passkey-verify, register, password-reset) must thread the user's diff --git a/src/lib/cache/__tests__/invalidate.test.ts b/src/lib/cache/__tests__/invalidate.test.ts index 476145139..9823f5a07 100644 --- a/src/lib/cache/__tests__/invalidate.test.ts +++ b/src/lib/cache/__tests__/invalidate.test.ts @@ -89,7 +89,7 @@ async function primeAllCaches(): Promise { await cached(caches.dashboardWidgets, USER_A, async () => ({ d: 1 })); await cached(caches.dashboardWidgets, USER_B, async () => ({ d: 2 })); - // v1.7.0 W6 — the unified dashboard snapshot lives under the analytics + // v1.7.0 — the unified dashboard snapshot lives under the analytics // bucket. Since v1.21.2 the real cell is keyed WITH the resolved locale // (`${userId}|dashboard-snapshot|${locale}`) because the briefing prose is // locale-specific. Seed the locale-suffixed cell (the production shape) so @@ -148,7 +148,7 @@ describe("invalidateUserMeasurements", () => { expect(caches.analytics.get(`${USER_A}|default`)).toBeNull(); expect(caches.analytics.get(`${USER_A}|summaries`)).toBeNull(); - // v1.7.0 W6 — the `${userId}|` prefix sweep covers the snapshot key. + // v1.7.0 — the `${userId}|` prefix sweep covers the snapshot key. expect(caches.analytics.get(snapshotCell(USER_A))).toBeNull(); expect(caches.achievements.get(USER_A)).toBeNull(); expect(caches.workouts.get(`${USER_A}|3|0||`)).toBeNull(); @@ -253,7 +253,7 @@ describe("invalidateUserMedications", () => { expect(caches.medications.get(USER_A)).toBeNull(); expect(caches.medicationsIntake.get(`${USER_A}|compliance|30`)).toBeNull(); expect(caches.achievements.get(USER_A)).toBeNull(); - // v1.7.0 W6 — medication writes sweep the analytics bucket, which + // v1.7.0 — medication writes sweep the analytics bucket, which // covers the snapshot key. expect(caches.analytics.get(snapshotCell(USER_A))).toBeNull(); expect(caches.medications.get(USER_B)).not.toBeNull(); @@ -333,7 +333,7 @@ describe("invalidateUserDashboardWidgets", () => { invalidateUserDashboardWidgets(USER_A); expect(caches.dashboardWidgets.get(USER_A)).toBeNull(); expect(caches.dashboardWidgets.get(USER_B)).not.toBeNull(); - // v1.7.0 W6 — the layout rides inside the snapshot now, so a tile + // v1.7.0 — the layout rides inside the snapshot now, so a tile // reorder must drop the snapshot too. The widget invalidator does // NOT sweep the whole analytics bucket, so the slim/default cells // stay warm — only the point-keyed snapshot is dropped. diff --git a/src/lib/cache/invalidate.ts b/src/lib/cache/invalidate.ts index bb16d8dd3..9ecf5ad9d 100644 --- a/src/lib/cache/invalidate.ts +++ b/src/lib/cache/invalidate.ts @@ -1,5 +1,5 @@ /** - * v1.4.34 IW-G — per-user cache invalidation helpers. + * v1.4.34 — per-user cache invalidation helpers. * * Each write endpoint that touches a measurement / mood / medication / * dashboard-widget row calls the matching helper after its successful @@ -23,7 +23,7 @@ import { forgetCoverageProbe } from "@/lib/rollups/coverage-inflight"; import { caches } from "./server-cache"; /** - * v1.7.0 W6 — server cache key for the unified dashboard first-paint + * v1.7.0 — server cache key for the unified dashboard first-paint * snapshot. Lives under the analytics cache bucket so the `${userId}|` * sweep on a measurement / mood / medication write already covers it * (measurement writes mark it stale; mood / medication writes hard-evict @@ -98,7 +98,7 @@ export function invalidateUserMeasurements( } caches.achievements.deleteByPrefix(userId); caches.workouts.deleteByPrefix(`${userId}|`); - // v1.4.36 W1 — measurement writes change the per-target consistency + // v1.4.36 — measurement writes change the per-target consistency // strips, the in-range rates and the streak counters that // `/api/insights/targets` computes. // @@ -134,7 +134,7 @@ export function invalidateUserMood(userId: string): void { caches.moodAnalytics.deleteByPrefix(userId); caches.achievements.deleteByPrefix(userId); caches.analytics.deleteByPrefix(`${userId}|`); - // v1.4.36 W1 — mood writes change the mood target rows on the + // v1.4.36 — mood writes change the mood target rows on the // insights/targets response (MOOD_SCORE, MOOD_STABILITY). caches.insightsTargets.deleteByPrefix(userId); // v1.8.5 — mood writes dirty every dimension of the mood-insights @@ -182,7 +182,7 @@ export function invalidateUserMedications( // so the card rates and the heatmap reflect the action on the next // read. caches.medicationCompliance.deleteByPrefix(`${userId}|`); - // v1.4.38 W-F — `/api/dashboard/summary` lives under the analytics + // v1.4.38 — `/api/dashboard/summary` lives under the analytics // cache and reads `medicationIntakeEvent` for both today's compliance // tally and the 365-day streak feed. A taken / skipped event must // evict the user-bucket so the next iOS poll reflects the change. @@ -199,7 +199,7 @@ export function invalidateUserMedications( } caches.medicationsIntake.deleteByPrefix(`${userId}|`); caches.achievements.deleteByPrefix(userId); - // v1.4.36 W1 — medication writes change the MEDICATION_COMPLIANCE + // v1.4.36 — medication writes change the MEDICATION_COMPLIANCE // target rollup (compliance7 / compliance30 / consistency strip). caches.insightsTargets.deleteByPrefix(userId); } @@ -248,7 +248,7 @@ export function invalidateUserProfile(userId: string): void { */ export function invalidateUserDashboardWidgets(userId: string): void { caches.dashboardWidgets.deleteByPrefix(userId); - // v1.7.0 W6 — the layout rides inside the unified snapshot now, so a + // v1.7.0 — the layout rides inside the unified snapshot now, so a // tile reorder / visibility toggle must also drop the snapshot. The // widget invalidator does NOT touch the analytics bucket, so the // prefix sweep above does not cover the snapshot key — drop it diff --git a/src/lib/cache/server-cache.ts b/src/lib/cache/server-cache.ts index 64447d7dc..22bafff84 100644 --- a/src/lib/cache/server-cache.ts +++ b/src/lib/cache/server-cache.ts @@ -1,5 +1,5 @@ /** - * v1.4.34 IW-G — in-process LRU + single-flight server cache. + * v1.4.34 — in-process LRU + single-flight server cache. * * Generalises the v1.4.33 Coach snapshot cache shape * (`src/lib/ai/coach/snapshot.ts:292-336`, commit `af17db5d`) into a @@ -535,7 +535,7 @@ function createServerCaches() { staleTtlMs: 600_000, }), /** - * v1.18.11 (W5 perf) — stale-while-revalidate window. The + * v1.18.11 — stale-while-revalidate window. The * `AchievementUnlockNotifier` is mounted on every authenticated page and * polls this endpoint every 2 minutes. Against a 60 s hard-TTL bucket * that poll always landed on an expired entry, firing a cold ~400 ms @@ -637,7 +637,7 @@ function createServerCaches() { staleTtlMs: 600_000, }), /** - * v1.4.36 W1 — `/api/insights/targets` is read on every Insights + * v1.4.36 — `/api/insights/targets` is read on every Insights * mount and runs >1 s cold (it pulls 30-day measurements for every * type + medications + intakes + mood + glucose). 60 s TTL matches * the analytics cache so multiple sub-page mounts inside a minute diff --git a/src/lib/charts/__tests__/comparison-shift-edge-cases.test.ts b/src/lib/charts/__tests__/comparison-shift-edge-cases.test.ts index 42262eb87..223450d89 100644 --- a/src/lib/charts/__tests__/comparison-shift-edge-cases.test.ts +++ b/src/lib/charts/__tests__/comparison-shift-edge-cases.test.ts @@ -6,7 +6,7 @@ import { } from "@/lib/charts/comparison-shift"; /** - * v1.4.25 W6 — edge-case end-to-end pinning for the `comparisonBaseline` + * v1.4.25 — edge-case end-to-end pinning for the `comparisonBaseline` * overlay. * * The basic shift suite (`comparison-shift.test.ts`) already pins the diff --git a/src/lib/charts/__tests__/comparison-shift.test.ts b/src/lib/charts/__tests__/comparison-shift.test.ts index 9cafbdf02..4badecbc9 100644 --- a/src/lib/charts/__tests__/comparison-shift.test.ts +++ b/src/lib/charts/__tests__/comparison-shift.test.ts @@ -9,7 +9,7 @@ import { const MS_PER_DAY = 24 * 60 * 60 * 1000; /** - * v1.4.16 phase B8 — comparison shift unit tests. + * v1.4.16 — comparison shift unit tests. * * The shift is integer-day forward addition; the helper does not care * about timezones because every input timestamp is already anchored at diff --git a/src/lib/charts/comparison-shift.ts b/src/lib/charts/comparison-shift.ts index 4a2340a15..4b262a6b9 100644 --- a/src/lib/charts/comparison-shift.ts +++ b/src/lib/charts/comparison-shift.ts @@ -1,5 +1,5 @@ /** - * v1.4.16 phase B8 — comparison overlay helper. + * v1.4.16 — comparison overlay helper. * * The dashboard charts already fetch every measurement for the active * type set (paged through `/api/measurements`). When the comparison diff --git a/src/lib/clinician-share/__tests__/resolve-share-token.test.ts b/src/lib/clinician-share/__tests__/resolve-share-token.test.ts index c7252dd65..a2e416cd1 100644 --- a/src/lib/clinician-share/__tests__/resolve-share-token.test.ts +++ b/src/lib/clinician-share/__tests__/resolve-share-token.test.ts @@ -1,5 +1,5 @@ /** - * v1.11.0 (Epic C, C3) — share-token resolver, the security core. + * v1.11.0 — share-token resolver, the security core. * * Asserts the load-bearing properties: * - a valid, live token resolves to a ShareContext carrying ONLY the owner diff --git a/src/lib/clinician-share/__tests__/share-token-reject-on-authed-route.test.ts b/src/lib/clinician-share/__tests__/share-token-reject-on-authed-route.test.ts index ab14c5285..bfe22ca7b 100644 --- a/src/lib/clinician-share/__tests__/share-token-reject-on-authed-route.test.ts +++ b/src/lib/clinician-share/__tests__/share-token-reject-on-authed-route.test.ts @@ -1,5 +1,5 @@ /** - * v1.11.0 (Epic C, C3) — CRITICAL INVARIANT. + * v1.11.0 — CRITICAL INVARIANT. * * A share token can authenticate ONLY the public share surface. Presented on a * normal authenticated route it must be REJECTED: `requireAuth` reads only the diff --git a/src/lib/clinician-share/__tests__/share-view-data.test.ts b/src/lib/clinician-share/__tests__/share-view-data.test.ts index b1535f6e0..7a97cee91 100644 --- a/src/lib/clinician-share/__tests__/share-view-data.test.ts +++ b/src/lib/clinician-share/__tests__/share-view-data.test.ts @@ -1,5 +1,5 @@ /** - * v1.11.0 (Epic C, C8) — clinician-view scoped data load guarantees. + * v1.11.0 — clinician-view scoped data load guarantees. * * The public clinician view aggregates ONLY the data the owner froze into the * link, and it must NEVER surface the insurance number (KVNR). KVNR is diff --git a/src/lib/consent/receipts.ts b/src/lib/consent/receipts.ts index e9c6827a6..a45b95818 100644 --- a/src/lib/consent/receipts.ts +++ b/src/lib/consent/receipts.ts @@ -1,5 +1,5 @@ /** - * v1.4.40 SB-10 — AI consent receipts CRUD helper. + * v1.4.40 — AI consent receipts CRUD helper. * * Append-only audit trail. Every grant + revoke mints a fresh row; * no updates, no deletes outside of cascade-on-user-delete. A diff --git a/src/lib/crypto/encrypted-columns.ts b/src/lib/crypto/encrypted-columns.ts index 12e8bd000..0b1fa378c 100644 --- a/src/lib/crypto/encrypted-columns.ts +++ b/src/lib/crypto/encrypted-columns.ts @@ -266,13 +266,13 @@ export const ENCRYPTED_COLUMNS: readonly EncryptedColumn[] = [ // v1.39.3 — the title: the opening words of the first message, or a rename. { model: "CoachConversation", field: "titleEncrypted", kind: "bytes" }, { model: "CoachFact", field: "factEncrypted", kind: "bytes" }, - // v1.21.3 (B1) — Coach goal / if-then plan free-text columns. + // v1.21.3 — Coach goal / if-then plan free-text columns. { model: "CoachPlan", field: "ifCueEncrypted", kind: "bytes" }, { model: "CoachPlan", field: "thenActionEncrypted", kind: "bytes" }, { model: "CoachPlan", field: "targetEncrypted", kind: "bytes" }, - // v1.22 (W9, C2) — n-of-1 experiment outcome read-back prose. + // v1.22 — n-of-1 experiment outcome read-back prose. { model: "CoachPlan", field: "outcomeEncrypted", kind: "bytes" }, - // v1.22 (B2/B6) — Coach episodic reminder free-text note. + // v1.22 — Coach episodic reminder free-text note. { model: "CoachReminder", field: "noteEncrypted", kind: "bytes" }, // ───── User health profile (Bytes columns) ───── diff --git a/src/lib/daily/__tests__/daily-briefing-push.test.ts b/src/lib/daily/__tests__/daily-briefing-push.test.ts index 1bffd2983..9092e46f4 100644 --- a/src/lib/daily/__tests__/daily-briefing-push.test.ts +++ b/src/lib/daily/__tests__/daily-briefing-push.test.ts @@ -1,5 +1,5 @@ /** - * S5 — daily-briefing push decision seam + composer. + * Daily-briefing push decision seam + composer. * * Pins the once-per-day, opt-in-only, morning-window, non-alert contract of * `maybeDispatchDailyBriefing` and the floor/lead composition of diff --git a/src/lib/daily/daily-briefing-push.ts b/src/lib/daily/daily-briefing-push.ts index 5e9c2eea2..de858255f 100644 --- a/src/lib/daily/daily-briefing-push.ts +++ b/src/lib/daily/daily-briefing-push.ts @@ -1,5 +1,5 @@ /** - * S5 — the daily briefing push (§2.2). + * The daily briefing push (§2.2). * * A CALM, once-per-day morning nudge that rides the EXISTING notification * cascade (`dispatchNotification` → APNs → Telegram → ntfy → Webhook → Email → diff --git a/src/lib/daily/digest.ts b/src/lib/daily/digest.ts index 178889c87..e3e2f1392 100644 --- a/src/lib/daily/digest.ts +++ b/src/lib/daily/digest.ts @@ -1,5 +1,5 @@ /** - * P3 — `buildDailyDigest`, the ONE data spine of the daily-value system. + * `buildDailyDigest`, the ONE data spine of the daily-value system. * * A pure, deterministic composer that assembles the day's read from data that * ALREADY EXISTS — the nightly `insight-pregenerate` output lifted read-only @@ -201,7 +201,7 @@ export interface DailyDigestUpcomingVisit { } /** - * S11 — a confident elevated-at-rest ("tension") window for the day, already + * A confident elevated-at-rest ("tension") window for the day, already * detected server-side (`loadIntradayPulse`) under its full confidence gate. * The builder only formats it; the signal correctness lives in the analytics * layer, and a null here means the honest "no window" — nothing is emitted. @@ -212,7 +212,7 @@ export interface DailyDigestTensionWindow { } /** - * S10 — the latest ECG recording, for the `ecg_new_recording` rail item. Only + * The latest ECG recording, for the `ecg_new_recording` rail item. Only * ever the DEVICE's verdict + when it was recorded — NEVER the waveform (the * DTO carries no samples). The builder decides "new" from `recordedAt`; the * card attributes any verdict to the recording device. @@ -358,13 +358,13 @@ export interface DailyDigestInput { /** Standing coach plans (active + reviewed) — check-in candidates (§2.3). */ coachPlans: DailyDigestCoachPlan[]; /** - * S12 — the single freshly-reached durable milestone for today, or null. The + * The single freshly-reached durable milestone for today, or null. The * IO seam gathers candidates from the existing streak / personal-record * engines and applies the reached-once gate (`selectFreshMilestone`), so the * builder only ever sees a milestone worth celebrating today. */ milestone?: Milestone | null; - /** S11 — the day's detected elevated-at-rest window, or null (honest-absent). */ + /** The day's detected elevated-at-rest window, or null (honest-absent). */ tensionWindow: DailyDigestTensionWindow | null; /** * v1.34.0 — today's cumulative-activity read against this person's own @@ -374,7 +374,7 @@ export interface DailyDigestInput { */ sameTime?: DailyDigestSameTime | null; /** - * S10 — the freshest ECG recording (device verdict + recordedAt only), or + * The freshest ECG recording (device verdict + recordedAt only), or * null. Optional so consumers that predate the ECG weave stay valid; the * builder treats a missing value as "no recent recording". */ @@ -753,7 +753,7 @@ function capWithPinned( } /** - * S12 — the calm reward card. Emits ONE `milestone` PriorityItem when a durable + * The calm reward card. Emits ONE `milestone` PriorityItem when a durable * state was reached TODAY (the IO seam already applied the reached-once gate). * Data, not AI: no module gate beyond the metric's own (applied by the IO * seam). Celebratory-but-quiet: `success` status, a single "view" action into the @@ -784,7 +784,7 @@ function buildMilestoneItem( } /** - * S11 — the elevated-at-rest ("tension") card. Emitted at most once per day + * The elevated-at-rest ("tension") card. Emitted at most once per day * (the window is already the day's single most confident stretch), gated on * a non-null window (the analytics layer stays silent unless every confidence * gate holds). Pulse is a core vital, so no module gates it. Cautious, non-diagnostic copy: @@ -955,7 +955,7 @@ function buildCoachCheckinItem( } /** - * S10 — how recently an ECG recording must have landed to count as "new" for + * How recently an ECG recording must have landed to count as "new" for * the rail (§3.5.3). A calendar-day window: a recording synced within the last * day surfaces once, then retires on its own (no persisted "seen" marker, no * migration). The 24h window is itself the one/day cap — at most one recording @@ -1199,7 +1199,7 @@ export function buildDailyDigest( visits.today, ].filter((item): item is PriorityItem => item !== null), ); - // S11 — the calm, informational tension marker sits last: it is context, not + // The calm, informational tension marker sits last: it is context, not // an action that expires, so a time-sensitive dose / sync / check-in wins the // bounded rail ahead of it. const tension = buildTensionWindowItem( diff --git a/src/lib/daily/load-digest.ts b/src/lib/daily/load-digest.ts index 52db327b0..0d4d9dcad 100644 --- a/src/lib/daily/load-digest.ts +++ b/src/lib/daily/load-digest.ts @@ -132,7 +132,7 @@ const MILESTONE_STREAK_WINDOW_DAYS = 30; const MILESTONE_RECORD_LOOKBACK_MS = 2 * 24 * 60 * 60 * 1000; /** - * S12 — gather today's single freshly-reached milestone (or null) from the + * Gather today's single freshly-reached milestone (or null) from the * engines that ALREADY exist. Two cheap, fail-soft reads reused verbatim: * - `detectStreak` over the salient vitals' day-mean series (the exact * `score-narrative` pattern) → return-to-range + sustained-in-range states; @@ -397,7 +397,7 @@ async function loadDailyDigestExtrasCached( caches.analytics as ServerCache, digestExtrasCacheKey(userId, todayLocalDate), async () => { - // S11 / S12 — independent reads, hoisted into one Promise.all (a prior + // Independent reads, hoisted into one Promise.all (a prior // revision ran them sequentially). Each is already fault-isolated // internally (a milestone read-hiccup or a tension-read failure leaves // its own field quiet rather than breaking the other). @@ -446,7 +446,7 @@ async function loadDailyDigestExtrasCached( ); } -/** S10 — how recently an ECG recording counts as "new" for the rail read. */ +/** How recently an ECG recording counts as "new" for the rail read. */ const ECG_NEW_WINDOW_MS = 24 * 60 * 60 * 1000; interface CoachPlanRow { @@ -637,7 +637,7 @@ export async function loadDailyDigest( orderBy: { updatedAt: "desc" }, take: CHECKIN_PLAN_READ_LIMIT, }), - // S10 — the freshest ECG recording within the last-day window, for the + // The freshest ECG recording within the last-day window, for the // `ecg_new_recording` rail item. Descriptors only (verdict + recordedAt) — // the encrypted waveform is never selected, so no decrypt and no trace // crosses into the digest. The builder gates on the `insights` module. @@ -809,7 +809,7 @@ export async function loadDailyDigest( } : null; - // S10 — the freshest ECG recording (device verdict + recordedAt only). The + // The freshest ECG recording (device verdict + recordedAt only). The // builder decides "new" and gates on the `insights` module. An ECG row only // ever carries an AFib-screening verdict; the shared enum's other members // (walking-steadiness / event codes) never apply, so anything else maps to @@ -833,7 +833,7 @@ export async function loadDailyDigest( ? planRows.map((row) => toCoachPlanCandidate(row as CoachPlanRow)) : []; - // S12 — the calm reward layer. S11 — the day's elevated-at-rest window, + // The calm reward layer. S11 — the day's elevated-at-rest window, // computed on demand from raw for TODAY only (read-swap, one bounded // day-read; never persisted), fault-isolated: a tension-read failure must // never break the digest (a hot, must-not-fail path), it just omits the calm diff --git a/src/lib/daily/milestones.ts b/src/lib/daily/milestones.ts index c6df8e160..f8d21a516 100644 --- a/src/lib/daily/milestones.ts +++ b/src/lib/daily/milestones.ts @@ -1,5 +1,5 @@ /** - * P2 — `Milestone`, the durable-states reward primitive. + * `Milestone`, the durable-states reward primitive. * * A milestone is "a state REACHED" — a durable achievement the record now * carries: a metric that settled back inside the user's own range, a run of diff --git a/src/lib/daily/morning-refresh-trigger.ts b/src/lib/daily/morning-refresh-trigger.ts index e62402931..dce843215 100644 --- a/src/lib/daily/morning-refresh-trigger.ts +++ b/src/lib/daily/morning-refresh-trigger.ts @@ -1,5 +1,5 @@ /** - * S4 — the sleep-arrival trigger for the morning digest refresh. + * The sleep-arrival trigger for the morning digest refresh. * * The nightly insight pre-pass (`insight-pregenerate`, 04:30) runs before last * night's sleep has synced, so the morning digest + health score reference the diff --git a/src/lib/daily/priority-item.ts b/src/lib/daily/priority-item.ts index fda13e6f5..286b8d214 100644 --- a/src/lib/daily/priority-item.ts +++ b/src/lib/daily/priority-item.ts @@ -1,5 +1,5 @@ /** - * P1 — `PriorityItem`, the ONE typed model behind every "worth a look" item. + * `PriorityItem`, the ONE typed model behind every "worth a look" item. * * A `PriorityItem` is the single rendering unit for every rail entry the * unified daily-value system produces: a coach check-in, a dose-window CTA, a diff --git a/src/lib/dashboard-layout.ts b/src/lib/dashboard-layout.ts index 0e13c86fd..62be34c74 100644 --- a/src/lib/dashboard-layout.ts +++ b/src/lib/dashboard-layout.ts @@ -44,7 +44,7 @@ export const DASHBOARD_WIDGET_IDS = [ "hrv", "respiratoryRate", "achievements", - // v1.4.25 W8d — VO2 max trend tile (secondary-metric pattern). The + // v1.4.25 — VO2 max trend tile (secondary-metric pattern). The // strip tile is on by default and self-gates on having any VO2_MAX // sample, so accounts that sync cardio fitness see it without enabling // it in Settings → Dashboard, while empty accounts get a clean tile @@ -58,7 +58,7 @@ export const DASHBOARD_WIDGET_IDS = [ // cue is discoverable from the dashboard rather than hidden // behind Settings. "recentWorkouts", - // v1.11.2 B5 — the v1.10 additive HealthKit signals become pinnable. + // v1.11.2 — the v1.10 additive HealthKit signals become pinnable. // Each maps 1:1 to a MeasurementType the server already stores; // default-invisible so existing dashboards stay unchanged until the // user opts in via Settings → Dashboard. @@ -85,7 +85,7 @@ export const DASHBOARD_WIDGET_IDS = [ export type DashboardWidgetId = (typeof DASHBOARD_WIDGET_IDS)[number]; /** - * v1.11.2 B5 / HIGH-1 — widget ids that are WRITABLE (members of + * v1.11.2 — widget ids that are WRITABLE (members of * `DASHBOARD_WIDGET_IDS`, so the iOS pin PUT validates them) but have NO * web render path: `src/app/page.tsx` renders strip tiles via hardcoded * per-id blocks and never iterates an id→component map, so these ids draw @@ -195,7 +195,7 @@ export interface DashboardWidgetConfig { */ visible: boolean; /** - * v1.4.15 Fix 5 — independent visibility for the *strip tile* (the + * v1.4.15 — independent visibility for the *strip tile* (the * upper row of trend cards). When omitted, the resolver mirrors * `visible` so existing saved layouts keep their previous behaviour * (one toggle controls both surfaces). When set explicitly the user @@ -208,7 +208,7 @@ export interface DashboardWidgetConfig { } /** - * v1.4.16 phase B8 — comparison baseline persisted alongside widgets. + * v1.4.16 — comparison baseline persisted alongside widgets. * * Research §7 Q3 settled on piggy-backing the comparison preference * onto the existing `User.dashboardWidgetsJson` blob rather than @@ -264,7 +264,7 @@ export const CHART_OVERLAY_KEYS = [ "medications", "sleep", "steps", - // v1.4.25 W16a — VO2 max chart-row on /insights/pulse. Independent + // v1.4.25 — VO2 max chart-row on /insights/pulse. Independent // overlay-prefs slot so the cardio sub-page's VO2 chart-cog state // doesn't bleed into the pulse chart sitting directly above it. "vo2Max", @@ -586,7 +586,7 @@ function coerceEnabledHeroItemKinds(value: unknown): PriorityItemKind[] { export interface DashboardLayout { version: number; widgets: DashboardWidgetConfig[]; - /** v1.4.16 phase B8 — see `COMPARISON_BASELINES` doc. */ + /** v1.4.16 — see `COMPARISON_BASELINES` doc. */ comparisonBaseline?: ComparisonBaseline; /** v1.4.18 — per-chart overlay-prefs (3 toggles per chart card). */ chartOverlayPrefs?: ChartOverlayPrefsMap; @@ -770,14 +770,14 @@ export const DEFAULT_DASHBOARD_LAYOUT: DashboardLayout = { { id: "sleep", visible: false, tileVisible: true, order: 14 }, { id: "steps", visible: false, tileVisible: true, order: 15 }, { id: "glucose", visible: false, tileVisible: true, order: 16 }, - // v1.4.15 phase-B4 — recent-unlocks card (dashboard surface for the + // v1.4.15 — recent-unlocks card (dashboard surface for the // gamification feature). Visible by default — the card is small, // self-gates on having any achievements (otherwise it shows a brief // "no achievements yet" + link), and maintainer-asked-for-it explicitly. // `tileVisible` is forced false because there is no tile surface // for this widget; only the chart-row card. { id: "achievements", visible: true, tileVisible: false, order: 17 }, - // v1.4.25 W8d — VO2 max trend tile. The strip tile is on by default + // v1.4.25 — VO2 max trend tile. The strip tile is on by default // (`tileVisible: true`) so accounts that already sync a VO2max sample // see cardio fitness without hunting through Settings → Dashboard; the // tile self-gates on having any VO2_MAX sample (see `showVo2Tile` in @@ -791,7 +791,7 @@ export const DEFAULT_DASHBOARD_LAYOUT: DashboardLayout = { // workouts list and renders an Apple-Health onboarding hint // otherwise. { id: "recentWorkouts", visible: true, tileVisible: true, order: 19 }, - // v1.11.2 B5 — v1.10 additive HealthKit signals, pinnable but + // v1.11.2 — v1.10 additive HealthKit signals, pinnable but // default-invisible on both surfaces. The user opts in via // Settings → Dashboard; each tile self-gates on having any sample. { id: "cardioRecovery", visible: false, tileVisible: false, order: 20 }, @@ -881,7 +881,7 @@ export function resolveDashboardLayout(raw: unknown): DashboardLayout { return { version: DASHBOARD_LAYOUT_VERSION, widgets: [...normalized, ...appended].sort((a, b) => a.order - b.order), - // v1.4.16 phase B8 — clamp unknown / missing comparison values to + // v1.4.16 — clamp unknown / missing comparison values to // "none" so the dashboard renders the pre-B8 behaviour for legacy // layouts and refuses to act on a value it doesn't recognise. comparisonBaseline: isComparisonBaseline(candidate.comparisonBaseline) @@ -939,7 +939,7 @@ export function serializeDashboardLayout( typeof w.tileVisible === "boolean" ? w.tileVisible : w.visible, order: i, // normalize to 0-based dense order })), - // v1.4.16 phase B8 — persist the comparison baseline explicitly. + // v1.4.16 — persist the comparison baseline explicitly. comparisonBaseline: isComparisonBaseline(layout.comparisonBaseline) ? layout.comparisonBaseline : "none", diff --git a/src/lib/dashboard/__tests__/score-narrative.test.ts b/src/lib/dashboard/__tests__/score-narrative.test.ts index b8bd1052b..c41335ce5 100644 --- a/src/lib/dashboard/__tests__/score-narrative.test.ts +++ b/src/lib/dashboard/__tests__/score-narrative.test.ts @@ -1,5 +1,5 @@ /** - * v1.21.2 (A5 / A6) — unit pins for the score-card narrative resolver. + * v1.21.2 — unit pins for the score-card narrative resolver. * * The PROSE is not tested; the FLAGS the card renders are: * - tension fires on a DISAGREEING readiness component set and carries the diff --git a/src/lib/dashboard/__tests__/snapshot.test.ts b/src/lib/dashboard/__tests__/snapshot.test.ts index 53daaec70..85b07d2eb 100644 --- a/src/lib/dashboard/__tests__/snapshot.test.ts +++ b/src/lib/dashboard/__tests__/snapshot.test.ts @@ -1,5 +1,5 @@ /** - * v1.7.0 W6 — unit tests for `buildDashboardSnapshot`. + * v1.7.0 — unit tests for `buildDashboardSnapshot`. * * The builder is tested in isolation with every sub-helper mocked so * the assertions pin the assembly contract without a live DB / LLM: @@ -452,7 +452,7 @@ describe("buildDashboardSnapshot — per-type thick-phase gate", () => { deltaReason: null, scoreVersion: SCORE_VERSION, bandSetter: "BLOOD_PRESSURE", - // v1.21.2 (A5 / A6) — additive narrative fields, null absent a + // v1.21.2 — additive narrative fields, null absent a // disagreeing readiness set / a returned metric (the fake derived // engines resolve nothing here). tension: null, @@ -566,8 +566,8 @@ describe("buildDashboardSnapshot — healthScore (warm phase only)", () => { computeBpInTargetFastPath.mockResolvedValue({ last7Days: { pct: 70, pairs: 6 }, last30Days: { pct: 80, pairs: 12 }, - // v1.17 W1d — the BP pillar reads this 90-day window, NOT last30Days. - // v1.17 W1b — `pairs` above the confidence floor so the pillar grades + // v1.17 — the BP pillar reads this 90-day window, NOT last30Days. + // v1.17 — `pairs` above the confidence floor so the pillar grades // the 90-day rate rather than falling back to all-time. last90Days: { pct: 77, pairs: 30 }, last90EarliestAt: new Date("2026-01-01T00:00:00.000Z"), @@ -598,7 +598,7 @@ describe("buildDashboardSnapshot — healthScore (warm phase only)", () => { deltaReason: null, scoreVersion: SCORE_VERSION, bandSetter: "BLOOD_PRESSURE", - // v1.21.2 (A5 / A6) — additive narrative fields, null here. + // v1.21.2 — additive narrative fields, null here. tension: null, returnToBand: null, }); @@ -1093,7 +1093,7 @@ describe("module gating maps — every toggleable-domain widget is mapped (v1.18 "glucose", "achievements", "recentWorkouts", - // v1.18.1 (D3) — medications graduated from CORE to a toggleable module. + // v1.18.1 — medications graduated from CORE to a toggleable module. "medications", "cardioRecovery", "sixMinuteWalk", @@ -1356,7 +1356,7 @@ describe("buildDashboardSnapshot — module gating (v1.18.0)", () => { }); }); -// v1.21.2 (A4 / A5 / A6) — the ambient-narrative wire. The Tension Verdict + +// v1.21.2 — the ambient-narrative wire. The Tension Verdict + // return-to-baseline ride the warm-phase healthScore; the briefing recall + // forward-look rides `briefingMemory`. The resolvers themselves are pinned in // `score-narrative.test.ts`; here only the THREADING through the snapshot DTO is diff --git a/src/lib/dashboard/score-narrative.ts b/src/lib/dashboard/score-narrative.ts index 5cd582570..85362de2b 100644 --- a/src/lib/dashboard/score-narrative.ts +++ b/src/lib/dashboard/score-narrative.ts @@ -1,5 +1,5 @@ /** - * v1.21.2 (A5 / A6) — the score-card narrative block for the dashboard / + * v1.21.2 — the score-card narrative block for the dashboard / * insights hero. * * Two server-resolved, DTO-only signals the `HealthScoreCard` renders but never diff --git a/src/lib/dashboard/snapshot-read.ts b/src/lib/dashboard/snapshot-read.ts index 135aa9c28..23857a904 100644 --- a/src/lib/dashboard/snapshot-read.ts +++ b/src/lib/dashboard/snapshot-read.ts @@ -143,7 +143,7 @@ export async function readDashboardSnapshotCached( healthScoreConfigJson: user.healthScoreConfigJson, }; - // v1.21.2 (A4) — the briefing recall + forward-look is locale-specific + // v1.21.2 — the briefing recall + forward-look is locale-specific // prose, so the snapshot cache key carries the resolved locale: an EN and // a DE session never share a snapshot cell (and never see each other's // memory wording). The `${user.id}|` prefix still covers the key under diff --git a/src/lib/dashboard/snapshot.ts b/src/lib/dashboard/snapshot.ts index bcd5054c5..ac58730d3 100644 --- a/src/lib/dashboard/snapshot.ts +++ b/src/lib/dashboard/snapshot.ts @@ -1,5 +1,5 @@ /** - * v1.7.0 W6 — unified dashboard first-paint snapshot. + * v1.7.0 — unified dashboard first-paint snapshot. * * `buildDashboardSnapshot` assembles every above-the-fold tile field in * a single server round-trip from the existing rollup / mood / widget @@ -278,7 +278,7 @@ export interface DashboardSnapshotExtras { bpInTargetPctPriorMonth: number | null; bpInTargetPctPriorYear: number | null; /** - * v1.17 W1b — number of paired BP readings inside the trailing-90-day + * v1.17 — number of paired BP readings inside the trailing-90-day * window, and the EFFECTIVE label span (real calendar span capped at 90 * days, or `null` when the window is empty). The BD-Zielbereich tile reads * the count against the confidence floor to decide between a percentage and @@ -378,7 +378,7 @@ export interface DashboardSnapshotHealthScore { */ restMode?: RestModeAnnotation | null; /** - * v1.21.2 (A5) — Tension Verdict, locale-agnostic. Fires only when the + * v1.21.2 — Tension Verdict, locale-agnostic. Fires only when the * readiness composite's contributors DISAGREE (≥1 strongly favourable AND ≥1 * strongly unfavourable); suppressed (`null`) under a clinical red-flag so the * red-flag path dominates. `positive` / `negative` carry the readiness @@ -388,7 +388,7 @@ export interface DashboardSnapshotHealthScore { */ tension?: ScoreTensionDto | null; /** - * v1.21.2 (A6) — return-to-baseline, locale-agnostic. Present only when a + * v1.21.2 — return-to-baseline, locale-agnostic. Present only when a * salient metric has come BACK inside the user's own personal range after a * prior out-of-band run; at most one (the most salient). `metricType` is the * `MeasurementType` the client maps to its localised metric name. Null @@ -398,7 +398,7 @@ export interface DashboardSnapshotHealthScore { } /** - * v1.21.2 (A4) — the briefing recall + forward-look. Both strings are + * v1.21.2 — the briefing recall + forward-look. Both strings are * already-localised, server-resolved prose: `recall` is the prior period's * narrative headline; `forward` points ahead to the most salient trend drift * (or a calm "holding steady" when nothing drifted). The card renders them @@ -500,7 +500,7 @@ export interface DashboardSnapshot { scoreRings?: DashboardScoreRing[]; briefing: DailyBriefing | null; /** - * v1.21.2 (A4) — server-resolved recall + forward-look for the briefing card. + * v1.21.2 — server-resolved recall + forward-look for the briefing card. * Null when no prior narrative is on file. Rides the snapshot DTO so iOS reads * the same already-localised block. Optional on the type (additive contract) * so older cached snapshots / fixtures without it stay valid. @@ -681,7 +681,7 @@ async function buildExtras( let bpInTargetPctPriorYear: number | null = null; let bpInTargetCount90: number | null = null; let bpInTargetSpanDays90: number | null = null; - // v1.17 W1b — hold the current + prior-week BP envelopes so the shared + // v1.17 — hold the current + prior-week BP envelopes so the shared // Health-Score input builder grades the pillar off the identical shape the // analytics route uses. let bpEnvelope: BpInTargetEnvelope | null = null; @@ -698,7 +698,7 @@ async function buildExtras( ); const clinicalBpTargets = getBpTargets(user.dateOfBirth); if (bpTargets) { - // v1.17 W1b — two runs (current + prior-week), identical to the + // v1.17 — two runs (current + prior-week), identical to the // analytics route, so the dashboard ring's week-over-week delta reflects // BP movement instead of zeroing it out. Both reuse the already-probed // coverage map and share the rollup/live branch decision. @@ -759,7 +759,7 @@ async function buildExtras( }), ]); } - // v1.17 W1d — the headline standardises on the trailing-90-day + // v1.17 — the headline standardises on the trailing-90-day // window (labelled "· 90 T" in the tile), identical to the analytics // route, so the dashboard tile and the insights surface never narrate // two windows for the same metric. All-time stays carried for the @@ -770,7 +770,7 @@ async function buildExtras( bpInTargetPctAllTime = windows.allTime?.pct ?? null; bpInTargetPctPriorMonth = windows.priorMonth?.pct ?? null; bpInTargetPctPriorYear = windows.priorYear?.pct ?? null; - // v1.17 W1b — count + effective span for the tile's confidence gate, + // v1.17 — count + effective span for the tile's confidence gate, // derived through the shared helper so this surface and `/api/analytics` // can never disagree on the gate or the label span. const bpWindow = deriveBpWindow90( @@ -786,7 +786,7 @@ async function buildExtras( // already-probed coverage map (no second probe). Score + band + // delta only; the component breakdown stays off this wire. // - // v1.17 W1b — the BP-pillar inputs come from the ONE shared + // v1.17 — the BP-pillar inputs come from the ONE shared // `buildHealthScoreBpInputs` builder the analytics route also uses, so the // ring and the insights card grade the pillar off identical inputs (same // 90-day window via W1d, same all-time fallback, same graded score, same @@ -1158,7 +1158,7 @@ function gateLayoutByModules( } /** - * v1.21.2 (A4) — map the machine-derived coach memory block onto the briefing + * v1.21.2 — map the machine-derived coach memory block onto the briefing * card's `{ recall, forward }` shape, both already-localised. `recall` is the * prior period's narrative headline (locale-generated prose). `forward` points * ahead from the single most salient trend drift (a metric whose current-period @@ -1424,7 +1424,7 @@ export async function buildDashboardSnapshot( locale, ); - // v1.21.2 (A4 / A5 / A6) — the score-card narrative (Tension Verdict + + // v1.21.2 — the score-card narrative (Tension Verdict + // return-to-baseline) and the briefing recall + forward-look. The score // narrative is computed from the derived engines, no model involved, so no // AI switch and no AI opt-out gates it. The recall reads the period @@ -1521,7 +1521,7 @@ export async function buildDashboardSnapshot( }, extras: extrasResult?.extras ?? null, medsToday, - // v1.21.2 (A5 / A6) — fold the score-card narrative onto the health score so + // v1.21.2 — fold the score-card narrative onto the health score so // the hero reads tension + return-to-baseline off the resolved score DTO. healthScore: extrasResult?.healthScore ? { @@ -1535,7 +1535,7 @@ export async function buildDashboardSnapshot( // older cached snapshots stay valid. scoreRings, briefing: briefing.briefing, - // v1.21.2 (A4) — the briefing recall + forward-look. Null when there is no + // v1.21.2 — the briefing recall + forward-look. Null when there is no // prior narrative on file or no briefing is shown. briefingMemory: briefing.briefing !== null ? briefingMemory : null, briefingState: briefing.briefingState, diff --git a/src/lib/dashboard/summary-reads.ts b/src/lib/dashboard/summary-reads.ts index 2ca4df790..1a0322d63 100644 --- a/src/lib/dashboard/summary-reads.ts +++ b/src/lib/dashboard/summary-reads.ts @@ -18,7 +18,7 @@ import { Prisma, type PrismaClient } from "@/generated/prisma/client"; import type { MeasurementType } from "@/generated/prisma/client"; -/** v1.4.38 W-F — the single most recent reading per measurement +/** v1.4.38 — the single most recent reading per measurement * type, irrespective of age. One row per metric the user has ever * touched; replaces the legacy unbounded `prisma.measurement.findMany`. * @@ -37,12 +37,12 @@ export interface LatestEverRow { measured_at: Date; } -/** v1.4.38 W-F — per-day measurement_rollup bucket feeding the dashboard +/** v1.4.38 — per-day measurement_rollup bucket feeding the dashboard * sparkline. At most `sparkDays` buckets per metric × N metrics — * bounded by `sparkDays * |measurementTypes|` rather than the raw row * count. * - * v1.4.39 W-SUM — `sum_value` rides along so the cumulative tile + * v1.4.39 — `sum_value` rides along so the cumulative tile * (ACTIVITY_STEPS) renders the daily SUM rather than the per-bucket * MEAN. Spot metrics ignore the column; cumulative tiles fall back to * `mean * count` when the legacy NULL hits (boot-backfill convergence diff --git a/src/lib/devices/__tests__/revoke.test.ts b/src/lib/devices/__tests__/revoke.test.ts index 3aad9b26f..089eebd6a 100644 --- a/src/lib/devices/__tests__/revoke.test.ts +++ b/src/lib/devices/__tests__/revoke.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; -// v1.4.23 W6 (HIGH 6) — verify the device-revoke cascade wraps every +// v1.4.23 (HIGH 6) — verify the device-revoke cascade wraps every // write inside `prisma.$transaction` so a partial failure doesn't // leave the device row alive with all its tokens revoked. Also pins // the cross-user 404 path (returns null) and the no-access-token diff --git a/src/lib/devices/revoke.ts b/src/lib/devices/revoke.ts index f31db5804..383125637 100644 --- a/src/lib/devices/revoke.ts +++ b/src/lib/devices/revoke.ts @@ -1,5 +1,5 @@ /** - * v1.4.23 W6 (HIGH 6 / S-01) — shared device-revoke cascade. + * v1.4.23 (HIGH 6 / S-01) — shared device-revoke cascade. * * Both `DELETE /api/auth/me/devices/[id]` and `DELETE /api/devices/[id]` * historically performed the same four-write cascade (refresh-token diff --git a/src/lib/doctor-report-helpers.ts b/src/lib/doctor-report-helpers.ts index 305fe5e70..584efdd22 100644 --- a/src/lib/doctor-report-helpers.ts +++ b/src/lib/doctor-report-helpers.ts @@ -323,7 +323,7 @@ export function normaliseDateRange( } /** - * v1.17 W1a — doctor-report medication adherence through the dose-ledger + * v1.17 — doctor-report medication adherence through the dose-ledger * authority. * * The previous implementation tallied RAW intake rows: `taken = takenAt != null`, diff --git a/src/lib/doctor-report-pdf-core.ts b/src/lib/doctor-report-pdf-core.ts index 3da0ac540..447ec726a 100644 --- a/src/lib/doctor-report-pdf-core.ts +++ b/src/lib/doctor-report-pdf-core.ts @@ -143,7 +143,7 @@ export interface DoctorReportRenderOptions { */ now?: Date; /** - * v1.4.25 W7 — per-user display timezone. When omitted the report + * v1.4.25 — per-user display timezone. When omitted the report * renders timestamps in Europe/Berlin (legacy contract). Server * callers pass `resolveUserTimezone(user.id)`; client callers pass * the value from auth context so a US user's PDF carries diff --git a/src/lib/doctor-report-pdf/clinical-records-notes-section.ts b/src/lib/doctor-report-pdf/clinical-records-notes-section.ts index 39ad2f552..02538766d 100644 --- a/src/lib/doctor-report-pdf/clinical-records-notes-section.ts +++ b/src/lib/doctor-report-pdf/clinical-records-notes-section.ts @@ -180,7 +180,7 @@ export function buildClinicalRecordsNotesSection( } } - // v1.18.1 P4 — illness / condition episodes overlapping the window. Present + // v1.18.1 — illness / condition episodes overlapping the window. Present // only when the illness module is on AND the window held an episode (the // aggregator gates `data.illnessEpisodes`). Labels + lifecycle + dates and // the body site where one is stated; the encrypted note is never read. A purely retrospective, factual table — diff --git a/src/lib/doctor-report-pdf/medication-mood-wellness-section.ts b/src/lib/doctor-report-pdf/medication-mood-wellness-section.ts index 2aa8715f0..e29c1b040 100644 --- a/src/lib/doctor-report-pdf/medication-mood-wellness-section.ts +++ b/src/lib/doctor-report-pdf/medication-mood-wellness-section.ts @@ -83,7 +83,7 @@ export function buildMedicationMoodWellnessSection( }), ); const compRows = complianceEntries.map(([id, c]) => { - // v1.17 W1a — `total` is the ledger rate denominator (taken + missed, + // v1.17 — `total` is the ledger rate denominator (taken + missed, // deliberate skips excluded), so `taken / total` matches the app's // detail-page adherence %. The column is labelled "Expected" rather // than "Total" so the row stays internally coherent: Taken + Missed = @@ -140,7 +140,7 @@ export function buildMedicationMoodWellnessSection( .finalY + 8; } - // v1.4.25 W4d — GLP-1 therapy section. Only renders when the data + // v1.4.25 — GLP-1 therapy section. Only renders when the data // aggregator emitted a non-null `glp1` block (user has at least one // active GLP-1 medication and the compliance toggle is on). Lists // current drug + dose, full titration history, weight curve over diff --git a/src/lib/doctor-report-types.ts b/src/lib/doctor-report-types.ts index de8a01b22..6c7ae66b6 100644 --- a/src/lib/doctor-report-types.ts +++ b/src/lib/doctor-report-types.ts @@ -192,7 +192,7 @@ export interface DoctorReportData { } | null; mood: DoctorReportMood | null; /** - * v1.4.25 W4d — GLP-1 therapy section. Populated when the user has at + * v1.4.25 — GLP-1 therapy section. Populated when the user has at * least one active GLP-1 medication. Null/absent when the user has no * GLP-1 prescription (the vast majority of accounts). When set the * PDF gains a "GLP-1 Therapy" section listing dose history, weight @@ -312,7 +312,7 @@ export interface DoctorReportData { count: number; }> | null; /** - * v1.18.1 P4 — illness / condition episodes overlapping the report window. + * v1.18.1 — illness / condition episodes overlapping the report window. * Populated ONLY when the `illness` module is enabled (default-on, opt-out); * the journal is on and shared deliberately, so the privacy stance matches * labs (ON when the module is on), not mood / cycle. @@ -543,7 +543,7 @@ export interface CollectDoctorReportOptions { } /** - * v1.17 W1a — the medication row shape the ledger compliance builder needs. + * v1.17 — the medication row shape the ledger compliance builder needs. * Carries the cadence-engine context fields (course window + creation + * archived schedule eras) so the same band minter the detail page uses can * reconstruct expected slots. @@ -560,7 +560,7 @@ export interface DoctorReportComplianceMedication { createdAt: Date; schedules: ComplianceSchedule[]; scheduleRevisions?: ScheduleRevisionLike[]; - /** v1.25 H-MED1 — pause eras so paused days drop out of the denominator. */ + /** v1.25 — pause eras so paused days drop out of the denominator. */ pauseEras?: MedicationPauseEraLike[]; /** v1.40 (#1024) — the courses, so a gap between two expects nothing. */ courses?: { startsOn: Date; endsOn: Date | null }[]; diff --git a/src/lib/documents/__tests__/auto-stage-labs.test.ts b/src/lib/documents/__tests__/auto-stage-labs.test.ts index 90a9943f0..9c92effed 100644 --- a/src/lib/documents/__tests__/auto-stage-labs.test.ts +++ b/src/lib/documents/__tests__/auto-stage-labs.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; /** - * S8 — auto-stage lab facts after the auto-index job. Pins the sign-off + * Auto-stage lab facts after the auto-index job. Pins the sign-off * (auto-STAGE, never auto-COMMIT), the double module gate, the reuse of the * auto-index consent gate, the "looks like a lab" heuristic, idempotency (a * non-STORED or already-staged document is left alone), and the guarded diff --git a/src/lib/documents/__tests__/extract.test.ts b/src/lib/documents/__tests__/extract.test.ts index 6e398accf..6d4dabbc8 100644 --- a/src/lib/documents/__tests__/extract.test.ts +++ b/src/lib/documents/__tests__/extract.test.ts @@ -1,5 +1,5 @@ /** - * v1.25 (W-DOCS-IN) — extraction mapping safety tests. + * v1.25 — extraction mapping safety tests. * * The mapping is the safety boundary: it must reproduce STATED facts only and * never let a code, value, or status leak across resource types or past the diff --git a/src/lib/documents/auto-stage-labs.ts b/src/lib/documents/auto-stage-labs.ts index a30714219..4250be7df 100644 --- a/src/lib/documents/auto-stage-labs.ts +++ b/src/lib/documents/auto-stage-labs.ts @@ -1,5 +1,5 @@ /** - * S8 — auto-stage lab facts from a freshly indexed vault document. + * Auto-stage lab facts from a freshly indexed vault document. * * After the per-document auto-index job transcribes a stored document, a * document that LOOKS LIKE a lab result is run through the SAME inbound diff --git a/src/lib/documents/commit.ts b/src/lib/documents/commit.ts index 8c92183d8..db39f3d31 100644 --- a/src/lib/documents/commit.ts +++ b/src/lib/documents/commit.ts @@ -1,5 +1,5 @@ /** - * v1.25 (W-DOCS-IN) — commit an APPROVED staged fact into a structured store. + * v1.25 — commit an APPROVED staged fact into a structured store. * * The only write path out of the staging area. Each approved fact is routed to * its existing structured store through the same field-by-field create the @@ -72,7 +72,7 @@ function statedDateOrNow(date: string | null): Date { } /** - * v1.43 (D1223) — the marker the person picked for an OBSERVATION on the + * v1.43 — the marker the person picked for an OBSERVATION on the * review screen, and whether to remember the label as its alias. */ export interface ObservationChoice { diff --git a/src/lib/documents/extract.ts b/src/lib/documents/extract.ts index 04477338f..6b39b6b9c 100644 --- a/src/lib/documents/extract.ts +++ b/src/lib/documents/extract.ts @@ -1,5 +1,5 @@ /** - * v1.25 (W-DOCS-IN) — inbound clinical-document extraction orchestration. + * v1.25 — inbound clinical-document extraction orchestration. * * Takes the sniffed upload bytes (or in-browser-OCR'd text) + the resolved * OCR/vision provider, runs ONE (retry-once) structured-extraction call, diff --git a/src/lib/documents/store.ts b/src/lib/documents/store.ts index a6ef65d24..1be6f2a0c 100644 --- a/src/lib/documents/store.ts +++ b/src/lib/documents/store.ts @@ -1,5 +1,5 @@ /** - * v1.25 (W-DOCS-IN) — server-side helpers for the inbound-document store. + * v1.25 — server-side helpers for the inbound-document store. * * The AES-256-GCM ↔ `Bytes` codec for `InboundDocument.contentEncrypted` (the * raw uploaded document, base64-of-binary → `encrypt()` string → UTF-8 bytes, @@ -264,7 +264,7 @@ export function serialiseDocument( /** * Map a persisted staged-fact row to the review DTO. * - * v1.43 (D1223) — with the account's `markerIndex`, a pending OBSERVATION + * v1.43 — with the account's `markerIndex`, a pending OBSERVATION * also says which marker its label resolves to (`biomarkerId`, `matchKind`) * or offers up to three (`suggestions`), the same resolution the confirm * applies. Without an index, or for any other fact, the fields are absent. diff --git a/src/lib/environment/__tests__/service.test.ts b/src/lib/environment/__tests__/service.test.ts index f9fb1c86d..895f1cbb2 100644 --- a/src/lib/environment/__tests__/service.test.ts +++ b/src/lib/environment/__tests__/service.test.ts @@ -1,5 +1,5 @@ /** - * v1.25 (W-ENV) — conservative location resolution. + * v1.25 — conservative location resolution. * * Pins the precedence (explicit period → [DEVICE, reserved] → home-on/after- * effective-from → SKIP) and the conservative default backfill range. The whole diff --git a/src/lib/environment/fields.ts b/src/lib/environment/fields.ts index 1b346c308..c1d5dad3e 100644 --- a/src/lib/environment/fields.ts +++ b/src/lib/environment/fields.ts @@ -1,5 +1,5 @@ /** - * v1.25 (W-ENV) — the canonical environmental-exposure field vocabulary. + * v1.25 — the canonical environmental-exposure field vocabulary. * * One declarative table that names each daily weather/daylight field HealthLog * stores AND correlates. It is the single source the two surfaces speak: diff --git a/src/lib/environment/open-meteo.ts b/src/lib/environment/open-meteo.ts index a0f8e7e43..4025d4edf 100644 --- a/src/lib/environment/open-meteo.ts +++ b/src/lib/environment/open-meteo.ts @@ -1,5 +1,5 @@ /** - * v1.25 (W-ENV) — Open-Meteo client (keyless historical weather + geocoding). + * v1.25 — Open-Meteo client (keyless historical weather + geocoding). * * The single outbound egress for the environmental-context module. Open-Meteo's * Historical (Archive) API is keyless, free for non-commercial use, and offers diff --git a/src/lib/environment/service.ts b/src/lib/environment/service.ts index 071e245e7..b14c85b95 100644 --- a/src/lib/environment/service.ts +++ b/src/lib/environment/service.ts @@ -1,5 +1,5 @@ /** - * v1.25 (W-ENV) — environmental-context resolution + fetch/upsert service. + * v1.25 — environmental-context resolution + fetch/upsert service. * * Owns the location-precedence logic and the per-day upsert into * `EnvironmentContext`. Both the nightly job and the on-demand backfill route diff --git a/src/lib/export.ts b/src/lib/export.ts index c39edf350..f2ad30b28 100644 --- a/src/lib/export.ts +++ b/src/lib/export.ts @@ -1,7 +1,7 @@ /** * Export utilities — CSV, JSON, and ZIP generation. * - * v1.4.25 W7 — per-user timezone. Timestamps were emitted with + * v1.4.25 — per-user timezone. Timestamps were emitted with * `.toISOString()` (UTC `Z` suffix), which Excel / LibreOffice * silently render in the viewer's local zone — issue #167. The * helpers now accept a `userTz` argument and write diff --git a/src/lib/export/__tests__/profile-backup-decrypt-failures.test.ts b/src/lib/export/__tests__/profile-backup-decrypt-failures.test.ts index 48b03e390..1cc78844b 100644 --- a/src/lib/export/__tests__/profile-backup-decrypt-failures.test.ts +++ b/src/lib/export/__tests__/profile-backup-decrypt-failures.test.ts @@ -1,5 +1,5 @@ /** - * v1.37.19 (A6-9) — the portable export's decryptFailures manifest. + * v1.37.19 — the portable export's decryptFailures manifest. * * Watched red: with the collector dropped from `decryptProfileFieldSoft` * (the pre-fix fail-soft that only nulled the field), the first case fails diff --git a/src/lib/export/backup-plan.ts b/src/lib/export/backup-plan.ts index d2d561cda..25a1246e1 100644 --- a/src/lib/export/backup-plan.ts +++ b/src/lib/export/backup-plan.ts @@ -588,7 +588,7 @@ export const TWO_ENDED_MODELS = [ // questionnaire over every module decision taken in Settings since. It is // carried and asserted, not defaulted. "OnboardingRecord", - // v1.43 (D1223) — a marker's other spellings ride on the marker as + // v1.43 — a marker's other spellings ride on the marker as // `aliases[]`, and the merge ledger travels as its own list whose target is // resolved by id or by name like a lab result's marker. Without the aliases a // restored account is asked again at the next scan which marker each diff --git a/src/lib/export/profile-backup.ts b/src/lib/export/profile-backup.ts index 60b0e63b1..0f1952c89 100644 --- a/src/lib/export/profile-backup.ts +++ b/src/lib/export/profile-backup.ts @@ -150,7 +150,7 @@ export interface CustomMetricEntryBackupEntry { */ noteEncrypted?: string | null; createdAt?: string; - /** v1.37.20 (A3-11) — soft-delete tombstone; DR payloads only. */ + /** v1.37.20 — soft-delete tombstone; DR payloads only. */ deletedAt?: string | null; } @@ -210,7 +210,7 @@ export interface ProfileBackupSection { healthProfileFacts: HealthProfileFactBackupEntry[]; correlationPatterns: CorrelationPatternBackupEntry[]; /** - * v1.37.19 (A6-9) — field paths whose ciphertext this instance could not + * v1.37.19 — field paths whose ciphertext this instance could not * open while building a PORTABLE export (fail-soft nulls). Disclosed in * the file itself so an export with a nulled emergency field no longer * reads byte-identical to one where the field was never written — the @@ -242,7 +242,7 @@ export interface ProfileBackupCounts { function decryptProfileFieldSoft( buf: Uint8Array | null, field: string, - /** v1.37.19 (A6-9) — collector for the file's decryptFailures manifest. */ + /** v1.37.19 — collector for the file's decryptFailures manifest. */ failures?: string[], ): string | null { if (!buf || buf.byteLength === 0) return null; @@ -308,7 +308,7 @@ export async function buildProfileBackupSection( options: ProfileBackupOptions = {}, ): Promise { const disasterRecovery = options.purpose === "disaster-recovery"; - // v1.37.19 (A6-9) — every fail-soft decrypt below records its field path + // v1.37.19 — every fail-soft decrypt below records its field path // here; the list rides the exported file as its decryptFailures manifest. const decryptFailures: string[] = []; @@ -320,7 +320,7 @@ export async function buildProfileBackupSection( where: disasterRecovery ? { userId } : { userId, deletedAt: null }, orderBy: { name: "asc" }, include: { - // v1.37.20 (A3-11) — entries soft-delete now: a portable export + // v1.37.20 — entries soft-delete now: a portable export // omits tombstoned readings (a restore must not resurrect them); a // DR payload carries them, tombstone included, like the parent. entries: { diff --git a/src/lib/export/records-backup.ts b/src/lib/export/records-backup.ts index eb191d378..c003e9db7 100644 --- a/src/lib/export/records-backup.ts +++ b/src/lib/export/records-backup.ts @@ -130,7 +130,7 @@ export interface BiomarkerBackupEntry { /** The catalogue key the marker's name and unit resolve to. */ analyteKey?: string | null; /** - * v1.43 (D1223) — the other spellings the marker answers to, with where + * v1.43 — the other spellings the marker answers to, with where * each came from. Absent on files written before them. */ aliases?: BiomarkerAliasBackupEntry[]; @@ -147,7 +147,7 @@ export interface BiomarkerAliasBackupEntry { } /** - * v1.43 (D1223) — one marker folded into another. The target travels by id + * v1.43 — one marker folded into another. The target travels by id * in a disaster-recovery file and by name in both, the way a lab result's * marker does; the source is gone, so its id and name are the record. */ diff --git a/src/lib/export/restore-backup.ts b/src/lib/export/restore-backup.ts index 7a2c43ce0..12387f67f 100644 --- a/src/lib/export/restore-backup.ts +++ b/src/lib/export/restore-backup.ts @@ -788,7 +788,7 @@ export async function restoreBackup( const moods = await tx.moodEntry.deleteMany({ where: { userId: ownerId }, }); - // v1.4.39 W-MOOD — wipe the persisted mood rollup partition + // v1.4.39 — wipe the persisted mood rollup partition // for this owner so the next analytics read doesn't surface // pre-restore daily means. The fold below mints fresh rows // from the restored mood entries. @@ -2106,7 +2106,7 @@ export async function restoreBackup( }); biomarkerByName.set(biomarker.name, created.id); restoredBiomarkerIds.add(created.id); - // v1.43 (D1223) — the other spellings, folded the way the resolver + // v1.43 — the other spellings, folded the way the resolver // reads them, so the next scan matches them again. const aliasRows = (biomarker.aliases ?? []) .map((alias) => ({ @@ -2141,7 +2141,7 @@ export async function restoreBackup( } } - // v1.43 (D1223) — the merge ledger, against the markers just written: + // v1.43 — the merge ledger, against the markers just written: // by id where the file carries one this restore kept, by name // otherwise. A row whose target the file does not carry has nothing // to point at and is left out (counted on the event). @@ -3039,7 +3039,7 @@ export async function restoreBackup( } } - // v1.4.39 W-MOOD — re-fold the mood rollup tier from the just- + // v1.4.39 — re-fold the mood rollup tier from the just- // restored entries. Runs outside the transaction so the (5-year) // fold can't hold a long write lock; best-effort so a populator // hiccup doesn't undo the restore. The boot-time backfill is the @@ -3058,7 +3058,7 @@ export async function restoreBackup( } } - // v1.4.39 W-MED — re-fold the medication-compliance rollup tier + // v1.4.39 — re-fold the medication-compliance rollup tier // from the restored intake events. The medication delete inside the // transaction already cascaded the existing rollup partition for // this owner via the FK, so the fold mints fresh rows. Boot-time diff --git a/src/lib/fhir/build-bundle.ts b/src/lib/fhir/build-bundle.ts index fe0309f10..db9225600 100644 --- a/src/lib/fhir/build-bundle.ts +++ b/src/lib/fhir/build-bundle.ts @@ -82,7 +82,7 @@ function escapeXml(value: string): string { } /** - * v1.25 (W-RECORDS) — the structured records folded into the document bundle. + * v1.25 — the structured records folded into the document bundle. * Passed separately from `DoctorReportData` (they are always-available * reference records, not part of the time-windowed report aggregation). * Empty arrays emit nothing. diff --git a/src/lib/fhir/records.ts b/src/lib/fhir/records.ts index df86985f1..11c72df87 100644 --- a/src/lib/fhir/records.ts +++ b/src/lib/fhir/records.ts @@ -1,5 +1,5 @@ /** - * v1.25 (W-RECORDS) — FHIR R4 emitters for the structured health records. + * v1.25 — FHIR R4 emitters for the structured health records. * * Pure functions over the server-authoritative DTOs (`@/lib/records/dto`): * - Allergy → `AllergyIntolerance` diff --git a/src/lib/fhir/resources/conditions.ts b/src/lib/fhir/resources/conditions.ts index a97effba8..5ed13cb4f 100644 --- a/src/lib/fhir/resources/conditions.ts +++ b/src/lib/fhir/resources/conditions.ts @@ -121,7 +121,7 @@ function conditionClinicalStatus(resolved: boolean): FhirCodeableConcept { } /** - * v1.18.1 P4 — emit one `Condition` per illness/condition episode plus a + * v1.18.1 — emit one `Condition` per illness/condition episode plus a * bounding `Encounter`. Absent unless the illness module is enabled AND the * window held an episode (the aggregator only populates `illnessEpisodes` * then). Ids run `condition-1..N` / `encounter-1..N`; the Encounter references diff --git a/src/lib/fhir/types.ts b/src/lib/fhir/types.ts index 9685b4419..c8a70e22e 100644 --- a/src/lib/fhir/types.ts +++ b/src/lib/fhir/types.ts @@ -250,7 +250,7 @@ export interface FhirComposition { } /** - * v1.18.1 P4 — R4 `Condition`. Carries the illness/condition episode the + * v1.18.1 — R4 `Condition`. Carries the illness/condition episode the * journal records: a `code` (SNOMED 64572001 "Disease" as the safe non- * diagnostic root + a `.text` label), the clinical + verification status, an * onset, and (when resolved) an `abatementDateTime`. HealthLog does NOT assert @@ -294,7 +294,7 @@ export interface FhirBodyStructure { } /** - * v1.18.1 P4 — R4 `Encounter`. Emitted alongside a resolved episode to bound + * v1.18.1 — R4 `Encounter`. Emitted alongside a resolved episode to bound * the period of care (onset → resolution) so a clinician sees the episode as a * time-boxed event. `status` is `finished` for a resolved episode and * `in-progress` for an ongoing one; `class` is the ambulatory code. @@ -310,7 +310,7 @@ export interface FhirEncounter { } /** - * v1.25 (W-RECORDS) — R4 `AllergyIntolerance`. A self-recorded allergy / + * v1.25 — R4 `AllergyIntolerance`. A self-recorded allergy / * intolerance. `clinicalStatus`/`verificationStatus` carry "what the user * asserts" (verification is always `unconfirmed` — patient-reported, not a * clinician-confirmed diagnosis). The substance rides `code.text` (no @@ -335,7 +335,7 @@ export interface FhirAllergyIntolerance { } /** - * v1.25 (W-RECORDS) — R4 `FamilyMemberHistory`. One relative's condition as + * v1.25 — R4 `FamilyMemberHistory`. One relative's condition as * recorded by the user. `relationship` carries the SNOMED family-member * concept; the condition rides `condition[].code.text` (no machine-guessed * code). `status: "completed"` marks the record as captured. Patient-reported. diff --git a/src/lib/fitbit/sync-sleep.ts b/src/lib/fitbit/sync-sleep.ts index 4bfcb2f45..96a4ca8c5 100644 --- a/src/lib/fitbit/sync-sleep.ts +++ b/src/lib/fitbit/sync-sleep.ts @@ -104,7 +104,7 @@ export async function syncUserSleep( ); // `markSynced` is owned by the orchestrator (`syncUserFitbit`). - // S4 — trigger the debounced morning refresh on a last-night segment landing + // Trigger the debounced morning refresh on a last-night segment landing // (mirrors the Withings / WHOOP / Apple seams). void maybeEnqueueMorningRefresh( userId, diff --git a/src/lib/format-locale.ts b/src/lib/format-locale.ts index cf5a9617f..76434a6fa 100644 --- a/src/lib/format-locale.ts +++ b/src/lib/format-locale.ts @@ -212,7 +212,7 @@ export function makeFormatters( // so axis month names follow the UI language, not the date-order pin. const dateLocale = dateOrderLocale(dateFormat, intlLocale); - // v1.28.42 (H2) — the returned closures used to construct a fresh + // v1.28.42 — the returned closures used to construct a fresh // `Intl.NumberFormat` / `Intl.DateTimeFormat` on every call. `makeFormatters` // is memoised per (locale, tz, timeFormat, dateFormat) on the client, but a // dense history list still calls these closures thousands of times per diff --git a/src/lib/format.ts b/src/lib/format.ts index 2da828699..00098e7b7 100644 --- a/src/lib/format.ts +++ b/src/lib/format.ts @@ -14,7 +14,7 @@ * pre-fetch data (e.g. a static prop), migrate it to `useFormatters()` to * stay consistent with the server-rendered locale. * - * v1.4.43 QoL (M8) — `activeLocale()` now reads the full `Locale` union + * v1.4.43 — `activeLocale()` now reads the full `Locale` union * (`de | en | fr | es | it | pl`). Pre-fix, a sub-locale user (fr / es / * it / pl) saw French strings but the legacy SSR formatters silently * fell back to `en`, producing "12/24/2025, 2:30 PM" in a French shell. @@ -88,7 +88,7 @@ export function formatTime(date: Date | string): string { } /** - * v1.4.43 QoL (L8) — render a timestamp as a relative phrase when it + * v1.4.43 — render a timestamp as a relative phrase when it * happened within the last 24 hours, otherwise fall back to the * absolute locale-aware date+time. Closes the visual inconsistency * the measurement-list surfaced: the same screen could show "vor 3 diff --git a/src/lib/gamification/__tests__/achievements.test.ts b/src/lib/gamification/__tests__/achievements.test.ts index b42dcfdd0..bcab22f94 100644 --- a/src/lib/gamification/__tests__/achievements.test.ts +++ b/src/lib/gamification/__tests__/achievements.test.ts @@ -186,7 +186,7 @@ describe("applyDiscoveryFilter", () => { }); }); -// v1.18.1 P4 — Rest Mode streak-freeze. A streak that lapses ONLY across +// v1.18.1 — Rest Mode streak-freeze. A streak that lapses ONLY across // illness days bridges into one continuous run rather than breaking; it is // never invented, and a non-illness gap still breaks the streak. describe("bridgeFrozenStreakGaps (Rest Mode streak-freeze)", () => { diff --git a/src/lib/gamification/__tests__/expansion-metrics.test.ts b/src/lib/gamification/__tests__/expansion-metrics.test.ts index 435b66940..f6e305499 100644 --- a/src/lib/gamification/__tests__/expansion-metrics.test.ts +++ b/src/lib/gamification/__tests__/expansion-metrics.test.ts @@ -367,7 +367,7 @@ describe("buildExpansionMetricValues — integration smoke", () => { }); }); -// v1.18.11 (W5 perf) — the achievements builder now precomputes each vitals +// v1.18.11 — the achievements builder now precomputes each vitals // row's Berlin day-key (and hour) ONCE and threads the parallel arrays into // the engagement + hidden passes instead of re-running `Intl.DateTimeFormat` // per row per pass. The optimisation must be a pure speed-up: supplying the diff --git a/src/lib/gamification/achievements-result.ts b/src/lib/gamification/achievements-result.ts index e5d4cf9cd..d0b87240f 100644 --- a/src/lib/gamification/achievements-result.ts +++ b/src/lib/gamification/achievements-result.ts @@ -108,7 +108,7 @@ function maxDate(a: Date, b: Date): Date { } /** - * v1.18.1 P4 — every local day key covered by an illness episode (onset day + * v1.18.1 — every local day key covered by an illness episode (onset day * through the resolved day, or through `now` while ongoing), inclusive on * both ends. These are the days a streak is allowed to lapse across without * breaking (the Rest Mode freeze). Uses the same `tz` day anchoring every @@ -355,7 +355,7 @@ function getOnTimePerfectDaySeries( { tz }, ); - // v1.4.34 IW-C — `early` is also a compliant bucket (dose taken + // v1.4.34 — `early` is also a compliant bucket (dose taken // within the 3h pre-window grace), so it preserves a perfect day. if (timing !== "on_time" && timing !== "early") { isPerfectDay = false; @@ -425,11 +425,11 @@ function getCompliance80DaySeries( } /** - * v1.4.34 IW-G — pulled out of the GET handler so `cached()` can wrap + * v1.4.34 — pulled out of the GET handler so `cached()` can wrap * the heavy aggregation. Returns the locale-agnostic web payload; the * iOS-format transform runs in the handler after the cache read. * - * v1.18.0 B5 — this builder NO LONGER writes. It computes the not-yet- + * v1.18.0 — this builder NO LONGER writes. It computes the not-yet- * persisted unlocks and returns them as `pendingUnlocks`; the handler * performs the idempotent `createMany({ skipDuplicates: true })` on every * request after the cache read. That keeps the persistence off the cached @@ -610,7 +610,7 @@ export async function buildAchievementsResult( updatedAt: true, }, }), - // v1.18.1 P4 — illness episodes over the window, for the streak-freeze. + // v1.18.1 — illness episodes over the window, for the streak-freeze. // Gated on the illness module so a non-illness account does no read and // never freezes anything. Both ongoing and already-resolved episodes are // read: a past illness should not retroactively break a historical @@ -633,7 +633,7 @@ export async function buildAchievementsResult( : Promise.resolve([]), ]); - // v1.18.1 P4 — Rest Mode streak-freeze. Build the set of local-day keys + // v1.18.1 — Rest Mode streak-freeze. Build the set of local-day keys // covered by an illness episode (onset → resolved, or → now while // ongoing). A day-streak that lapses only across these days is bridged // rather than broken (see `bridgeFrozenStreakGaps`): being unwell pauses a @@ -741,7 +741,7 @@ export async function buildAchievementsResult( const metrics = { totalTakenIntakes: takenIntakeDates.length, - // v1.18.1 P4 — every day-streak runs through `freeze(...)` so an active + // v1.18.1 — every day-streak runs through `freeze(...)` so an active // or past illness episode bridges (does not break) the run across the // days the user was unwell. bmiGreenStreak: calculateLongestStreak(freeze(healthSeries.bmi.dayKeys)), @@ -930,7 +930,7 @@ export async function buildAchievementsResult( mergedDates, ); - // v1.18.0 B5 — drop badges whose owning module is disabled BEFORE both + // v1.18.0 — drop badges whose owning module is disabled BEFORE both // unlock-persistence and serialisation. A sleep badge must never unlock // (or render) while the sleep module is off; same for mood badges when // mood is off, etc. Core-domain and account-wide badges return a null @@ -941,7 +941,7 @@ export async function buildAchievementsResult( return moduleMap[owner as ModuleKey] !== false; }); - // v1.18.0 B5 — compute the newly unlocked rows here but DON'T write them + // v1.18.0 — compute the newly unlocked rows here but DON'T write them // inside the cached factory: a write must not be a side effect of a // cached GET (a cache hit would silently skip it, a concurrent miss // could duplicate it). The handler performs the idempotent @@ -981,7 +981,7 @@ export async function buildAchievementsResult( .filter((a) => !a.unlocked && !a.isHidden) .sort((a, b) => b.progressPercent - a.progressPercent)[0] ?? null; - // v1.4.18 reconcile — redact hidden+locked entries before serialising + // v1.4.18 — redact hidden+locked entries before serialising // so a curious user opening DevTools/Network can't read the trigger // semantics from `metric`, `titleKey`, `descriptionKey`, `icon`, // `target`, `current`, or `progressPercent`. The DOM render path @@ -1015,7 +1015,7 @@ export async function buildAchievementsResult( }, achievements: redactedAchievements, metrics: redactedMetrics, - // v1.18.0 B5 — carried out of the cached factory so the handler can + // v1.18.0 — carried out of the cached factory so the handler can // persist them on every read (idempotent), not as a write side effect // that a cache hit would skip. NOT part of the serialised payload. pendingUnlocks, @@ -1024,7 +1024,7 @@ export async function buildAchievementsResult( return result; } -/** v1.18.0 B5 — a not-yet-persisted unlock the handler writes after the cache read. */ +/** v1.18.0 — a not-yet-persisted unlock the handler writes after the cache read. */ interface PendingUnlock { achievementId: string; unlockedAt: string; diff --git a/src/lib/gamification/achievements.ts b/src/lib/gamification/achievements.ts index a526fa146..e84309e55 100644 --- a/src/lib/gamification/achievements.ts +++ b/src/lib/gamification/achievements.ts @@ -711,7 +711,7 @@ export function calculateLongestStreak(dayKeys: string[]): number { } /** - * v1.18.1 P4 — freeze (don't break) a day-streak across illness days. + * v1.18.1 — freeze (don't break) a day-streak across illness days. * * Rest Mode principle: a streak that lapses ONLY because the user was unwell * should not be broken — being ill is not a failure to track. This pure diff --git a/src/lib/gamification/expansion-metrics.ts b/src/lib/gamification/expansion-metrics.ts index f85e28742..40e30e06f 100644 --- a/src/lib/gamification/expansion-metrics.ts +++ b/src/lib/gamification/expansion-metrics.ts @@ -157,7 +157,7 @@ export function getEngagementMetrics(input: { moodEntries: MoodEntryRecord[]; intakeEvents: IntakeEventRecord[]; /** - * v1.18.11 (W5 perf) — day-keys for `measurements`, precomputed once by + * v1.18.11 — day-keys for `measurements`, precomputed once by * the caller and passed in parallel to the array. When present the * measurement rows reuse these instead of re-deriving a day key per row; * mood / intake timestamps (far fewer) still derive inline. Falls back to @@ -276,7 +276,7 @@ export function getHiddenMetrics(input: { intakeEvents: IntakeEventRecord[]; auditEvents: AuditLogRecord[]; /** - * v1.18.11 (W5 perf) — Berlin day-keys + hours for `measurements`, + * v1.18.11 — Berlin day-keys + hours for `measurements`, * precomputed once by the caller and passed in parallel to the array. When * present the measurement rows reuse these instead of re-running * `Intl.DateTimeFormat` per row for both the hour and the day-key; mood / @@ -395,7 +395,7 @@ export function buildExpansionMetricValues(input: { intakeEvents: IntakeEventRecord[]; auditEvents: AuditLogRecord[]; /** - * v1.18.11 (W5 perf) — Berlin day-keys for `measurements`, precomputed once + * v1.18.11 — Berlin day-keys for `measurements`, precomputed once * by the caller (the achievements builder already derives them for the * green-day / weekly-consistency passes) and threaded into the engagement + * hidden passes so the full vitals array is `Intl`-walked once, not ~3×. diff --git a/src/lib/geo.ts b/src/lib/geo.ts index fcae66f66..86707f29c 100644 --- a/src/lib/geo.ts +++ b/src/lib/geo.ts @@ -64,7 +64,7 @@ * does not hammer ipwho.is — the cache holds both hits and misses * (negative caching) for a bounded TTL. * - * v1.4.16 A8a: the online-fallback body is decoded as UTF-8 explicitly + * v1.4.16: the online-fallback body is decoded as UTF-8 explicitly * via `TextDecoder('utf-8')` instead of `Response.json()`, and an * `Accept-Language: de, en;q=0.5` hint is sent so providers return * native city names ("Nürnberg") rather than ASCII folds ("Nuremberg"). @@ -72,7 +72,7 @@ * maintainer-flagged login row that rendered as "Nrnberg" in * /admin/login-overview — see `docs/audit/v1416-summary.md`. * - * v1.4.27 R5: the build no longer hard-fails on a missing + * v1.4.27: the build no longer hard-fails on a missing * `MAXMIND_LICENSE_KEY` secret — the CI workflow drops an `.empty` * marker into the geo asset directory when the key is unset. * `offlineGeoReady()` is the canonical check used by `/api/version` @@ -205,7 +205,7 @@ export function geoLiteDir(): string { // ── Per-IP location cache ──────────────────────────────────────────── // -// v1.15.12 E1: the online tier is now the baseline path, so a burst of +// v1.15.12: the online tier is now the baseline path, so a burst of // audit events from the same address must not fan out one ipwho.is // request each. We cache the resolved "City, CC" string per IP for a // bounded TTL and also negative-cache misses (stored as null) so a @@ -565,13 +565,13 @@ async function lookupIpOnline(ip: string): Promise { // v1.11.2 — the lookup URL comes from the operator IP_GEO_LOOKUP_URL env; // pin the connect-time DNS check so it can't be pointed at a private / // metadata address (SSRF/rebinding). - // v1.15.12 E1 — keep the online timeout below the audit-log 3 s race + // v1.15.12 — keep the online timeout below the audit-log 3 s race // window so a slow-but-successful lookup still lands before the race // resolves null and drops the location (the "—" symptom on apps01). { timeoutMs: 2_500, requirePublicHost: true }, ); if (!res.ok) { - // v1.18.11 (W3) — a non-ok response (403 free-plan/CORS rejection, + // v1.18.11 — a non-ok response (403 free-plan/CORS rejection, // 429 rate-limit, 5xx) is not a clean "this IP has no location": it is // a provider-level failure that, left silent, renders as "—" with no // signal anywhere. Surface it on the wide event so the next provider diff --git a/src/lib/google-health/__tests__/sync-metrics-failsoft.test.ts b/src/lib/google-health/__tests__/sync-metrics-failsoft.test.ts index 1f0ce79d2..937523c14 100644 --- a/src/lib/google-health/__tests__/sync-metrics-failsoft.test.ts +++ b/src/lib/google-health/__tests__/sync-metrics-failsoft.test.ts @@ -1,5 +1,5 @@ /** - * F-SYNC-3 — the metric MAPPER runs inside a per-type catch, not just the + * The metric MAPPER runs inside a per-type catch, not just the * fetch. A single malformed point whose `resource.map(point)` throws used to * escape the METRIC_RESOURCES loop and skip every metric type ordered after it * (body-fat, spo2, hrv, rhr, respiratory, glucose, temps), also blocking the diff --git a/src/lib/google-health/sync-sleep.ts b/src/lib/google-health/sync-sleep.ts index 4d79e797a..b29d40ac1 100644 --- a/src/lib/google-health/sync-sleep.ts +++ b/src/lib/google-health/sync-sleep.ts @@ -147,7 +147,7 @@ async function syncSleepSessions( ); // `markSynced` is owned by the orchestrator (`syncUserGoogleHealth`). - // S4 — trigger the debounced morning refresh on a last-night segment landing + // Trigger the debounced morning refresh on a last-night segment landing // (mirrors the Withings / WHOOP / Apple seams). Google Health is a first- // class sleep transport, so without this a Google-Health user's morning // refresh never fired and their day stayed stuck at the 04:30 pre-pass. diff --git a/src/lib/i18n/__tests__/relative-time-updated.test.ts b/src/lib/i18n/__tests__/relative-time-updated.test.ts index def03f95d..a04a5491e 100644 --- a/src/lib/i18n/__tests__/relative-time-updated.test.ts +++ b/src/lib/i18n/__tests__/relative-time-updated.test.ts @@ -1,5 +1,5 @@ /** - * v1.22 (W6) — `formatUpdatedLabel`: time only for today, no time for + * v1.22 — `formatUpdatedLabel`: time only for today, no time for * yesterday, locale date for older (honouring the passed formatter). */ import { afterEach, describe, expect, it, vi } from "vitest"; diff --git a/src/lib/i18n/config.ts b/src/lib/i18n/config.ts index b3d26bdb5..d0efb9683 100644 --- a/src/lib/i18n/config.ts +++ b/src/lib/i18n/config.ts @@ -57,7 +57,7 @@ export const localeLabels: Record = { }; /** - * v1.4.25 W9e — locales actively maintained by the project owner. + * v1.4.25 — locales actively maintained by the project owner. * Locales outside this set are AI-initial translations and surface a * notice at the top of the auth shell so users * know to expect rough edges and where to contribute fixes. Promoting diff --git a/src/lib/i18n/relative-time.ts b/src/lib/i18n/relative-time.ts index c15ce8448..70edda1bd 100644 --- a/src/lib/i18n/relative-time.ts +++ b/src/lib/i18n/relative-time.ts @@ -1,5 +1,5 @@ /** - * v1.4.20 phase D reconcile — shared relative-time helper. + * v1.4.20 — shared relative-time helper. * * Formats an ISO timestamp as a localised "just now" / "N minutes ago" * string via i18n keys. Three Insights surfaces (hero strip, daily @@ -8,7 +8,7 @@ * * Buckets: <1m → just-now, <1h → minutes, <24h → hours, else days. * - * v1.4.43 H6 — pluralisation. Each bucket reads a *One (singular) or + * v1.4.43 — pluralisation. Each bucket reads a *One (singular) or * *Other (plural) key, matching the dashboard.staleHintWeeksOne / Other * pattern already in the bundle. Eliminates the "vor 1 Minuten" grammar * bug a German user spotted. @@ -103,7 +103,7 @@ export function relativeCalendarDate( } /** - * v1.22 (W6) — "Updated …" freshness label for the insight surfaces. + * v1.22 — "Updated …" freshness label for the insight surfaces. * * Replaces the bare relative read ("2 hours ago") on the briefing + per-metric * cards with a calendar-bucketed label that shows the TIME only for today: diff --git a/src/lib/i18n/shared-resolve.ts b/src/lib/i18n/shared-resolve.ts index eae146d0d..c7aba9d32 100644 --- a/src/lib/i18n/shared-resolve.ts +++ b/src/lib/i18n/shared-resolve.ts @@ -1,7 +1,7 @@ /** * Server-side message-bundle map + re-export of the shared key resolver. * - * v1.4.27 B7 / BL-P4-11-S10 — client and server previously each carried + * v1.4.27 — client and server previously each carried * their own private copy of `allMessages` plus a near-identical * `resolveKey` helper; pinning both to one import stopped the maps * drifting when a locale was added. diff --git a/src/lib/illness/__tests__/correlation-read.test.ts b/src/lib/illness/__tests__/correlation-read.test.ts index 1bbd00b9a..b2ac8fafe 100644 --- a/src/lib/illness/__tests__/correlation-read.test.ts +++ b/src/lib/illness/__tests__/correlation-read.test.ts @@ -1,5 +1,5 @@ /** - * v1.18.1 P3 — illness correlation READ layer + rollup-vs-live parity. + * v1.18.1 — illness correlation READ layer + rollup-vs-live parity. * * The reliability anchor: the same per-day series fed to the engine must * produce IDENTICAL findings whether it was resolved from the rollup DAY tier diff --git a/src/lib/illness/__tests__/correlation.test.ts b/src/lib/illness/__tests__/correlation.test.ts index a2b31352e..4c7e5cbd9 100644 --- a/src/lib/illness/__tests__/correlation.test.ts +++ b/src/lib/illness/__tests__/correlation.test.ts @@ -1,5 +1,5 @@ /** - * v1.18.1 P3 — illness correlation engine (the pure math). + * v1.18.1 — illness correlation engine (the pure math). * * The reliability battery for `computeIllnessCorrelation`: * - golden-fixture recovery-gap math (felt-better vs physiological return) diff --git a/src/lib/illness/__tests__/rest-mode.test.ts b/src/lib/illness/__tests__/rest-mode.test.ts index 533addb84..29e78a652 100644 --- a/src/lib/illness/__tests__/rest-mode.test.ts +++ b/src/lib/illness/__tests__/rest-mode.test.ts @@ -1,5 +1,5 @@ /** - * v1.18.1 P4 — Rest Mode resolver. + * v1.18.1 — Rest Mode resolver. * * Rest Mode ANNOTATES, never penalises. These tests pin the resolver * contract: module-gated (a disabled / opted-out account is never in Rest diff --git a/src/lib/illness/__tests__/retrospective.test.ts b/src/lib/illness/__tests__/retrospective.test.ts index 3cb4d062e..72401685e 100644 --- a/src/lib/illness/__tests__/retrospective.test.ts +++ b/src/lib/illness/__tests__/retrospective.test.ts @@ -1,5 +1,5 @@ /** - * v1.18.1 P3 — illness retrospective summary aggregation. + * v1.18.1 — illness retrospective summary aggregation. * * Asserts the count/byMonth/byType tallies, the CHRONIC_ONGOING gap exclusion, * and the v1.18.9 signal-density gates: the "typical recovery gap" is WITHHELD diff --git a/src/lib/insights-layout-reorder.ts b/src/lib/insights-layout-reorder.ts index 8a21af264..3bd4ee75c 100644 --- a/src/lib/insights-layout-reorder.ts +++ b/src/lib/insights-layout-reorder.ts @@ -1,5 +1,5 @@ /** - * v1.15.11 W3 — pure reorder helper for the inline Insights edit mode. + * v1.15.11 — pure reorder helper for the inline Insights edit mode. * * Mirrors the `reorderWidgets` contract in * `src/components/settings/dashboard-layout-section.tsx` (move an item from @@ -43,7 +43,7 @@ export function reorderById( } /** - * v1.15.11 QA M2 — rebuild a full tile list after reordering ONLY the Vitals + * v1.15.11 — rebuild a full tile list after reordering ONLY the Vitals * subset, without the non-transitive mixed-key comparator the inline edit mode * used to run. Walks the ORIGINAL `order` of every tile: a non-Vitals tile * (the routed sub-page strip) keeps its slot verbatim; each Vitals slot, in diff --git a/src/lib/insights-layout.ts b/src/lib/insights-layout.ts index 33cad047b..1c0b188ec 100644 --- a/src/lib/insights-layout.ts +++ b/src/lib/insights-layout.ts @@ -146,7 +146,7 @@ export interface InsightsLayout { } /** - * v1.32.21 (R5a) — the resolved layout plus the server-stamped `updatedAt` + * v1.32.21 — the resolved layout plus the server-stamped `updatedAt` * optimistic-concurrency token (mirrors `User.updatedAt`, never persisted * inside the blob). GET and every write response carry it; the client echoes * it back as `baseUpdatedAt` on the next write. Mirrors @@ -413,7 +413,7 @@ function serializeInsightsSections( } /** - * v1.15.11 W2 — the visible section ids in render order, derived from a + * v1.15.11 — the visible section ids in render order, derived from a * resolved layout. Pure helper so the page can map each id onto its JSX in * `SECTION_REGISTRY` without re-implementing the sort/filter inline (and so * the ordering is unit-testable without rendering React). Sections marked @@ -433,7 +433,7 @@ export function orderedVisibleSectionIds( } /** - * v1.15.11 W2c — resolve a single tile's layout decision against a resolved + * v1.15.11 — resolve a single tile's layout decision against a resolved * layout. Returns `{ visible, order }` for a known tile id; for a tile id the * layout does not enumerate (a dashboard tile with no corresponding layout * entry — e.g. a derived re-frame like FITNESS_AGE) the tile is treated as diff --git a/src/lib/insights/__tests__/biomarker-status.test.ts b/src/lib/insights/__tests__/biomarker-status.test.ts index 4cd0bcc60..7c6bed73d 100644 --- a/src/lib/insights/__tests__/biomarker-status.test.ts +++ b/src/lib/insights/__tests__/biomarker-status.test.ts @@ -9,7 +9,7 @@ vi.mock("@/lib/db", () => ({ upsert: vi.fn(), updateMany: vi.fn(), }, - // v1.30.3 (QA F5) — `resolveUserTimezone` reads this directly. + // v1.30.3 — `resolveUserTimezone` reads this directly. user: { findUnique: vi.fn() }, }, })); diff --git a/src/lib/insights/__tests__/blood-pressure-status.test.ts b/src/lib/insights/__tests__/blood-pressure-status.test.ts index 974623e27..43891fcb2 100644 --- a/src/lib/insights/__tests__/blood-pressure-status.test.ts +++ b/src/lib/insights/__tests__/blood-pressure-status.test.ts @@ -143,7 +143,7 @@ describe("generateBloodPressureStatusForUser — graded payload", () => { expect(sys).toHaveProperty("recent"); expect(sys).toHaveProperty("weekly"); expect(sys).toHaveProperty("monthly"); - // v1.18.10 (HIGH-4) — the per-channel SIGNAL block (the finished + // v1.18.10 — the per-channel SIGNAL block (the finished // recent-vs-baseline comparison) now rides the snapshot so the model // states the trend rather than re-deriving it. expect(snapshot.bloodPressure.systolic.signal).toHaveProperty("current"); diff --git a/src/lib/insights/__tests__/bmi-status.test.ts b/src/lib/insights/__tests__/bmi-status.test.ts index 16a1fd4fa..4713332c9 100644 --- a/src/lib/insights/__tests__/bmi-status.test.ts +++ b/src/lib/insights/__tests__/bmi-status.test.ts @@ -16,7 +16,7 @@ vi.mock("@/lib/db", () => ({ upsert: vi.fn(), updateMany: vi.fn(), }, - // v1.18.11 (P6) — the input gate probes salient inputs via groupBy. + // v1.18.11 — the input gate probes salient inputs via groupBy. measurement: { findMany: vi.fn(), groupBy: vi.fn() }, measurementRollup: { findMany: vi.fn() }, // Weekly / monthly / yearly buckets are folded from the DAY tier in SQL. @@ -90,7 +90,7 @@ beforeEach(() => { // folds from the full-history `measurement.findMany` fallback on a miss. vi.mocked(prisma.measurementRollup.findMany).mockResolvedValue([] as never); vi.mocked(prisma.$queryRaw).mockResolvedValue([] as never); - // v1.18.11 (P6) — input-gate probe default: empty groups so the fingerprint + // v1.18.11 — input-gate probe default: empty groups so the fingerprint // is computed but, with no cached `inputHash`, the gate misses and fixtures // build normally. The forced fixtures skip the gate entirely. vi.mocked(prisma.measurement.groupBy).mockResolvedValue([] as never); diff --git a/src/lib/insights/__tests__/chart-tokens.test.ts b/src/lib/insights/__tests__/chart-tokens.test.ts index 13cddcb6b..28dcd5fd2 100644 --- a/src/lib/insights/__tests__/chart-tokens.test.ts +++ b/src/lib/insights/__tests__/chart-tokens.test.ts @@ -57,7 +57,7 @@ describe("stripChartTokens", () => { expect(stripChartTokens(" metric:WEIGHT ")).toBe(""); }); - // v1.4.17 hotfix — defensive: reading `insight.summary` on a legacy + // v1.4.17 — defensive: reading `insight.summary` on a legacy // cached payload (no `summary` field) returns undefined. Crashing // with `Cannot read properties of undefined (reading 'replace')` was // the production /insights bug the maintainer hit on 2026-05-10. Treat @@ -71,7 +71,7 @@ describe("stripChartTokens", () => { }); }); -// v1.4.25 W5b — the maintainer reported the raw "Metric Pressure_Sys" leak on +// v1.4.25 — the maintainer reported the raw "Metric Pressure_Sys" leak on // /insights 2026-05-14. The v1.4.22 W1a regex only caught the canonical // `metric:` colon form; the model emitted a capitalised "Metric" // word followed by a Pascal/enum-cased identifier. The fix extends the diff --git a/src/lib/insights/__tests__/coach-launch-context.test.tsx b/src/lib/insights/__tests__/coach-launch-context.test.tsx index 482a4038f..7eeaedbee 100644 --- a/src/lib/insights/__tests__/coach-launch-context.test.tsx +++ b/src/lib/insights/__tests__/coach-launch-context.test.tsx @@ -4,7 +4,7 @@ import { renderToStaticMarkup } from "react-dom/server"; import type { AccountAccess } from "@/lib/sharing/account-access-view"; /** - * v1.4.27 R3d MB4 — Coach launch context smoke contract. + * v1.4.27 — Coach launch context smoke contract. * * The project's test convention is `renderToStaticMarkup` (no * `@testing-library/react` dependency). We can't simulate interaction diff --git a/src/lib/insights/__tests__/comparison-snapshot-window.test.ts b/src/lib/insights/__tests__/comparison-snapshot-window.test.ts index 56d5b293f..809ed61cc 100644 --- a/src/lib/insights/__tests__/comparison-snapshot-window.test.ts +++ b/src/lib/insights/__tests__/comparison-snapshot-window.test.ts @@ -1,5 +1,5 @@ /** - * v1.20.1 perf — the comparison-snapshot builder must bound its per-type + * v1.20.1 — the comparison-snapshot builder must bound its per-type * measurement read instead of scanning a user's entire history. v1.28.46 * perf — it now computes the two means it actually reads (avg30 + one * baseline mean) as DB-side AVG aggregates over the exact windows, instead diff --git a/src/lib/insights/__tests__/comprehensive-aggregator.test.ts b/src/lib/insights/__tests__/comprehensive-aggregator.test.ts index 131104667..cb1c13dfb 100644 --- a/src/lib/insights/__tests__/comprehensive-aggregator.test.ts +++ b/src/lib/insights/__tests__/comprehensive-aggregator.test.ts @@ -144,7 +144,7 @@ describe("buildComprehensiveAggregate", () => { avg7: 81.9, avg30: 82.1, avg30_last_month: 83.0, - // v1.20.0 F6 — slope / r² no longer ride the narrow aggregate; + // v1.20.0 — slope / r² no longer ride the narrow aggregate; // they compose from the accumulator buckets below. }, ]).mockResolvedValueOnce([ @@ -158,7 +158,7 @@ describe("buildComprehensiveAggregate", () => { // `collapseRollupRowsBySource` can resolve dual-source days; with // a single source per day every bucket passes through unchanged. // - // v1.20.0 F6 — buckets are placed on the two most-recent UTC days so + // v1.20.0 — buckets are placed on the two most-recent UTC days so // they fall inside the 7-day slope window, and carry the regression // accumulators the windowed slope / r² compose from. Each bucket // models its `count` readings as all landing at the bucket's day @@ -213,7 +213,7 @@ describe("buildComprehensiveAggregate", () => { const since7 = startOfUtcDay(new Date(Date.now() - 7 * 86_400_000)); const expectedReg7 = composeWindowedRegression(accBuckets, since7); - // v1.18.10 I-1 — bpRawRows now reads source-collapsed rows via + // v1.18.10 — bpRawRows now reads source-collapsed rows via // `$queryRawUnsafe` (3rd UNSAFE call: narrow, latests, bpRaw). Empty // here since the rollup-fresh fixture doesn't exercise BP pairing. UNSAFE.mockResolvedValueOnce([]); @@ -235,7 +235,7 @@ describe("buildComprehensiveAggregate", () => { expect(weight.avg30).toBe(82.1); expect(weight.avg30LastMonth).toBe(83.0); expect(weight.anomalyCount).toBe(3); - // v1.20.0 F6 — slope7 composes from the accumulator buckets. The two + // v1.20.0 — slope7 composes from the accumulator buckets. The two // recent buckets (means 81 → 83 one day apart) yield a positive, // perfect-fit slope; assert against the closed form the reader uses. expect(expectedReg7.slope).not.toBeNull(); @@ -258,7 +258,7 @@ describe("buildComprehensiveAggregate", () => { // DISTINCT-ON latest moved to `$queryRawUnsafe` (2 calls). Neither // path runs the legacy heavy COUNT/MIN/MAX/AVG query. expect(RAW).toHaveBeenCalledTimes(2); - // v1.18.10 I-1 — narrow + latests + bpRaw all run on `$queryRawUnsafe`. + // v1.18.10 — narrow + latests + bpRaw all run on `$queryRawUnsafe`. expect(UNSAFE).toHaveBeenCalledTimes(3); // bpRawRows no longer uses `measurement.findMany`. expect(FIND_MANY).not.toHaveBeenCalled(); @@ -289,7 +289,7 @@ describe("buildComprehensiveAggregate", () => { expect(result.firstMeasurementAt).toBeNull(); expect(result.totalMeasurements).toBe(0); expect(RAW).toHaveBeenCalledTimes(1); - // v1.18.10 I-1 — heavy + latests + bpRaw all on `$queryRawUnsafe`. + // v1.18.10 — heavy + latests + bpRaw all on `$queryRawUnsafe`. expect(UNSAFE).toHaveBeenCalledTimes(3); expect(FIND_MANY).not.toHaveBeenCalled(); // The cold path's rollup.findMany still fires (in case some @@ -332,7 +332,7 @@ describe("buildComprehensiveAggregate", () => { .mockResolvedValueOnce([ { type: "WEIGHT", value: 81.4, measured_at: now }, ]) - // v1.18.10 I-1 — bpRaw (empty) is the 3rd UNSAFE call. + // v1.18.10 — bpRaw (empty) is the 3rd UNSAFE call. .mockResolvedValueOnce([]); const result = await buildComprehensiveAggregate("user-cold"); @@ -402,7 +402,7 @@ describe("buildComprehensiveAggregate", () => { measured_at: measuredAt, }, ]) - // v1.18.10 I-1 — bpRaw runs on `$queryRawUnsafe` through the + // v1.18.10 — bpRaw runs on `$queryRawUnsafe` through the // source-collapsed canonical subquery. The query returns the // already-collapsed sys + dia rows in raw SQL snake_case shape; the // aggregator partitions by type in JS so the bpRawRows.sys / .dia diff --git a/src/lib/insights/__tests__/comprehensive-briefing-grounding-disposal.test.ts b/src/lib/insights/__tests__/comprehensive-briefing-grounding-disposal.test.ts index 6c4fe80f7..898355e11 100644 --- a/src/lib/insights/__tests__/comprehensive-briefing-grounding-disposal.test.ts +++ b/src/lib/insights/__tests__/comprehensive-briefing-grounding-disposal.test.ts @@ -29,7 +29,7 @@ vi.mock("@/lib/db", () => ({ user: { findUnique: (...a: unknown[]) => findUnique(...a), update: (...a: unknown[]) => userUpdate(...a), - // v1.32.22 (M6) — the cache commit is now a privacy-mode-guarded + // v1.32.22 — the cache commit is now a privacy-mode-guarded // updateMany; record it on the same spy but return a matched count so the // guard proceeds (mode unchanged in these tests). updateMany: (...a: unknown[]) => { diff --git a/src/lib/insights/__tests__/comprehensive-briefing-reroll.test.ts b/src/lib/insights/__tests__/comprehensive-briefing-reroll.test.ts index aef926cff..417cc94f1 100644 --- a/src/lib/insights/__tests__/comprehensive-briefing-reroll.test.ts +++ b/src/lib/insights/__tests__/comprehensive-briefing-reroll.test.ts @@ -1,5 +1,5 @@ /** - * v1.18.7 (MEDIUM-3) — daily-briefing phrasing re-roll on unchanged data. + * v1.18.7 — daily-briefing phrasing re-roll on unchanged data. * * The content-hash gate keeps the FINDINGS byte-stable when the snapshot is * unchanged, but the daily-briefing PARAGRAPH is re-rolled at most once per @@ -29,7 +29,7 @@ vi.mock("@/lib/db", () => ({ user: { findUnique: (...a: unknown[]) => findUnique(...a), update: (...a: unknown[]) => userUpdate(...a), - // v1.32.22 (M6) — the cache commit is now a privacy-mode-guarded + // v1.32.22 — the cache commit is now a privacy-mode-guarded // updateMany; record it on the same spy but return a matched count so the // guard proceeds (mode unchanged in these tests). updateMany: (...a: unknown[]) => { diff --git a/src/lib/insights/__tests__/comprehensive-budget-refusal.test.ts b/src/lib/insights/__tests__/comprehensive-budget-refusal.test.ts index 276565188..1ab2e37d8 100644 --- a/src/lib/insights/__tests__/comprehensive-budget-refusal.test.ts +++ b/src/lib/insights/__tests__/comprehensive-budget-refusal.test.ts @@ -59,7 +59,7 @@ vi.mock("@/lib/db", () => ({ user: { findUnique: (...a: unknown[]) => findUnique(...a), update: (...a: unknown[]) => userUpdate(...a), - // v1.32.22 (M6) — the cache commit is now a privacy-mode-guarded + // v1.32.22 — the cache commit is now a privacy-mode-guarded // updateMany; record it on the same spy but return a matched count so the // guard proceeds (mode unchanged in these tests). updateMany: (...a: unknown[]) => { diff --git a/src/lib/insights/__tests__/comprehensive-generate-force.test.ts b/src/lib/insights/__tests__/comprehensive-generate-force.test.ts index 0ec8ff289..5519de2cd 100644 --- a/src/lib/insights/__tests__/comprehensive-generate-force.test.ts +++ b/src/lib/insights/__tests__/comprehensive-generate-force.test.ts @@ -37,7 +37,7 @@ vi.mock("@/lib/db", () => ({ user: { findUnique: (...a: unknown[]) => findUnique(...a), update: (...a: unknown[]) => userUpdate(...a), - // v1.32.22 (M6) — the cache commit is now a privacy-mode-guarded + // v1.32.22 — the cache commit is now a privacy-mode-guarded // updateMany; record it on the same spy but return a matched count so the // guard proceeds (mode unchanged in these tests). updateMany: (...a: unknown[]) => { diff --git a/src/lib/insights/__tests__/comprehensive-json-retry.test.ts b/src/lib/insights/__tests__/comprehensive-json-retry.test.ts index 15fbfcdfb..dca1bcfe2 100644 --- a/src/lib/insights/__tests__/comprehensive-json-retry.test.ts +++ b/src/lib/insights/__tests__/comprehensive-json-retry.test.ts @@ -1,5 +1,5 @@ /** - * v1.18.7 (MEDIUM-5) — comprehensive JSON-retry robustness. + * v1.18.7 — comprehensive JSON-retry robustness. * * The comprehensive path used to fail cold to `invalid-json` on a first-pass * parse miss. It now reuses `buildRetryCorrectionMessage` for ONE corrective @@ -27,7 +27,7 @@ vi.mock("@/lib/db", () => ({ user: { findUnique: (...a: unknown[]) => findUnique(...a), update: (...a: unknown[]) => userUpdate(...a), - // v1.32.22 (M6) — the cache commit is now a privacy-mode-guarded + // v1.32.22 — the cache commit is now a privacy-mode-guarded // updateMany; record it on the same spy but return a matched count so the // guard proceeds (mode unchanged in these tests). updateMany: (...a: unknown[]) => { diff --git a/src/lib/insights/__tests__/comprehensive-outbound-repair.test.ts b/src/lib/insights/__tests__/comprehensive-outbound-repair.test.ts index 2a2682435..4d6025b50 100644 --- a/src/lib/insights/__tests__/comprehensive-outbound-repair.test.ts +++ b/src/lib/insights/__tests__/comprehensive-outbound-repair.test.ts @@ -30,7 +30,7 @@ vi.mock("@/lib/db", () => ({ user: { findUnique: (...a: unknown[]) => findUnique(...a), update: (...a: unknown[]) => userUpdate(...a), - // v1.32.22 (M6) — the cache commit is now a privacy-mode-guarded + // v1.32.22 — the cache commit is now a privacy-mode-guarded // updateMany; record it on the same spy but return a matched count so the // guard proceeds (mode unchanged in these tests). updateMany: (...a: unknown[]) => { diff --git a/src/lib/insights/__tests__/comprehensive-plan-progress.test.ts b/src/lib/insights/__tests__/comprehensive-plan-progress.test.ts index 068edaf70..98c1501e7 100644 --- a/src/lib/insights/__tests__/comprehensive-plan-progress.test.ts +++ b/src/lib/insights/__tests__/comprehensive-plan-progress.test.ts @@ -23,7 +23,7 @@ vi.mock("@/lib/db", () => ({ user: { findUnique: (...a: unknown[]) => findUnique(...a), update: (...a: unknown[]) => userUpdate(...a), - // v1.32.22 (M6) — the cache commit is now a privacy-mode-guarded + // v1.32.22 — the cache commit is now a privacy-mode-guarded // updateMany; record it on the same spy but return a matched count so the // guard proceeds (mode unchanged in these tests). updateMany: (...a: unknown[]) => { diff --git a/src/lib/insights/__tests__/comprehensive-timeout-honored.test.ts b/src/lib/insights/__tests__/comprehensive-timeout-honored.test.ts index 3209f8ca7..fb77da31c 100644 --- a/src/lib/insights/__tests__/comprehensive-timeout-honored.test.ts +++ b/src/lib/insights/__tests__/comprehensive-timeout-honored.test.ts @@ -31,7 +31,7 @@ vi.mock("@/lib/db", () => ({ user: { findUnique: (...a: unknown[]) => findUnique(...a), update: (...a: unknown[]) => userUpdate(...a), - // v1.32.22 (M6) — the cache commit is now a privacy-mode-guarded + // v1.32.22 — the cache commit is now a privacy-mode-guarded // updateMany; record it on the same spy but return a matched count so the // guard proceeds (mode unchanged in these tests). updateMany: (...a: unknown[]) => { diff --git a/src/lib/insights/__tests__/comprehensive-warm-timeout.test.ts b/src/lib/insights/__tests__/comprehensive-warm-timeout.test.ts index a171492a5..b1f089d00 100644 --- a/src/lib/insights/__tests__/comprehensive-warm-timeout.test.ts +++ b/src/lib/insights/__tests__/comprehensive-warm-timeout.test.ts @@ -33,7 +33,7 @@ vi.mock("@/lib/db", () => ({ user: { findUnique: (...a: unknown[]) => findUnique(...a), update: (...a: unknown[]) => userUpdate(...a), - // v1.32.22 (M6) — the cache commit is now a privacy-mode-guarded + // v1.32.22 — the cache commit is now a privacy-mode-guarded // updateMany; record it on the same spy but return a matched count so the // guard proceeds (mode unchanged in these tests). updateMany: (...a: unknown[]) => { diff --git a/src/lib/insights/__tests__/correlations.test.ts b/src/lib/insights/__tests__/correlations.test.ts index e7f3d12ed..dee74ffc9 100644 --- a/src/lib/insights/__tests__/correlations.test.ts +++ b/src/lib/insights/__tests__/correlations.test.ts @@ -11,7 +11,7 @@ import { import { seededRng } from "./helpers/seeded-series"; /** - * v1.4.20 phase B3 — correlation discovery. + * v1.4.20 — correlation discovery. * * Three pre-defined hypotheses are surfaced as `` rows. * Quality bar is non-negotiable: n >= 20 (v1.4.23 H6 raise), p < 0.05. @@ -70,7 +70,7 @@ describe("pearson", () => { }); it("returns r near 0 for noise, with p above the surface threshold", () => { - // v1.4.26 P6-1 — fixture regenerated against the exact incomplete-beta + // v1.4.26 — fixture regenerated against the exact incomplete-beta // p-value. The pre-existing jittered fixture happened to produce // r ≈ -0.494, which is a real moderate correlation; the old normal // approximation was loose enough that p still sat above 0.05, but @@ -95,7 +95,7 @@ describe("pearson", () => { }); /** - * v1.4.23 H6 — pin the surfacing-gate raise. n=15 is the borderline + * v1.4.23 — pin the surfacing-gate raise. n=15 is the borderline * case the v1.4.22 product-lead memo flagged: under the old n>=14 * gate the normal-approx p-value would surface a card; under the * new n>=20 gate the call short-circuits to `insufficient` so a @@ -113,7 +113,7 @@ describe("pearson", () => { }); /** - * v1.4.26 P6-1 — exact Student's-t p-value via regularised + * v1.4.26 — exact Student's-t p-value via regularised * incomplete beta. Pins three R-derived reference points so a * future refactor of the survival function can't quietly drift * back to the normal approximation. diff --git a/src/lib/insights/__tests__/glp1-plateau.test.ts b/src/lib/insights/__tests__/glp1-plateau.test.ts index 4ee91d935..9434f9dc5 100644 --- a/src/lib/insights/__tests__/glp1-plateau.test.ts +++ b/src/lib/insights/__tests__/glp1-plateau.test.ts @@ -249,7 +249,7 @@ describe("glp1-plateau", () => { expect(prompt).toContain("week 4"); }); - // v1.4.25 W10 reconcile (security H-1) — patient-safety regression + // v1.4.25 (security H-1) — patient-safety regression // guard: a malicious medication name must not be able to inject a // multi-line break that lets the LLM read the trailing text as a // new instruction block. diff --git a/src/lib/insights/__tests__/metric-availability.test.ts b/src/lib/insights/__tests__/metric-availability.test.ts index 9b6575167..728502d34 100644 --- a/src/lib/insights/__tests__/metric-availability.test.ts +++ b/src/lib/insights/__tests__/metric-availability.test.ts @@ -4,7 +4,7 @@ import { hasMetricData } from "../metric-availability"; import type { DataSummary } from "@/lib/analytics/trends"; /** - * v1.4.27 F17/F18/F19 — gating helper unit cover. + * v1.4.27 — gating helper unit cover. * * The matrix covers each metric × {has data, no data, missing * summary entry, undefined summaries}. The `MOOD` and `MEDICATION` diff --git a/src/lib/insights/__tests__/payload-budget.test.ts b/src/lib/insights/__tests__/payload-budget.test.ts index 717ed105b..2c26eb996 100644 --- a/src/lib/insights/__tests__/payload-budget.test.ts +++ b/src/lib/insights/__tests__/payload-budget.test.ts @@ -28,7 +28,7 @@ vi.mock("@/lib/db", () => ({ upsert: vi.fn(), updateMany: vi.fn(), }, - // v1.18.11 (P6) — the slow-mover input gate probes salient inputs. + // v1.18.11 — the slow-mover input gate probes salient inputs. measurement: { findMany: vi.fn(), groupBy: vi.fn() }, measurementRollup: { findMany: vi.fn() }, // Weekly / monthly / yearly buckets are folded from the DAY tier in SQL. @@ -112,7 +112,7 @@ beforeEach(() => { ); vi.mocked(prisma.measurementRollup.findMany).mockResolvedValue([] as never); vi.mocked(prisma.$queryRaw).mockResolvedValue([] as never); - // v1.18.11 (P6) — input-gate probe: empty groups + zero mood so the gate + // v1.18.11 — input-gate probe: empty groups + zero mood so the gate // misses (no cached inputHash) and every fixture builds normally. vi.mocked(prisma.measurement.groupBy).mockResolvedValue([] as never); vi.mocked(prisma.moodEntry.aggregate).mockResolvedValue({ diff --git a/src/lib/insights/__tests__/recovery-score.test.ts b/src/lib/insights/__tests__/recovery-score.test.ts index 12237c9aa..b889cdb56 100644 --- a/src/lib/insights/__tests__/recovery-score.test.ts +++ b/src/lib/insights/__tests__/recovery-score.test.ts @@ -31,7 +31,7 @@ vi.mock("@/lib/insights/derived/readiness", () => ({ computeReadiness: (...args: unknown[]) => computeReadinessMock(...args), })); -// v1.18.1 P4 — Rest Mode resolver. Default inactive; the annotation test +// v1.18.1 — Rest Mode resolver. Default inactive; the annotation test // flips it active to assert the score is NOT penalised, only annotated. const restModeMock = vi.hoisted(() => ({ resolveRestMode: vi.fn(async () => ({ diff --git a/src/lib/insights/__tests__/score-row.test.ts b/src/lib/insights/__tests__/score-row.test.ts index 68b211f84..fc606b84b 100644 --- a/src/lib/insights/__tests__/score-row.test.ts +++ b/src/lib/insights/__tests__/score-row.test.ts @@ -1,5 +1,5 @@ /** - * v1.10.0 QA — shared score-row helpers. + * v1.10.0 — shared score-row helpers. * * Pins the "score the PREVIOUS UTC day" contract (the cron fires ~03 UTC, so * scoring the current UTC day would miss a just-completed day's data), the diff --git a/src/lib/insights/__tests__/status-cache.test.ts b/src/lib/insights/__tests__/status-cache.test.ts index a6a36064f..cfd3515d0 100644 --- a/src/lib/insights/__tests__/status-cache.test.ts +++ b/src/lib/insights/__tests__/status-cache.test.ts @@ -8,7 +8,7 @@ vi.mock("@/lib/db", () => ({ upsert: vi.fn(), updateMany: vi.fn(), }, - // v1.18.11 (P6) — the input gate / fingerprint probe salient inputs. + // v1.18.11 — the input gate / fingerprint probe salient inputs. measurement: { groupBy: vi.fn() }, moodEntry: { aggregate: vi.fn() }, customMetric: { findMany: vi.fn() }, diff --git a/src/lib/insights/__tests__/streak-detector.test.ts b/src/lib/insights/__tests__/streak-detector.test.ts index ee0448ae9..be33192f6 100644 --- a/src/lib/insights/__tests__/streak-detector.test.ts +++ b/src/lib/insights/__tests__/streak-detector.test.ts @@ -7,7 +7,7 @@ import { } from "@/lib/insights/streak-detector"; /** - * v1.21.2 (A6) — determinism pins for the streak / return-to-baseline detector. + * v1.21.2 — determinism pins for the streak / return-to-baseline detector. * * The PROSE the Coach narrates is not tested; the FLAGS are. These assert: * - an in-band streak counts consecutive in-band days, diff --git a/src/lib/insights/__tests__/sub-page-manager-group.test.ts b/src/lib/insights/__tests__/sub-page-manager-group.test.ts index 82c339234..793a0a54d 100644 --- a/src/lib/insights/__tests__/sub-page-manager-group.test.ts +++ b/src/lib/insights/__tests__/sub-page-manager-group.test.ts @@ -9,7 +9,7 @@ import { } from "@/lib/insights/sub-page-metric"; /** - * v1.15.14 W2 — the "Anpassen" sub-page manager lists EVERY routed + * v1.15.14 — the "Anpassen" sub-page manager lists EVERY routed * sub-page slug grouped by category. These guards pin the total grouping * so a future slug can never silently drop out of the manager. */ diff --git a/src/lib/insights/__tests__/trend-chart-select.test.ts b/src/lib/insights/__tests__/trend-chart-select.test.ts index 73162857a..9e698087f 100644 --- a/src/lib/insights/__tests__/trend-chart-select.test.ts +++ b/src/lib/insights/__tests__/trend-chart-select.test.ts @@ -121,7 +121,7 @@ describe("selectTrendCharts", () => { }); it("every chart-bearing sourceMetric carries a standard caption key", () => { - // v1.8.6 W8 — a tile without an advisor annotation used to paint + // v1.8.6 — a tile without an advisor annotation used to paint // empty space below the chart. Every chart config now carries a // `captionKey` so the renderer can always surface a caption. This // guard keeps a future metric addition from re-opening the gap. diff --git a/src/lib/insights/__tests__/trend-descriptor.test.ts b/src/lib/insights/__tests__/trend-descriptor.test.ts index cf7258f7b..4070336ef 100644 --- a/src/lib/insights/__tests__/trend-descriptor.test.ts +++ b/src/lib/insights/__tests__/trend-descriptor.test.ts @@ -8,7 +8,7 @@ import { } from "../trend-descriptor"; /** - * v1.11.4 item J — deterministic Trends-row caption descriptor. + * v1.11.4 — deterministic Trends-row caption descriptor. * * The helper turns the same series the mini-chart plots into a neutral, * observational direction + magnitude. These tests pin the direction diff --git a/src/lib/insights/__tests__/weight-status.test.ts b/src/lib/insights/__tests__/weight-status.test.ts index f563bd7d5..65d5ed05f 100644 --- a/src/lib/insights/__tests__/weight-status.test.ts +++ b/src/lib/insights/__tests__/weight-status.test.ts @@ -15,7 +15,7 @@ vi.mock("@/lib/db", () => ({ upsert: vi.fn(), updateMany: vi.fn(), }, - // v1.18.11 (P6) — the input gate probes salient inputs via groupBy + + // v1.18.11 — the input gate probes salient inputs via groupBy + // moodEntry.aggregate before the heavy findMany build. measurement: { findMany: vi.fn(), groupBy: vi.fn() }, measurementRollup: { findMany: vi.fn() }, @@ -107,7 +107,7 @@ beforeEach(() => { vi.mocked(prisma.$queryRaw).mockResolvedValue([] as never); // v1.11.1 — null source-priority blob → default rank ladders. vi.mocked(prisma.user.findUnique).mockResolvedValue(null as never); - // v1.18.11 (P6) — input-gate probe. Default to empty groups + zero mood so + // v1.18.11 — input-gate probe. Default to empty groups + zero mood so // the fingerprint is computed but, with no cached `inputHash`, the gate // misses and every fixture proceeds to its normal build. Tests that // exercise the gate set these explicitly. diff --git a/src/lib/insights/biomarker-status.ts b/src/lib/insights/biomarker-status.ts index 17ca5636f..c14ae65db 100644 --- a/src/lib/insights/biomarker-status.ts +++ b/src/lib/insights/biomarker-status.ts @@ -128,7 +128,7 @@ export async function generateBiomarkerStatus(args: { const readOnly = args.readOnly === true; const scope = biomarkerStatusScope(marker.id); const cacheAction = statusCacheAction(scope, locale); - // v1.30.3 (QA F5) — resolve the user's own tz BEFORE the day-key so the + // v1.30.3 — resolve the user's own tz BEFORE the day-key so the // cache rolls over at the user's local midnight, not Berlin's. Has to // happen ahead of the cache read below (the earliest possible return // path). diff --git a/src/lib/insights/blood-pressure-status.ts b/src/lib/insights/blood-pressure-status.ts index e90d9f602..463073a7e 100644 --- a/src/lib/insights/blood-pressure-status.ts +++ b/src/lib/insights/blood-pressure-status.ts @@ -147,7 +147,7 @@ export async function generateBloodPressureStatusForUser( } /** - * v1.18.7 (HIGH-1) — everything up to (not including) the provider call. + * v1.18.7 — everything up to (not including) the provider call. * See `status-card-generation.ts` for the prepare/run/finalize contract. */ export async function prepareBloodPressureStatusForUser( @@ -163,7 +163,7 @@ export async function prepareBloodPressureStatusForUser( const force = options?.force === true; const readOnly = options?.readOnly === true; const cacheAction = statusCacheAction("blood-pressure", locale); - // v1.30.3 (QA F5) — resolve the user's own tz BEFORE the day-key so the + // v1.30.3 — resolve the user's own tz BEFORE the day-key so the // cache rolls over at the user's local midnight, not Berlin's. Has to // happen ahead of the cache read below (the earliest possible return // path), not just the later day-bucketing reads. @@ -230,7 +230,7 @@ export async function prepareBloodPressureStatusForUser( }, }); - // v1.4.28 FB-D2 — cap the snapshot input. The downstream + // v1.4.28 — cap the snapshot input. The downstream // `applyPayloadBudget` trims further; this `take` keeps the read // bounded for Apple-Health-rich accounts. BP captures three types // per reading day so 1095 = 365 d × 3 channels. @@ -254,7 +254,7 @@ export async function prepareBloodPressureStatusForUser( const now = new Date(); - // v1.7.0 SB-SCHED-2 / v1.2.5 (M-TZ3) — BOTH the BP-status compliance gate + // v1.7.0 v1.2.5 — BOTH the BP-status compliance gate // AND every day-bucketing pass below (applyPayloadBudget, pairDailyBuckets, // the continuity series) key on the user's own calendar day, not Berlin's. // `userTz` was already resolved above for the cache day-key. @@ -302,7 +302,7 @@ export async function prepareBloodPressureStatusForUser( inTarget: bpTargets == null ? null - : // v1.4.16 A2 — one-sided "at or below ceiling" semantics with a + : // v1.4.16 — one-sided "at or below ceiling" semantics with a // clinical hypotension floor. See lib/analytics/bp-in-target.ts. isBpReadingInTarget(entry.a, entry.b, bpTargets), })); @@ -338,7 +338,7 @@ export async function prepareBloodPressureStatusForUser( schedules: { select: SCHEDULE_COMPLIANCE_SELECT }, // v1.16.3 — archived schedule eras for era-aware compliance. scheduleRevisions: { orderBy: { validFrom: "asc" } }, - // v1.25 H-MED1 — pause eras so paused days drop out of the denominator. + // v1.25 — pause eras so paused days drop out of the denominator. pauseEras: { select: { pausedAt: true, resumedAt: true } }, // v1.40 (#1024) — the courses, so a gap between two expects nothing. courses: COURSES_COMPLIANCE_SELECT, @@ -455,7 +455,7 @@ export async function prepareBloodPressureStatusForUser( }); // Fetch mood context (optional — for enrichment only). v1.4.28 - // FB-D2 — cap at 90 entries (~3 months) so power users don't pull + // Cap at 90 entries (~3 months) so power users don't pull // unbounded rows before the bucket-series budget runs. const moodEntries = await prisma.moodEntry .findMany({ @@ -538,7 +538,7 @@ export async function prepareBloodPressureStatusForUser( ) : null; - // v1.18.10 (HIGH-4) — hand the model the finished recent-vs-baseline + // v1.18.10 — hand the model the finished recent-vs-baseline // comparison + normal-swing verdict per channel instead of asking it to // derive them. BP needs two signals; the user's own targets are the coarse // band anchor when present. diff --git a/src/lib/insights/bmi-status.ts b/src/lib/insights/bmi-status.ts index 06f213942..2a3ae4955 100644 --- a/src/lib/insights/bmi-status.ts +++ b/src/lib/insights/bmi-status.ts @@ -84,7 +84,7 @@ export async function generateBmiStatusForUser( } /** - * v1.18.7 (HIGH-1) — everything up to (not including) the provider call. + * v1.18.7 — everything up to (not including) the provider call. * Returns a finished `served` result for every path that never needed the * LLM, or a `pending` descriptor the single-card path and the batch path * both drive. See `status-card-generation.ts` for the contract. @@ -102,7 +102,7 @@ export async function prepareBmiStatusForUser( const force = options?.force === true; const readOnly = options?.readOnly === true; const cacheAction = statusCacheAction("bmi", locale); - // v1.30.3 (QA F5) — resolve the user's own tz BEFORE the day-key so the + // v1.30.3 — resolve the user's own tz BEFORE the day-key so the // cache rolls over at the user's local midnight, not Berlin's. Has to // happen ahead of the cache read below (the earliest possible return // path), not just the later day-bucketing reads. @@ -184,7 +184,7 @@ export async function prepareBmiStatusForUser( }; } - // v1.18.11 (P6) — input gate for this slow-moving metric. The snapshot + // v1.18.11 — input gate for this slow-moving metric. The snapshot // derives from WEIGHT rows and the profile heightCm; a cheap probe over // both fingerprints the inputs, so a non-forced unchanged week re-stamps // the cached assessment and skips the whole build (findMany + BMI series + @@ -216,7 +216,7 @@ export async function prepareBmiStatusForUser( } } - // v1.4.28 FB-D2 — cap the snapshot input (weight runs at most once + // v1.4.28 — cap the snapshot input (weight runs at most once // per day for typical users; 365 covers a full year while the // downstream payload budget trims further). const measurements = await prisma.measurement @@ -237,7 +237,7 @@ export async function prepareBmiStatusForUser( .then((rows) => rows.reverse()); const now = new Date(); - // v1.2.5 (M-TZ3) — bucket day windows on the user's own calendar so a + // v1.2.5 — bucket day windows on the user's own calendar so a // near-midnight reading lands on the user's day for non-Berlin self-hosters. // `userTz` was already resolved above for the cache day-key. const heightFactor = (user.heightCm / 100) ** 2; @@ -289,7 +289,7 @@ export async function prepareBmiStatusForUser( ) : null; - // v1.18.10 (HIGH-4) — hand the model the finished recent-vs-baseline + // v1.18.10 — hand the model the finished recent-vs-baseline // comparison + normal-swing verdict instead of asking it to derive them. // BMI reads against the WHO healthy band; "target-band" framing. const bmiSignal = buildMetricSignal({ @@ -464,7 +464,7 @@ export async function prepareBmiStatusForUser( todayKey, text: summary, snapshotHash, - // v1.18.11 (P6) — persist the input fingerprint for tomorrow's gate. + // v1.18.11 — persist the input fingerprint for tomorrow's gate. inputHash, }); return { diff --git a/src/lib/insights/chart-tokens.ts b/src/lib/insights/chart-tokens.ts index 02f6da8fc..56c0c470c 100644 --- a/src/lib/insights/chart-tokens.ts +++ b/src/lib/insights/chart-tokens.ts @@ -43,7 +43,7 @@ export const ALLOWED_CHART_TOKENS = [ "metric:WALKING_RUNNING_DISTANCE", "metric:VO2_MAX", "metric:BODY_TEMPERATURE", - // v1.4.25 W5d — Withings full coverage. Same posture as v1.4.23 — the + // v1.4.25 — Withings full coverage. Same posture as v1.4.23 — the // LLM can reference these in prose; dedicated chart components land in // v1.5 when the Insights body-composition + cardiovascular sub-pages // are designed. @@ -54,7 +54,7 @@ export const ALLOWED_CHART_TOKENS = [ "metric:PULSE_WAVE_VELOCITY", "metric:VASCULAR_AGE", "metric:VISCERAL_FAT", - // v1.4.25 W8d — Apple Health server-prep. Same posture as v1.4.23 / + // v1.4.25 — Apple Health server-prep. Same posture as v1.4.23 / // W5d: the LLM can reference these in prose; the corresponding // dedicated chart components land alongside the iOS-app sync. "metric:AUDIO_EXPOSURE_ENV", @@ -147,7 +147,7 @@ export type ChartToken = (typeof ALLOWED_CHART_TOKENS)[number]; const ALLOWED_SET = new Set(ALLOWED_CHART_TOKENS); -// v1.4.19 A3 — strip vs parse split. The strip-side character class +// v1.4.19 — strip vs parse split. The strip-side character class // is permissive (`[A-Za-z0-9_]+`) so model-emitted lowercase or // snake_case tokens like `metric:blood_pressure_sweet_spot` (verbatim // maintainer probe 2026-05-10) get cleaved out of prose instead of leaking to the @@ -162,7 +162,7 @@ const ALLOWED_SET = new Set(ALLOWED_CHART_TOKENS); const STRIP_TOKEN_REGEX = /metric:[A-Za-z0-9_]+/g; const PARSE_TOKEN_REGEX = /metric:[A-Z_]+/g; -// v1.4.25 W5b — capitalised-Metric form. The maintainer reported the leak +// v1.4.25 — capitalised-Metric form. The maintainer reported the leak // pattern `Metric Pressure_Sys` on /insights 2026-05-14: the model // emitted the metric reference as a two-word phrase ("Metric" + space // + enum-cased identifier) instead of the canonical `metric:` @@ -179,7 +179,7 @@ const PARSE_TOKEN_REGEX = /metric:[A-Z_]+/g; // ordinary prose ("Metricated", "submetric_x"). const METRIC_WORD_REGEX = /\bMetric\s+[A-Za-z][A-Za-z0-9_]*\b/g; -// v1.4.25 W5b — orphan enum-identifier leaks. The model sometimes +// v1.4.25 — orphan enum-identifier leaks. The model sometimes // drops the bare MeasurementType enum name straight into prose // ("Your BLOOD_PRESSURE_SYS is elevated"). This was harmless when // the only enums were short capitalised words but leaks badly with @@ -243,7 +243,7 @@ const ORPHAN_ENUM_REGEX = new RegExp( * are in the allowlist. Hallucinated tokens (e.g. `metric:NUKE`) are * silently dropped. * - * v1.4.17 hotfix: legacy cached payloads from before the strict insight + * v1.4.17: legacy cached payloads from before the strict insight * schema landed can deliver `undefined` here when the consumer reads a * field that didn't exist in the v1.4.14 shape (e.g. `insight.summary` * on a `{changed, stable, drivers, ...}` blob). Rather than crashing @@ -262,7 +262,7 @@ export function parseChartTokens( /** * Strip the chart tokens out of the visible insight text. * - * v1.4.17 hotfix: same defensive contract as `parseChartTokens()` — + * v1.4.17: same defensive contract as `parseChartTokens()` — * non-string input returns the empty string instead of crashing the * caller. The empty result keeps the surrounding JSX render path * (`

{stripChartTokens(insight.summary)}

`) alive so the legacy- diff --git a/src/lib/insights/coach-launch-context.tsx b/src/lib/insights/coach-launch-context.tsx index fc5689729..4491590b7 100644 --- a/src/lib/insights/coach-launch-context.tsx +++ b/src/lib/insights/coach-launch-context.tsx @@ -14,7 +14,7 @@ import { useRecordCapabilities } from "@/hooks/use-record-capabilities"; import type { CoachScopeSource, CoachScopeWindow } from "@/lib/ai/coach/types"; /** - * v1.4.27 R3d MB4 — Coach launch context. + * v1.4.27 — Coach launch context. * * Until v1.4.27 the Coach drawer was mounted inline on * `/insights/page.tsx` body, which meant navigating to a routed @@ -38,13 +38,13 @@ export interface CoachLaunchScope { /** * Optional metric the user is looking at when they open the Coach. * - * v1.4.28 R3c (BK-MED-2 / BK-F-M4) — narrowed from `string` to the + * v1.4.28 — narrowed from `string` to the * `CoachScopeSource` union so call sites cannot drift to a free-form * label that the sources rail then silently ignores. Adding a new * Apple-Health source to the rail (the union already covers the * v1.4.23 additions) automatically widens the allowed values here. * - * v1.21.0 (C4 H1/H4) — now LIVE: the scope threads through the drawer + * v1.21.0 — now LIVE: the scope threads through the drawer * into the chat request so a conversation opened from a metric surface * or an insight card is pre-narrowed to the relevant source(s). The * `void scope` discard is gone. @@ -120,7 +120,7 @@ interface CoachLaunchValue { workoutId?: string | null, ) => void; /** - * v1.21.0 (C4 H1) — register the metric surface the user is currently + * v1.21.0 — register the metric surface the user is currently * looking at so the global FAB (which calls `askCoach()` with no args) * still opens the Coach pre-scoped to that page. The optional * `seedPrefill` is the composer opener the FAB seeds when it inherits @@ -243,7 +243,7 @@ export function CoachLaunchProvider({ children }: CoachLaunchProviderProps) { nextDocumentId?: string | null, nextWorkoutId?: string | null, ) => { - // v1.21.0 (C4 H1/H4) — scope is live; v1.28.52 — document scope threads + // v1.21.0 — scope is live; v1.28.52 — document scope threads // through so the vault "Ask the Coach" action opens the drawer scoped to // the document instead of a full-page nav. Precedence lives in the pure // `resolveLaunchState` helper so the rules stay unit-testable. diff --git a/src/lib/insights/comprehensive-aggregator.ts b/src/lib/insights/comprehensive-aggregator.ts index d6b27a377..553952565 100644 --- a/src/lib/insights/comprehensive-aggregator.ts +++ b/src/lib/insights/comprehensive-aggregator.ts @@ -194,7 +194,7 @@ interface NarrowAggregateRow { avg7: number | null; avg30: number | null; avg30_last_month: number | null; - // v1.20.0 F6 — the slope7/30/90 + r2_7/30/90 columns have moved off this + // v1.20.0 — the slope7/30/90 + r2_7/30/90 columns have moved off this // live query onto the rollup accumulators (composeWindowedRegression); // they no longer scan the raw measurements partition on the warm path. } @@ -282,13 +282,13 @@ export async function buildComprehensiveAggregate( ): Promise { const ninetyDaysAgo = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000); - // v1.4.38 W-F — per-sub-query wall-clock timing. Emitted under + // v1.4.38 — per-sub-query wall-clock timing. Emitted under // `meta.insights.sub_*_ms` so the next perf-verify can attribute // a regression to a specific aggregate (narrow, latests, day-buckets, // bp-raw, first-at, coverage-probe) without re-instrumenting. const timings: Record = {}; - // v1.4.37.1 hotfix — fire-and-forget. See `src/app/api/analytics/route.ts` + // v1.4.37.1 — fire-and-forget. See `src/app/api/analytics/route.ts` // for the full rationale: awaiting this on the read path can stall // the event loop for 30–60 s on power-user accounts whose iOS step // samples keep the 90-day window slightly stale. The downstream @@ -297,7 +297,7 @@ export async function buildComprehensiveAggregate( // for the background refresh. void ensureUserRollupsFresh(userId); - // v1.4.36 QA C1 — per-type coverage probe. The legacy global COUNT + // v1.4.36 — per-type coverage probe. The legacy global COUNT // returned true as soon as any one type had a DAY bucket, which made // the bucket-derived path swallow a brand-new type's all-time history // (the per-write hook upserts a single bucket for the new type, the @@ -320,7 +320,7 @@ export async function buildComprehensiveAggregate( } /** - * v1.4.38 W-F — tiny timing helper. Wraps a promise-returning sub-query + * v1.4.38 — tiny timing helper. Wraps a promise-returning sub-query * builder so each Promise.all slot lands a wall-clock observation under * `meta.insights.sub_