Add an additive annotations[] prop to <HealthChart> that paints a
vertical Recharts <ReferenceLine> per AI-narrated event on the
underlying timeline. The line + tooltip + comparison overlay remain
untouched — annotations sit between the cartesian grid and the data
lines so they read as orientation, never as data.
The /insights page wires advisor.payload.insights.storyboardAnnotations
into the 90-day BP chart, mapping the four canonical categories to
Dracula colours (medication=pink, event=cyan, milestone=green,
warning=orange).
Pure helper resolveAnnotationPositions() handles the date->pointIndex
mapping with a 7-day snap window (so annotations don't stack on
monthly buckets) and a 24-char truncation for the small-viewport
label. The unit suite pins exact-day pinning, near-day snapping,
out-of-window dropping, and label truncation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a slim banner-card on <HeroStrip> that surfaces a fresh weeklyReport
between the title block and the action row. Three actions:
- Read -> Next.js Link to /insights/report/[week]
- Share -> navigator.share() with clipboard fallback + sonner toast
- Export PDF -> deep-link to the report URL with ?print=1 so the report
page auto-fires window.print() after first paint
The banner only renders when the parent passes weeklyReportReady; the
insights page derives that prop from advisor.payload.insights.
weeklyReport so the banner appears the moment a fresh PROMPT_VERSION
4.20.2 generation lands.
EN + DE translations under insights.heroBanner.* (Read / Share / Export
PDF + share success/failure toasts).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a newsletter-style report route at /insights/report/[week] that
parses the YYYY-Www slug, reads the cached weeklyReport block off the
AI advisor payload, and renders Summary / Going-well / Worth-watching /
Tips / Data-quality sections in a print-friendly layout.
Print export uses window.print() with Tailwind print: variants on the
toolbar (hidden) and article wrapper (full-width margins). The route
honours ?print=1 to deep-link the export PDF flow from the upcoming
hero banner card.
Includes:
- src/lib/insights/week-iso.ts — parseWeekISO / toWeekISO / weekISOToRange
- WeeklyReportView wrapper + WeeklyReportPresentation pure layer
- Empty-state with Generate CTA when the cached report's weekISO
doesn't match the route param
- EN + DE i18n keys under insights.report.* and insights.heroBanner.*
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend aiInsightResponseSchema with weeklyReport (ISO week + summary +
goingWell/worthWatching/tips bullets + optional dataQualityNotes) and
storyboardAnnotations (date + label + category + detail; max 20).
Both blocks are nullable + optional so legacy 4.20.1 caches round-trip.
Bump PROMPT_VERSION 4.20.1 -> 4.20.2 and add GROUND RULE 10 (weekly
report sections match layout, conservative phrasing, no causal claims)
+ GROUND RULE 11 (storyboard annotations are factual user-logged events
with neutral prose) to both EN and DE prompts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tick B3 in `.planning/STATE.md` against the six commit drops on
`develop` (correlation maths, schema + prompt rule,
TrendAnnotation + TrendsRow, CorrelationCard + CorrelationRow,
analytics route + page wiring, this report). Replace the stale
v1.4.16 host-load report with the v1.4.20 narrative — what
shipped, synthetic-data spot checks for each hypothesis, and
flagged-uncertain items for maintainer review (confidence-band
thresholds, eta-squared mapping, interpretation localisation
strategy).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Compute the three pre-defined correlation hypotheses
(BP × compliance, mood × pulse, weight × weekday) on the trailing
30-day window in `/api/analytics` and surface the typed result
shapes on the response payload. Each runner is gated on n >= 14
+ p < 0.05; below the bar the result.status === "insufficient"
and the per-card EmptyState paints in place of a misleading
near-zero scatter.
The insights page now mounts:
- `<CorrelationRow>` between the Daily Briefing and the Advisor
card — the layout slot the artboard reserves for the patterns
surface.
- `<TrendsRow>` immediately below it, fed from
`advisor.payload.trendAnnotations` (lifted client-side via
`trendAnnotationsSchema.safeParse`). Legacy cached payloads
predating PROMPT_VERSION 4.20.1 surface the per-metric
empty-state hint instead of a half-rendered annotation.
The wide-event annotation captures per-hypothesis status so admin
observability can attribute coverage gaps without re-querying
the DB.
10 source-scan tests pin the load-bearing imports, the JSX mounts,
the analytics-route surfacing, and the malformed-payload null
fallback in the advisor hook.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two cards-and-row primitives for the Insights redesign:
- `<CorrelationCard>` — single hypothesis. Tone-coloured left bar,
scatter sparkline (deferred-bundled), confidence chip, source
chip ("based on N paired readings · last 30 days"), interpretation
prose, and a disabled "Try a 7-day experiment" CTA with a
Coming-soon tooltip. Below-threshold results render the
EmptyState in place of the chart so we never paint a misleading
near-zero scatter.
- `<CorrelationRow>` — 2-up on `>= md`, single column on `< md`.
Row-level correlation-disclaimer ("Patterns are observational,
not causal — talk to your doctor if BP runs high.") sits below
the card grid once per row, not per card.
13 tests cover OK + insufficient rendering, the disabled CTA's
tooltip wiring, the confidence chip variants, the disclaimer
position, the responsive grid, and the EN / DE locale switch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add two presentational pieces for the Insights redesign:
- `<TrendAnnotation>` — pure renderer for the AI-authored
one-sentence annotation that sits below each Trends-row chart.
Empty state is locale-aware ("Awaiting more data to annotate
this trend"). Optional confidence chip surfaces a discrete
low / moderate / high band.
- `<TrendsRow>` — 3-up responsive grid (1 column < md, 3 columns
>= md) wrapping BP / Weight / Mood mini charts with their
annotations. Recharts ships through the existing `next/dynamic`
defer-load pattern so the bundle savings stay intact.
Adds the EN + DE i18n keys (trendsRow, trendAnnotation,
correlationRow). 15 unit tests cover the locale switch, the
empty-state hint per metric, the confidence chip variants, and
the row layout chrome.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extend `aiInsightResponseSchema` with an optional, nullable
`trendAnnotations: { bp?, weight?, mood? }` block — one short
sentence per metric, capped at 200 characters. Each field is
independently optional so the model emits only the metrics it can
ground in the snapshot.
Insight-generator prompt picks up GROUND RULE 9 (EN + DE):
single-sentence, observational, ≤ 200 chars, banned causal
phrases enumerated. PROMPT_VERSION bumps 4.20.0 → 4.20.1 to
signal the format change so cached payloads from B1 still parse
through the `.nullable().optional()` field.
13 tests cover the boundary (200 chars), the empty-string
rejection, the legacy-payload round-trip, and the EN/DE prompt
rule presence.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add `src/lib/insights/correlations.ts` with three pre-defined
hypothesis runners (BP × medication compliance, mood × resting
pulse, weight × weekday) plus pure Pearson + one-way weekday-ANOVA
helpers. Each runner gates the surface on n >= 14 paired rows AND
p < 0.05; below the bar a typed `insufficient` result lets the
card render an EmptyState rather than a misleading pattern.
Interpretations stay conservative ("a pattern worth watching" —
never "X causes Y"). 95 % confidence intervals come from a Fisher
z-transform on r and a discrete band-from-eta-squared mapping for
the weekday case. No new dependencies — the t-distribution +
F-statistic p-value approximations are inlined (Wilson-Hilferty +
Abramowitz-Stegun).
16 unit tests cover positive / negative / no-correlation / under-n /
non-significant / no-variance branches and the Monday-outlier
weekday flag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
B2b drawer UI shipped in four atomic commits on develop:
- TanStack Query hooks + SSE streaming client
- Drawer shell + message thread + source chips
- Composer + history rail + sources rail
- /insights wires the drawer via the hero strip's "Ask the coach"
button + suggested-prompt chips
Tests: 1781 → 1833 unit (+52). Integration unchanged at 78.
Six new test files cover the streaming parser, every drawer column,
the thread state machine, locale-aware labels, and the page wiring.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the loop between the hero strip's "Ask the coach" /
suggested-prompt chips and the Coach drawer:
- `<HeroStrip>` gains an `onAskCoach` prop. When supplied, the
"Ask the coach" button drops its disabled state + Coming-soon
tooltip and clicking calls the handler. Older parents that
haven't adopted the drawer still get the disabled affordance —
no breaking change.
- `<SuggestedPrompts>`'s `onPick` already routed up via
`onPickPrompt`; the page wires it to a setter that pre-fills the
drawer's input AND opens it in the same tick.
- `/insights` owns `coachOpen` + `coachPrefill` state. The drawer
mounts with `key={prefill ?? "blank"}` so the lazy `useState`
initialiser fires fresh on every chip transition, sidestepping
the `react-hooks/set-state-in-effect` rule.
Tests:
- Updates the hero-strip ratchet to allow either disabled (no
handler) or enabled (handler supplied) states.
- Adds three guards under `insights-polish.test.ts` that pin the
page-side wiring (CoachDrawer import, hero handler hook-up,
state ownership).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fills the three drawer slots from commit 2 with their full
implementations:
- `<CoachInput>` is a Dracula-styled `<textarea>` card. Plain Enter
and Cmd/Ctrl+Enter both submit; Shift+Enter inserts a newline.
The mic button is disabled with an "Voice input arrives with the
iOS app in v1.5" tooltip — the natural shipper of voice is the
native client. A footer disclaimer ("Coach replies are
generated. Clinical decisions belong with your doctor.") sits
beneath the box.
- `<HistoryRail>` reads from `useCoachConversations()`, exposes a
substring filter, marks the active conversation, and offers a
hover-revealed delete affordance with a confirm-then-go flow
driven by `useDeleteCoachConversation()`.
- `<SourcesRail>` lists the five contracts the snapshot helper
surfaces (BP, weight, pulse, mood, compliance) with a static
fresh-dot indicator. A v1.4.21 follow-up swaps in
`<IntegrationStatusPill>` once the rail decides whether to lean
on `/api/auth/me` or a dedicated `/api/insights/data-sources`
endpoint.
- `<CoachDrawer>` mounts these as the defaults so callers only
need to pass open + onOpenChange + prefill; the slot props stay
as escape hatches for tests.
Tests added: composer keyboard / disabled / streaming states (10),
sources-rail render + locale (5), history-rail render + locale +
empty / active states (7).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lays the visual scaffold for the AI Coach drawer (B2b):
- `<CoachDrawer>` wraps `<Sheet side="right">` and exposes the
three-column layout from the artboard (history rail · message
thread · sources rail) on `lg+`. Mobile (<lg) collapses to a
single column with the message thread in focus and the rails
hidden — they get reachable via tray menus in v1.4.21.
History / sources / composer are slot props so the parent wires
them in commit 3 without coupling them to the shell.
- `<MessageThread>` renders user / assistant bubbles, supports an
in-flight streaming bubble fed by `useSendCoachMessage()`, and
preserves a "user-pinned-to-bottom" auto-scroll heuristic so
reading earlier turns doesn't yank the viewport when a new token
lands.
- `<SourceChips>` renders the labels-only provenance envelope —
metric + window + n-count, no raw values — matching the wire
contract from B2a.
- i18n keys land under `insights.coach.*` for tagline, new chat,
composer placeholder, history rail, sources rail, metric labels,
and window labels. Both locales updated.
Drawer state (open / prefill) is consumed via a controlled prop +
lazy `useState` initialiser; the close path resets thread + composer
in the `onOpenChange` handler so we don't trip
`react-hooks/set-state-in-effect`.
Tests cover: source-chip render permutations (8), message-thread
empty / persisted / streaming / error states (8), provenance
locale-aware labels.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the data layer for the AI Coach drawer (B2b):
- `useCoachConversations()` reads `GET /api/insights/chat` for the
history rail (default page; cursor pre-wired for v1.4.21 infinite
scroll).
- `useCoachConversation(id)` reads the single decrypted thread.
- `useDeleteCoachConversation()` removes the thread with optimistic
list cache update + rollback on failure.
- `useSendCoachMessage()` POSTs to `/api/insights/chat`, walks the
body via `fetch().body.getReader()`, and decodes SSE frames
(`data: <json>\n\n`) with `TextDecoder`. Token / provenance / done /
error events update a `streaming` state object that the message
thread will render as the live assistant bubble. On `done` we
invalidate the conversation cache + the rail so the next mount sees
the canonical persisted shape.
- `parseSseChunk()` is exported as a pure helper so the parser is
unit-testable without a live network stream — 11 cases cover
single-frame, multi-frame, byte-by-byte interleaving, malformed
payload tolerance, and the full token + provenance + done round-trip.
No third-party SSE library introduced — `fetch` + `getReader()` is
enough.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Coach backend half of v1.4.20 wave B is complete; STATE.md
splits B2 into B2a (backend, done) + B2b (drawer UI, deferred to a
later dispatch) and records the test-count delta plus the migration
file in the status block. phase-B2a-report.md captures the five-
commit timeline, schema, helpers, route surface, token-budget
rationale, provenance JSON shape, and flagged-uncertain items.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GET /api/insights/chat returns the caller's conversations newest-first
with cursor pagination (default limit 20, hard cap 50). Each row
carries id, title, timestamps, and a message count so the rail can
render without paying the per-message decrypt cost.
GET /api/insights/chat/[id] fetches one conversation with every
message decrypted in memory before serialisation. Provenance metadata
round-trips alongside the assistant turns.
DELETE /api/insights/chat/[id] hard-deletes the thread; the FK chain
cascades the message rows.
Cross-user lookups map to 404, never 403, so the existence channel
never leaks across accounts.
6 integration tests cover the list ordering, owner-only filter,
decryption round-trip, and the 404 boundary on both verbs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The route gates on requireAuth (cookie or bearer), enforces the daily
token budget, runs the prompt-injection / off-topic refusal scanner,
and only then walks the user's provider chain.
The conversation is created via withIdempotency() when no
conversationId is supplied, so a retry under the same Idempotency-Key
replays the original assistant reply rather than spinning up a second
thread. Existing-thread turns skip the wrap.
Replies stream as SSE: one "token" frame per word-sized chunk, then a
"provenance" frame describing the windows + metrics the assistant
could see, then a "done" frame carrying the persisted message id.
Provider failure cascades to a single "error" frame and the assistant
message is never persisted. Conversation history is capped at 20 turns
in the prompt window via a synthetic summary placeholder so cost stays
bounded.
`buildCoachSnapshot()` reuses the analytics features pipeline so the
Coach narrates the same numbers the dashboard tiles render.
`getCoachSystemPrompt()` extends the strict-insight ground rules with
conversational framing while keeping the zero-hallucination rule.
5 integration tests pin the round-trip, refusal short-circuit, budget
429, foreign-conversation 404, and validation 422 paths against the
postgres testcontainer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire the foundation layer for the AI Coach:
- `types.ts` defines the SSE event shapes (token / provenance / done /
error), the chat-request zod schema, and the conversation/message
DTOs the route + UI share.
- `persistence.ts` encrypts every message body with the active
ENCRYPTION_KEYS entry before insert and decrypts on read.
`summariseTitle()` keeps the rail snippet ≤80 chars with a smart
word-boundary cut.
- `budget.ts` introduces a per-(user, UTC-day) ledger with a 25 000-
token daily cap. `enforceBudget()` throws HttpError(429,
"coach.budget.exceeded") so the route can short-circuit before
spending. UTC alignment matches the LLM-provider billing cycle.
- `refusal.ts` runs prompt-injection and off-topic detection on inbound
messages. Pattern bank covers EN+DE injection wording, jailbreak
variants, and obvious off-topic asks (weather, code-help, recipes).
Health-allow tokens override the off-topic deny list so mixed
questions land on-topic.
- i18n keys `insights.coach.refusal*` + `insights.coach.error*`
added to both locale bundles.
- 28 unit tests cover the helpers with mocked Prisma.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Persist Coach chat threads with AES-256-GCM-encrypted message bodies
and a per-user, per-day token-spend ledger. Encryption reuses the
active ENCRYPTION_KEYS entry so rotation continues to work without a
backfill. metric_source_json carries provenance labels only (window +
metric names, never raw values), keeping it queryable for analytics.
Cascade-on-user-delete is wired through the FK chain so GDPR delete
removes Coach data with the rest of the account. The integration-suite
truncation list picks up the new tables.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
STATE.md: Wave B1 ticked (hero strip / daily briefing / suggested
prompts / schema / prompt bump). The Apple-Health vitals tile row
is annotated as descoped + deferred to B3 — the per-section status
cards already render the same numbers, so a tile row above them
would duplicate the surface.
New `phase-B1-v1420-report.md` captures the 5 SHAs (9873363 →
7d29596), the test-count delta (1672 → 1753, +81 tests), the
deferral list (Coach wiring → B2, weekly-report → B4, Health Score
→ B5, Briefing variants → backlog, source-chips → B3), and 5
prompt-copy decisions flagged for maintainer review.
The older `.planning/phase-B1-report.md` (v1.4.18 achievements
marathon) is left untouched; v1.4.20's report uses the v1420
suffix to avoid the collision.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces `<InsightsPageHero>` with the new `<HeroStrip>` band and
mounts a full-width `<DailyBriefing>` directly below it, ahead of
the existing advisor + status cards which remain untouched.
`useInsightsAdvisorQuery` now lifts the optional `dailyBriefing`
block off the cached payload and validates it client-side via
`dailyBriefingSchema.safeParse`. A malformed cached briefing falls
back to null so the briefing card paints its empty-state CTA
instead of a half-rendered card. Legacy v1.4.19 cached payloads
(no `dailyBriefing` field) round-trip unchanged.
The CompareToggle migrates from the hero meta band into the
DailyBriefing's metaSlot — keeps the page-level comparison control
adjacent to the briefing it scopes.
The hero greeting reads the user's username (HealthLog has no
separate display-name field) and stops at the first whitespace so
"alice" / "alice.smith" both surface "Good morning, alice".
`insights-polish.test.ts` v1.4.19 A3 ratchet updated: the original
"exactly one onRegenerate prop on the page" assertion was a
string-count check that mis-fires now that the briefing card's
empty-state CTA legitimately wires the same handler. The
load-bearing constraint stays — the advisor card must NOT carry
onRegenerate — and is enforced via a tighter JSX-block check.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New `src/components/insights/hero-strip.tsx` — replaces the v1.4.16
`<InsightsPageHero>` band with the wider strip from the v1.4.20
redesign:
- locale-aware time-of-day greeting (morning / afternoon / evening
/ night → 4 i18n keys, with "night" mapping to "Good evening" in
both locales since 03:00 + Guten Morgen reads odd)
- briefing-paragraph subtitle (falls back to a generic line when
no briefing is cached)
- personal-baseline + 'Generated <relative-time>' meta band
- 3-button action row: weekly-report (disabled, B4), ask-the-coach
(disabled, B2), re-run analysis (wired through onRegenerate).
Disabled buttons surface a 'Coming soon' tooltip via title= so
we don't drag in a Radix tooltip provider for static labels.
- <SuggestedPrompts> chip strip below the action band; onPickPrompt
is a TODO until B2 lands the Coach drawer (parent passes a no-op)
New CSS utilities in `globals.css`:
- .hero-gradient — Dracula purple → pink linear gradient over card
- .glow-purple — soft purple aura via box-shadow
- @keyframes pulseDot + .pulse-dot — for the future "live" dot
affordance; reduced-motion users get a static dot
Old `<InsightsPageHero>` stays in place (used by the dashboard
preview) and gains a JSDoc @deprecated note pointing to the new
component. EN + DE translations added in lockstep.
Test suite: +22 specs covering greeting buckets per locale + per
hour, paragraph fallback, disabled-button affordance, regenerate
wiring, suggested-prompts mount.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New `src/components/insights/daily-briefing.tsx` renders the
full-width briefing card from the v1.4.20 redesign — narrative
paragraph + 0-5 key-finding rows below it, each row carries a
tone-coloured left bar (green/orange/cyan), metric icon, headline,
detail line, and an optional delta badge in the matching tone.
States covered:
- loaded: paragraph + findings + optional 'Generated <time>' meta
- empty (briefing=null + !loading): EmptyState with 'Generate
briefing' CTA wired through onRegenerate
- loading: animate-pulse skeleton matching the rest of the app's
loading affordance
- empty findings: paragraph still renders; the findings block
hides cleanly so the card doesn't paint a void section header
Optional metaSlot in the card header so the comparison toggle can
migrate from the hero to the briefing card in commit 5.
EN + DE translation keys added (insights.dailyBriefing.*). Test
suite: +18 specs covering rendering + states + locale + tone bars.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New `src/components/insights/suggested-prompts.tsx` renders the
"Try asking" chip row from the v1.4.20 Insights redesign hero strip.
Five default prompts (whyMonday, weightVsPulse, weekVsMonth,
tellMyDoctor, medicationWorking) read from
`insights.suggestedPrompts.*`, click invokes `onPick` with the
localised string so the parent can route to the future Coach drawer.
Mobile-first: chips wrap to multiple rows on narrow viewports.
Dracula purple chip styling matches the existing card vocabulary.
Custom prompts array accepted for tests + future per-user sets.
EN + DE translation keys added in lockstep — i18n-locale-integrity
test stays green. Test suite: +9 specs covering default rendering,
locale switching, custom prompts, and chip-slot pinning.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends `aiInsightResponseSchema` with an optional `dailyBriefing`
block (paragraph + 0-5 keyFindings, each with tone / headline / detail
/ delta / sourceWindow / sourceMetric). Nullable + optional so v1.4.19
cached payloads still parse cleanly.
Bumps PROMPT_VERSION 4.19.0 → 4.20.0 and adds GROUND RULE 8 in both
EN and DE system prompts, instructing the model to emit a briefing
block grounded in the user's own snapshot numbers (no extrapolation,
no medical advice claims, hard-cap at 5 findings).
TDD: new daily-briefing-schema.test.ts covers legacy back-compat,
well-formed parsing, > 5 findings rejection, empty-paragraph
rejection, finding-field validation, and the PROMPT_VERSION 4.20.x
match. Existing medical-reference-prompt.test.ts assertion stays
broad enough to ride the bump.
+31 tests (1672 → 1703); typecheck clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- STATE.md: F5 ticked complete with the four atomic commits referenced
- phase-F5-report.md: scope + per-commit justification + deferral list
- v1421-backlog.md: new backlog file for F5 LOW carry-over plus the
FX cleanup carry-over (source-comment sweep, bilingual CHANGELOG,
CLAUDE.md/AGENTS.md filenames)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Existing npm group covered runtime deps. Adds weekly bumps for the
.github/workflows/* action versions and the base image referenced in
Dockerfile so neither falls behind silently.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adopts the canonical Contributor Covenant 2.1 (verbatim, with
conduct@bombeck.io as the enforcement contact). CONTRIBUTING.md links
to it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CONTRIBUTING.md gains a "Branch Model" section with an ASCII flow
diagram and the two-rule summary (develop = daily target, main =
release-only). Hotfix flow + end-user-vs-contributor guidance
included. Mirror page on docs.healthlog.dev landed in healthlog-docs
5d96861.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- STATE.md: F1 ticked complete, F3+F4 collapsed into FX (combined
cleanup wave), all bullets reflect what shipped on origin/main
- phase-F1-report.md: GHCR workflow audit + branch model contract
- phase-FX-report.md: 3-repo PII/jargon/German scrub (HealthLog,
healthlog-docs, healthlog-landing) with carry-overs and the
pre-release PII grep gate spec
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Retroactive cleanup of CHANGELOG.md and docs/audit/v1.4.X-summary.md
files. Removes maintainer's real name, specific health figures tied to
the live tenant, internal release-process jargon, and stray German
strings in English-only release notes. Pre-release PII-grep gate added
to the v1.4.20 release pipeline. No git history rewrite.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Captures the next-session brief: foundation tasks F1-F6 (develop branch
model, CONTRIBUTING docs, retroactive jargon cleanup, release-notes
language audit, repo best-practice audit, doc-hallucination QA), then
the 5-phase Insights redesign + AI Coach work, then standard QA + release
on the new branch flow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Marc-Brief plus what-landed / deferred / CI / production-deploy /
v1.4.20-preview sections. Mirrors the v1.4.18 report shape.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
STATE update + reconcile report captures the six reviewer briefs and
the inline triage outcome (one CRITICAL fixed, seven HIGH applied,
three HIGH carried over). Backlogs updated: the v1.4.20 file gains a
"Phase D reconcile carry-over" block with file:line references; the
v1.5 file inherits the strategic risk watchlist (conversation
persistence eviction, correlation FDR control, streaming UX,
prompt-injection refusal, safeParse legacy-payload audit pattern).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`overflow-y-hidden` on the tabs strip clipped the bottom of the
focus-visible ring (`ring-[3px]`) on triggers because the parent
padding was only 3 px — the 3 px ring ended exactly at the
content-box edge. Bump the padding to `p-1` (4 px) so keyboard users
see the full ring while the y-axis clamp still hides the painted
scrollbar Marc reported.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The useState lazy initialiser already runs on the client during
hydration and reads the live `window.innerWidth`, so the post-mount
handler() call was a no-op that triggered a setState inside useEffect.
The eslint react-hooks/set-state-in-effect rule flags this pattern;
removing it lets the resize + orientationchange listeners alone drive
updates without a hydration-time double set.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The auto-issued token-name viewer rebuilt the dd.MM.yyyy HH:mm suffix
from `Date.getUTC*` accessors, producing 19:46 instead of the 21:46 a
Berlin-time admin would expect for a token issued at 19:46 UTC. Route
the parsed Date through `Intl.DateTimeFormat("de-DE", { timeZone:
"Europe/Berlin", … })` so the embedded timestamp matches every other
display surface in the app.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mechanical clean-up sweep across five hot spots flagged by the simplify
audit:
- Lift the 11-key auth.* audit-action label map out of the two admin
components that rebuilt it on every render. The shared
useAuthActionLabels() hook in _shared.tsx is now the single home for
new entries (login-overview + recent-audit-preview consume it).
- Drop the dead chipClass branches in IntegrationStatusPill: only the
connected state actually paints a Dracula-green chip; error and
disconnected fall back to the badge variant.
- Add thin DateInput / DateTimeInput wrappers under components/ui that
bake in the lang={locale} hint so 14 callsites across 9 files no
longer have to remember the prop. A future date input cannot
silently regress.
- Remove the leading icon-only flex wrappers left behind when the
duplicate card titles were dropped on /admin/feedback,
/admin/danger-zone, and the thresholds editor.
- Strip the five "v1.4.19 X removed because…" narration comments from
insights/page.tsx and the dashboard. git blame already answers what
changed and the comments aged into history.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The mobile card list checked m.type against the short forms BP_SYS /
BP_DIA, which never appear in API payloads — the canonical enum is
BLOOD_PRESSURE_SYS / BLOOD_PRESSURE_DIA. The badge condition was
therefore permanently false and the disambiguation tag never rendered
on Pixel 5. Route the label through MEASUREMENT_TYPE_LABEL_KEYS so the
mobile branch shares the desktop table's single source of truth, and
add a regression test that asserts both labels paint in both branches
under EN and DE.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wave B completed: 6 / 6 CRITICAL applied, 21 / 25 HIGH
applied, 4 HIGH deferred (3 not-an-issue, 1 owned by v1.4.20
Insights redesign), 31 MED + 16 LOW seeded as v1.4.20
backlog. Final verification clean. STATE.md status block
appended; A8 raw findings added to repo for future reference.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Run prettier on the test, e2e, and component files this v1.4.19
A8 wave touched. The .planning/* and docs/audit/* prettier
warnings are pre-existing baseline noise — left alone to keep
this commit narrow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The native auto-login flow names tokens by suffixing the issuing
call's `Date.now().toISOString()`, so the admin viewer rendered
"iOS auto-login 2026-05-05T19:46:20.603Z" — readable as debug
output, not as something a person picked. Added a renderer-side
formatter that detects the trailing ISO timestamp and rewrites
the cell to "iOS auto-login · 05.05.2026 19:46". Manual token
names without an ISO suffix pass through unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two related improvements to the admin recent-activity surface:
- New audit actions `auth.token.autoissue.native`,
`auth.token.refresh`, and `auth.token.revoke` were leaking
through both the dashboard preview and the Login Overview
viewer as raw event keys. Added translations and wired the
three keys through the action-label map shared by both
components.
- Recent-activity rows on /admin were a dead-end: a failed
login row showed "Unknown" + "Failed" with no link, no IP,
no way to drill in. Each row is now a Link into
/admin/login-overview, with the IP shown inline at sm+.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>