" 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) {
{t("insights.correlationRow.experimentCta")}
- {/* v1.21.0 (C4 H2) — the live Coach hand-off sits beside the
+ {/* v1.21.0 — the live Coach hand-off sits beside the
roadmap CTA: it scopes to both sources in the pair. */}
` by `BriefingRow`) so the user need not hit the small headline text.
function KeyFindingRow({ finding }: { finding: DailyBriefingKeyFinding }) {
return (
@@ -419,7 +419,7 @@ export function DailyBriefing({
memory read as a second briefing paragraph above the
structured list and pulled the card down; the server
still resolves `briefingMemory` for other consumers. */}
- {/* v1.4.27 B1 — the leading narrative paragraph dropped.
+ {/* v1.4.27 — the leading narrative paragraph dropped.
The hero strip subtitle on `/insights` already renders
the same `briefing.paragraph` text directly above this
card, so the user used to read the same string twice
@@ -537,7 +537,7 @@ export function DailyBriefing({
}
action={
onRegenerate ? (
- // v1.4.28 BK-M2 — switch the empty-state CTA from
+ // v1.4.28 — switch the empty-state CTA from
// `variant="outline"` to the default (filled) variant
// so the briefing card matches the dashboard
// empty-state CTA shape. Empty-state actions on a card
diff --git a/src/components/insights/derived/__tests__/score-anatomy-view.test.tsx b/src/components/insights/derived/__tests__/score-anatomy-view.test.tsx
index 54b488789..b1f5f1819 100644
--- a/src/components/insights/derived/__tests__/score-anatomy-view.test.tsx
+++ b/src/components/insights/derived/__tests__/score-anatomy-view.test.tsx
@@ -57,7 +57,7 @@ describe("", () => {
expect(html).toContain('data-present="false"');
expect(html).toContain('data-slot="provenance-explainer-method"');
expect(html).toContain("A blend of deviation components.");
- // v1.15.12 F4 — the "keine klinische Bewertung" footer line is removed;
+ // v1.15.12 — the "keine klinische Bewertung" footer line is removed;
// the non-clinical framing now lives in the inline provenance method.
expect(html).not.toContain('data-slot="score-anatomy-disclaimer"');
// Regression guard: `ProvenanceExplainer` renders its full method body
diff --git a/src/components/insights/derived/coach-read-strip.tsx b/src/components/insights/derived/coach-read-strip.tsx
index 2f15a960c..2edc88416 100644
--- a/src/components/insights/derived/coach-read-strip.tsx
+++ b/src/components/insights/derived/coach-read-strip.tsx
@@ -18,7 +18,7 @@ import { TileHeader } from "@/components/insights/tile-header";
import type { CoachReadStripData } from "@/lib/insights/derived/coach-read-shape";
/**
- * v1.21.2 (A1) — "Coach read" strip.
+ * v1.21.2 — "Coach read" strip.
*
* A compact two-line read rendered ABOVE the chart on each metric sub-page:
*
diff --git a/src/components/insights/derived/composite-score-anatomy.tsx b/src/components/insights/derived/composite-score-anatomy.tsx
index 51b8819ea..81a94aaae 100644
--- a/src/components/insights/derived/composite-score-anatomy.tsx
+++ b/src/components/insights/derived/composite-score-anatomy.tsx
@@ -59,7 +59,7 @@ export interface CompositeScoreAnatomyProps {
}
/**
- * v1.15.12 F1 — each anatomy metric maps to the same ring hue its dashboard
+ * v1.15.12 — each anatomy metric maps to the same ring hue its dashboard
* tile wears, so the detail card carries the matching tint + the ring arc
* leans the same colour (continuity from the tile tap). Mirrors the per-metric
* `hue` the wellness strip passes its `RingTile`s.
@@ -293,7 +293,7 @@ export function CompositeScoreAnatomy({
}
}
- // v1.15.12 B3 — the per-score AI assessment ("why is this score what it is")
+ // v1.15.12 — the per-score AI assessment ("why is this score what it is")
// already rides the single `/api/insights/derived` route as `data.assessment`
// (shipped to iOS in v1.13.2); the web simply discarded it. Render it BELOW
// the anatomy card, reusing the same `` the metric
diff --git a/src/components/insights/derived/score-anatomy-view.tsx b/src/components/insights/derived/score-anatomy-view.tsx
index de42563b3..d926ec04b 100644
--- a/src/components/insights/derived/score-anatomy-view.tsx
+++ b/src/components/insights/derived/score-anatomy-view.tsx
@@ -74,7 +74,7 @@ export interface ScoreAnatomyViewProps {
/** Band override; derived from the score when omitted. */
band?: ScoreBand;
/**
- * v1.15.12 (F1/F2/F3) — the metric's ring hue. When set, the detail card
+ * v1.15.12 — the metric's ring hue. When set, the detail card
* carries the same gentle per-metric tint the dashboard ring tile wears
* (visual continuity from tap to detail) and the ring arc leans the same
* hue instead of the generic band gradient. Omitted → plain `--card` surface
@@ -125,7 +125,7 @@ export function ScoreAnatomyView({
{/* 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 `` claimed the
// entire touch surface as a horizontal scroll container, so
@@ -812,7 +812,7 @@ function InsightsTabStripImpl({
// owns the gesture.
style={{ touchAction: "pan-y" }}
className={cn(
- // v1.4.27 MB7 / CF-72 — relative wrapper hosts the right-edge
+ // v1.4.27 — relative wrapper hosts the right-edge
// fade overlay below so the user reads "there's more to
// scroll" when the pill row overflows. The fade is a tiny
// pointer-events-none pseudo-strip painted onto the inner
@@ -899,7 +899,7 @@ function InsightsTabStripImpl({
);
}
- // v1.4.34 IW-D — group parent pill. Renders as a popover
+ // v1.4.34 — group parent pill. Renders as a popover
// trigger so desktop hover (`onMouseEnter` falls through to
// Radix click semantics — tap-to-open is the same gesture
// on every device, matching the `` MB3 decision).
@@ -994,7 +994,7 @@ function InsightsTabStripImpl({
>
- {/* v1.4.27 MB7 / CF-72 — right-edge fade. The gradient
+ {/* v1.4.27 — right-edge fade. The gradient
absolute-positions over the rightmost ~24 px of the strip
so the last visible pill softly fades into the background
colour, signalling "scroll for more" without a scrollbar.
diff --git a/src/components/insights/intraday-pulse-chart.tsx b/src/components/insights/intraday-pulse-chart.tsx
index e344f9c90..deff1ae99 100644
--- a/src/components/insights/intraday-pulse-chart.tsx
+++ b/src/components/insights/intraday-pulse-chart.tsx
@@ -54,7 +54,7 @@ function minuteLabel(minute: number): string {
const AXIS_TICKS = [0, 360, 720, 1080, 1440];
/**
- * S11 — the intraday pulse "shape of the day" layer inside the pulse insight.
+ * The intraday pulse "shape of the day" layer inside the pulse insight.
*
* Charts one local day's 10-minute mean heart rate (computed on demand from
* raw), with the personal resting baseline drawn as a reference line and any
diff --git a/src/components/insights/layout-coach-fab.tsx b/src/components/insights/layout-coach-fab.tsx
index 5a2dace1d..164c4c825 100644
--- a/src/components/insights/layout-coach-fab.tsx
+++ b/src/components/insights/layout-coach-fab.tsx
@@ -22,7 +22,7 @@ import { apiGet, apiPost } from "@/lib/api/api-fetch";
* by `/api/insights/coach/nudge-status`) paints a small unread dot on
* its corner instead of toggling the whole button.
*
- * v1.18.6 (CCH-02/CCH-03) — the proactive nudge now lands as a real
+ * v1.18.6 — the proactive nudge now lands as a real
* ASSISTANT message in the conversation rail, not a notification-only
* dispatch, so the unread signal moved onto the server-authoritative
* `User.coachLastSeenAt` stamp: `status.unread` is true while a Coach
@@ -49,7 +49,7 @@ export interface CoachNudgeStatus {
nudgedAt: string | null;
unread: boolean;
/**
- * v1.22 (W5) — the conversation holding the newest assistant message, so
+ * v1.22 — the conversation holding the newest assistant message, so
* the FAB can deep-link into the proactive thread on the unread path
* instead of opening a blank new chat. Null when no Coach reply exists.
*/
@@ -106,7 +106,7 @@ export function LayoutCoachFab() {
staleTime: 5 * 60 * 1000,
});
- // v1.18.6 (CCH-03) — stamp `coachLastSeenAt` server-side when the user
+ // v1.18.6 — stamp `coachLastSeenAt` server-side when the user
// opens the Coach so the dot clears across every device. Fire-and-
// forget from the UI's perspective (the local seen-stamp already
// suppressed the dot); on success the status query refetches so the
@@ -197,11 +197,11 @@ export function LayoutCoachFab() {
// Best effort — see above.
}
}
- // v1.18.6 (CCH-03) — stamp the server so the dot clears on every
+ // v1.18.6 — stamp the server so the dot clears on every
// device, not just this one. The local stamp above already hid the
// dot for an instant paint; the mutation makes it durable.
if (unread) markSeen.mutate();
- // v1.22 (W5) — when the Coach has written an UNREAD proactive message,
+ // v1.22 — when the Coach has written an UNREAD proactive message,
// deep-link straight into that conversation (`/coach?c=`) so the
// user lands on what the Coach said, rather than opening a blank new
// chat in the drawer. The full page honours `?c=` →
@@ -235,7 +235,7 @@ export function LayoutCoachFab() {
// safe-area inset on mobile; plain bottom offset once the
// bottom-nav hides (`md:hidden` on the nav, so `md:` here —
// not `lg:` — keeps the FAB from floating mid-air 768-1023px).
- // v1.18.1 (W-COACH-UI C5) — corner inset: on desktop the right and
+ // v1.18.1 — corner inset: on desktop the right and
// bottom insets match (`md:right-8` / `md:bottom-8`) so the FAB
// sits symmetrically in the corner. On mobile the right inset is
// `right-6` but the bottom is deliberately larger — it clears the
@@ -282,7 +282,7 @@ export function LayoutCoachFab() {
>
{showDot ? (
- // v1.18.6 (CCH-03) — discreet "the Coach said something" dot.
+ // v1.18.6 — discreet "the Coach said something" dot.
// Deliberately NOT an alarming red (the medication-card rule:
// status via calm signal, never an alarm tint): a small cyan
// dot ringed against the FAB background reads as informative,
diff --git a/src/components/insights/layout-coach-mount.tsx b/src/components/insights/layout-coach-mount.tsx
index a997c1b44..89eb51783 100644
--- a/src/components/insights/layout-coach-mount.tsx
+++ b/src/components/insights/layout-coach-mount.tsx
@@ -24,7 +24,7 @@ const CoachDrawer = dynamic(
);
/**
- * v1.4.27 R3d MB4 — bridge between the layout-level
+ * v1.4.27 — bridge between the layout-level
* `` and the Coach drawer mount.
*
* The layout file is a server component (it lives next to other server
@@ -47,7 +47,7 @@ export function LayoutCoachMount() {
open={launch.open}
onOpenChange={launch.setOpen}
prefill={launch.prefill}
- // v1.21.0 (C4 H1/H4) — carry the launch scope so a conversation
+ // v1.21.0 — carry the launch scope so a conversation
// opened from a metric surface or insight card is pre-narrowed to
// the relevant source(s).
scope={launch.scope}
diff --git a/src/components/insights/metric-correlation-card.tsx b/src/components/insights/metric-correlation-card.tsx
index 81a0913f6..8016cd1ee 100644
--- a/src/components/insights/metric-correlation-card.tsx
+++ b/src/components/insights/metric-correlation-card.tsx
@@ -89,7 +89,7 @@ export function MetricCorrelationCard({ slug }: MetricCorrelationCardProps) {
title={t("insights.correlationRow.title")}
/>
- {/* v1.18.6 (DISC-01) — the "observational, not causal / talk to your
+ {/* v1.18.6 — the "observational, not causal / talk to your
doctor" disclaimer is removed; the one-time onboarding acknowledgment
now covers the not-medical-advice framing app-wide. */}
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 && (
setAddOpen(true)}
- // v1.18.10 (W10) — on the narrowest phones the h1 + Edit + Delete +
+ // v1.18.10 — on the narrowest phones the h1 + Edit + Delete +
// text "Add" button crowd the row and truncate the title hard.
// Drop the Add button to icon-only under `sm` (label kept for
// screen readers via `aria-label`); the full text returns at `sm+`.
@@ -430,7 +430,7 @@ export function LabBiomarkerDetail({ biomarkerId }: { biomarkerId: string }) {
) : null}
- {/* v1.43 (D1223) — the other spellings this marker answers to. */}
+ {/* v1.43 — the other spellings this marker answers to. */}
{marker ? (
) : null}
diff --git a/src/components/labs/lab-form.tsx b/src/components/labs/lab-form.tsx
index 2bc855388..839244897 100644
--- a/src/components/labs/lab-form.tsx
+++ b/src/components/labs/lab-form.tsx
@@ -97,7 +97,7 @@ interface LabFormProps {
onSuccess?: (created: LabResultDto) => void;
onCancel?: () => void;
/**
- * v1.30.1 (H3 QoL fix) — "Save & add another": the sheet stays open, the
+ * v1.30.1 — "Save & add another": the sheet stays open, the
* reading is cleared for the next entry, and `takenAt` is PRESERVED (a
* real lab report is 10-20 analytes sharing one blood-draw date, usually
* in the past — re-picking that date for every row was the pain point).
@@ -180,7 +180,7 @@ export function LabForm({
// The bounds are derived from what is typed; what is typed is kept verbatim.
const [sourceRangeOpen, setSourceRangeOpen] = useState(false);
const [sourceRange, setSourceRange] = useState("");
- // v1.30.1 H3 — which submit button is in flight, so the OTHER one doesn't
+ // v1.30.1 — which submit button is in flight, so the OTHER one doesn't
// also show a spinner. `!== null` replaces the old plain `submitting`
// boolean for every disabled check below.
const [pendingAction, setPendingAction] = useState<
@@ -219,7 +219,7 @@ export function LabForm({
e.preventDefault();
setError(null);
- // v1.30.1 H3 — both Save and Save & add another are `type="submit"`
+ // v1.30.1 — both Save and Save & add another are `type="submit"`
// buttons tied to this one `