1481 Commits
Author SHA1 Message Date
Marc-André Bombeck 9a31be0ea8 Merge pull request #264 from MBombeck/release/v1.12.7
release/v1.12.7 — Insights that react, read clean, and stay legible
v1.12.7
2026-06-05 14:23:05 +02:00
Marc-André Bombeck 7ce8868079 chore(release): v1.12.7 — Insights that react, read clean, and stay legible 2026-06-05 14:15:58 +02:00
Marc-André BombeckandClaude Opus 4.8 d2edaeb8d1 refactor(insights): point the pulse page to the cardio-fitness surface for VO2 max
VO2 max had two divergent surfaces — the dedicated /insights/cardio-fitness
page (full chart + plain-language assessment) and a legacy compact chart-row
on the pulse page. Replace the pulse-page row with a clear cross-link to the
dedicated page so there is one VO2 max surface, and remove the now-unused
chart-row component + its test. New cardioLink copy added across all six
locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:07:56 +02:00
Marc-André BombeckandClaude Opus 4.8 4847d2fc40 fix(insights): surface an error + retry on the overview, not the empty state
The overview empty-state gated on the comprehensive query reporting zero
measurements, but a transient 500 / network drop settles the query with no
data too — so a user with history saw the false 'no data yet — add a
measurement' prompt with no way to recover. Add an isError branch above the
empty-state that renders a load-error message plus a Retry that refetches the
query, mirroring the vitals-dashboard pattern. Reuses the existing
insights.loadError + common.retry keys.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:07:56 +02:00
Marc-André BombeckandClaude Opus 4.8 4a88db6966 fix(ui): route status colours through AA-safe semantic tokens
Raw text-dracula-* utilities used as text fail WCAG AA contrast in light
mode (the Alucard light theme carries no override for the raw primitives).
Swap every text usage and its paired bg/border tint across the settings
integration/notification/sharing/api cards, the insights recommendation,
confidence, daily-briefing, health-score and coach-feedback surfaces, the
bug-report banners, the copy-confirmation icon and the chart comparison
caption to the theme-aware --success / --warning / --info / --destructive /
--dose-accent tokens, which both themes override to an AA-safe tone. Dark
mode is unchanged; decorative aria-hidden gradients and chart series fills
stay on the raw primitives.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 14:07:56 +02:00
Marc-André BombeckandClaude Opus 4.8 b6126db754 feat(insights): extend the anti-repetition diversity context to the headline cards
The v1.12.1 diversity block (variety lead, data-strength line, steady-run
repetition signal, FDR-screened cross-metric relations) reached only the
generic archetype cards. The seven hand-tuned headline cards — the ones users
see most — ran at temperature 0.3 with only the previous-context block, so they
stayed prone to repetitive openers and forced filler steps. Thread the same
assessmentContextBlock through the specialized get*UserPrompt builders and
raise their temperature to 0.45 to match the archetypes.

Soften the unconditional "close with one step" across the specialized sections
and the activity / body-composition / sleep archetypes to the base prompt's
conditional form — close with a step only when the finding implies one,
otherwise affirm and name one thing to watch — so a steady metric no longer
manufactures a filler step. EN/DE parity preserved; guard computeSteadyRun
against an absent graded slice.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:58:28 +02:00
Marc-André BombeckandClaude Opus 4.8 941947399a perf(insights): bound the medication-intake reads to the rendered window
general-status and blood-pressure-status read the full medication-intake
history with no time floor, scanning every row for accounts with years of
data. Both only consume the trailing ~365 days (the adherence fold and the
continuity series / 7-30 day compliance gate), so add a scheduledFor floor at
now - 365d.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:58:28 +02:00
Marc-André BombeckandClaude Opus 4.8 005f4ba2be feat(insights): wire curated medical references into the live insight paths
The reference-grounding builder (buildSystemPromptWithReferences) and its
SOURCES list shipped to no one: both live paths called the plain system prompt,
so the model was asked to cite referenceId values it was never shown — and the
parser rejects invented ids, so the citation machinery the schema and UI
footnote already support stayed dark. Wire the builder into both consumers,
passing the metric sections the snapshot actually carries (a weight-only run
never lists BP guidance). Apply the fenced-JSON parse fallback here too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:58:28 +02:00
Marc-André BombeckandClaude Opus 4.8 e6ef9f2a3e fix(insights): tolerate fenced JSON from providers without a JSON mode
Anthropic and the local provider do not send a JSON mode, so a compliant model
still wraps its { "summary": … } reply in a code fence or prefixes a sentence,
which broke JSON.parse and surfaced the raw fenced string as the assessment.
Add a shared stripJsonFences helper (drop the fence, narrow to the first { …
last }) and apply it as a parse fallback in parseSummaryFromContent. No-op on
already-clean JSON.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:58:28 +02:00
Marc-André BombeckandClaude Opus 4.8 0193dbc1ee refactor(insights): strip release markers from the model-facing prompt
The insights ground-rules carried changelog tokens ("v1.4.16 phase B8", "W4d")
inside the prompt strings the model reads — token waste and inconsistent with
the project voice. Move the rule content to clean numbered rules; the markers
belong in code comments, not shipped prompt prose. Update the test pins to the
cleaned rule headers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:58:28 +02:00
Marc-André BombeckandClaude Opus 4.8 9a07a27001 fix(insights): default to current full-size AI models for the headline feature
The OpenAI BYOK + admin-shared fallback defaulted to a small model and
Anthropic to a 2024 Sonnet, so a user on the operator's shared key got the
weakest model for the Insights assessments. Bump the OpenAI default to gpt-4o
and the Anthropic default to claude-sonnet-4-6; both stay overridable by user
config. Sync the provider docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:58:28 +02:00
Marc-André BombeckandClaude Opus 4.8 437a627a66 perf(dashboard): reuse the probed coverage map in the summaries slice
The snapshot builder probed rollup coverage up front to gate the thick
extras phase, then called computeSummariesSlice which re-ran the
identical probeRollupCoverage query. Thread the already-probed map into
the slice as an optional argument so the snapshot's hot path issues the
probe once. The argument is optional with the internal probe as the
fallback, so every other caller is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:51:54 +02:00
Marc-André BombeckandClaude Opus 4.8 6de5de0284 perf(dashboard): serve the snapshot stale-while-revalidate on a measurement write
The dashboard snapshot lives in the analytics cache bucket, which had no
stale window, and every measurement write hard-evicted the whole
${userId}| prefix. A high-frequency iOS Apple-Health sync therefore
busted the snapshot into a cold rebuild on the next read all day long
(the same class as the v1.8.7 insight-cache eviction).

Give the analytics bucket a stale-while-revalidate window, read the
snapshot via cachedSwr, and switch the measurement-write invalidation
from a hard deleteByPrefix to markStaleByPrefix. The snapshot now serves
the prior value immediately while a single background recompute warms a
fresh one. Slim / thick / summary cells read via plain cached are
unaffected: a marked-stale entry has expiresAt === now, so their next
read is a clean miss and rebuilds fresh. Widget reorder keeps its hard
point-evict so a layout change paints synchronously; mood and medication
writes keep their hard analytics sweep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:51:54 +02:00
Marc-André BombeckandClaude Opus 4.8 ae468edca2 fix(idempotency): claim the key before running the side-effect
findCached returned null and the response was only persisted after the
handler completed, so two concurrent requests carrying the same
Idempotency-Key both passed the lookup and both executed the side-effect
(duplicate writes for the very clients idempotency protects).

Claim the key by inserting a pending sentinel row under the existing
(userId, key, method, path) unique constraint before the handler runs.
A racing request either finds the pending row or loses the insert race
and is refused with 409. The claim is promoted to the completed response
on a cachable result, released on a non-cachable result or a
secret-shaped body, and released on a handler throw. The pending row
carries a short claim TTL so a crashed handler self-heals and the
existing expiry-sweep cleanup still applies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:51:54 +02:00
Marc-André BombeckandClaude Opus 4.8 037da560ad fix(compliance): compute medication streak in the user's timezone
The analytics compliance streak keyed days with host-local
getFullYear/getMonth/getDate against a timeline whose slot.day is minted
in the user's IANA zone, so the streak drifted off by a day whenever the
server clock's zone differed from the user's. Consolidate onto the
detail-page streaksFromTimeline helper threaded with the medication's
timezone so both call sites share one correct day-keyed walk.

Also fix the DST step in that helper: it stepped a fixed +24h while its
comment claimed 25h, which could skip a local day on the 23h
spring-forward boundary and run the window short. Snap each cursor to
the user-local midnight and advance +25h then re-snap, mirroring the
cadence expander, so every local calendar day is visited exactly once
across 23/24/25h boundaries.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:51:54 +02:00
Marc-André BombeckandClaude Opus 4.8 3b9f95e0b7 fix(measurements): fold late cumulative samples into existing day total
The per-sample cumulative drain strips already-collapsed stats: rows
from the day bucket, so a trailing watch sync that lands after a day's
total was minted leaves the bucket holding only the late samples. The
upsert update branch then replaced the full day total with the partial
late sum, and the contributing samples were already hard-deleted, making
the lost count unrecoverable.

Probe the existing total inside the write transaction and fold the late
sum into it (existing + late) instead of overwriting. The late samples
are genuine additional readings, unlike the legacy-step pass where the
existing total is HealthKit's own aggregate and folding would double-
count. Per-sample rows are still hard-deleted so a re-run cannot
re-accumulate them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:51:54 +02:00
Marc-André BombeckandClaude Opus 4.8 1f00946bfe refactor(insights): tighten the subpage spine and clarify the VO2 max chart
Pull the subpage spine to a denser space-y-3/4 rhythm to match the tighter
stat strip, and refresh the stale brush comment now that the windowed stats
are wired. Give the cardio-fitness page its Gauge stat-strip glyph so VO2
max leads with the same standard treatment as the other HealthKit metrics;
the dedicated subpage already renders the full chart. Clarify the VO2 row's
sparse-series note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:49:46 +02:00
Marc-André BombeckandClaude Opus 4.8 d0d0f49385 feat(insights): add loading skeleton and error+retry to HealthKit subpages
The HealthKit metric pages consumed only `{data, isEmpty}` and ignored the
hook's `isLoading`/`error`, so they painted nothing in flight and fell
through silently on failure. Branch on `isLoading` to a stat-strip + chart
skeleton, and on `error` to a message plus a Retry that re-issues the
analytics query (mirrors the vitals-dashboard pattern). Expose `refetch`
from the analytics hook and add a `subPage.loadError` string in all six
locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:49:46 +02:00
Marc-André BombeckandClaude Opus 4.8 60c5db8f60 feat(insights): combine the blood-pressure stat strips into one card
Render systolic and diastolic as side-by-side columns in a single strip
card rather than two stacked cards, reclaiming the vertical space the
operator flagged. Each column keeps its header and its four stats, and the
single chart's brushed window still updates both columns independently.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:49:46 +02:00
Marc-André BombeckandClaude Opus 4.8 ad2f038963 feat(insights): tighten the stat strip and add a side-by-side multi-series mode
Drop the strip card to a denser py-3/gap-2 rhythm with 44 px stat cells and
text-base values so the numbers-first header gives away less vertical space
on every metric subpage while staying readable. Extract the per-series block
(header + four stats + brushed-window pill) into a reusable piece and add a
`series` prop that renders multiple series as columns in ONE card (two on
desktop, stacking on narrow mobile). Single-series callers keep their
contract unchanged; the brushed-window stats stay per-series.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:49:46 +02:00
Marc-André BombeckandClaude Opus 4.8 80c5f14748 feat(insights): reorder the mood page to the calendar-first spine
Lift the Stimmungskalender above the line chart and place the better-days
assessment directly under the Ziel card. `MoodInsightsSections` now renders by
region (`heatmap` / `assessment` / `rest`); the page threads them so the spine
reads heading → summary → calendar → chart → target → assessment →
classification + breakdowns. All three regions share the one `moodInsights`
query so the fetch is deduped. Every `data-slot` is preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:46:42 +02:00
Marc-André BombeckandClaude Opus 4.8 56934fd658 style(insights): move mood colored text onto semantic feedback tokens
The mood confidence chips, the better-days / influence / crosstab delta
read-outs, and the mood-chart comparison caption coloured their TEXT with
raw `--dracula-*` values. Those stay bright green/cyan/red/purple in light
mode (Alucard) and fail WCAG AA on the white card. Swap the coloured-text
surfaces to the semantic feedback tokens (`--success` / `--info` /
`--destructive`) and the theme-aware `dose-accent` purple, which carry the
light-mode overrides that clear AA in both themes. Decorative chart fills,
strokes, heatmap cells, and legend swatches keep their `--dracula-*` tokens —
those are non-text and already token-based.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:46:37 +02:00
Marc-André BombeckandClaude Opus 4.8 3cd04533be style(insights): tighten mood chart heights and unify the overview rhythm
The mood line chart plots a 1-5 score band; the 240/280 px full-mode height
left a dead strip below the line on the mood subpage. Trim the default to
200/220 px (a per-mount --chart-height override still wins) and lower the
mood-correlation scatter height so the cards fit their content without the
empty gap.

The overview used space-y-8 between top-level blocks while the vitals wrap used
space-y-6, reading as two tiers. Unify to space-y-6 for one consistent vertical
rhythm down the page, in line with the denser-Insights direction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:40:44 +02:00
Marc-André BombeckandClaude Opus 4.8 4be9767428 fix(insights): normalize the assessment-card tile-header icon size
The TileHeader nodeIcon path rendered the caller-passed icon node verbatim and
dropped the canonical h-5 w-5 text-foreground class TileHeader forwards, so a
caller passing an unsized node could drift off the contract. Forward the class
onto a sizing span that pins any child SVG to the canonical box regardless of
what the caller passed. Existing call sites that already pass a pre-sized icon
stay visually identical.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:40:44 +02:00
Marc-André BombeckandClaude Opus 4.8 68ab78d6d8 feat(insights): merge the two mood "what stands out" surfaces into one card
The mood page carried two separate "what stands out" tiles: the narrative
takeaway feed and the FDR-controlled discovered relations, each a full card
under the same heading. Fold them into a single card — one header, the
narrative one-liners, then the discovered relations as a labelled subsection.
Each half gates independently; the card degrades to nothing when both are empty.

Drop the muted FDR explainer paragraph: the statistical caveat rides the
subsection info-icon tooltip, and every finding now reads in the standard
foreground text colour. Remove the orphaned discovery.description string from
all six locales and add the subsection label.

Give the better-days board the assessment-card treatment (insight-assessment
hook, tighter gap/padding, the insight-in entry) so it reads as the mood
assessment, consistent with the per-metric subpages.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:40:44 +02:00
Marc-André Bombeck f25f7f3597 settings: full-width timezone select + match the excluded-sites card surface 2026-06-05 13:40:12 +02:00
Marc-André BombeckandClaude Opus 4.8 4dde3e1d8b feat(insights): recompute the stat strip for a brushed chart window
Wire the metric sub-pages so brushing a window in the chart recomputes the
Min/Max/Median/Mittelwert strip for the visible domain. The page lifts the
shared brushed-window state, passes `selectableDomain` + `onDomainStats` to
the chart, and threads the per-series window stats into the strip. With no
selection the strip keeps the precomputed full-range summary, so the default
is unchanged and costs no extra work.

The strip renders the windowed numbers when present and pins a "selected
range" pill to its header so the user reads the stats as their selection.
Blood pressure brushes both series at once — the single chart reports
systolic and diastolic stats and each strip reads its half.

Covers weight, pulse, blood pressure, and the ~37 HealthKit pages that ride
the shared scaffold. BMI (a derived metric with no first-class strip) and
sleep (no strip) are left as-is. New copy added in all six locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:35:54 +02:00
Marc-André BombeckandClaude Opus 4.8 0995664893 feat(charts): add opt-in brush that reports per-type windowed stats
Add an optional Recharts <Brush> to the shared HealthChart, gated behind a
new `selectableDomain` prop. When set, the chart mounts the brush beneath
the plot and reports the Min/Max/Median/Mean over the brushed window per
type through `onDomainStats`. A range-tab change or a series refetch clears
or clamps the selection so the brush never indexes past the data.

The dashboard, trends-row, VO2 row, sleep, and rationale mounts do not pass
the prop, so they render unchanged — the feature is fully isolated behind
the flag.

`computeWindowStats` is a pure helper (no React/Recharts) so the chart can
summarise the array it already holds without a refetch and the maths is
unit-tested directly. The median uses the same linear-interpolated midpoint
as `summarize()` so a window and the full-range summary read on one
algorithm. `useChartDomainStats` lifts the per-type window into shared state
with a stable callback and a shallow guard against repeat windows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 13:35:44 +02:00
Marc-André Bombeck d4e3533b64 Merge pull request #263 from MBombeck/release/v1.12.6
release/v1.12.6 — one consistent Insights surface
v1.12.6
2026-06-05 13:14:53 +02:00
Marc-André Bombeck 41eda2d163 insights: reword the deferred chart-window stats note without a TODO marker 2026-06-05 13:09:52 +02:00
Marc-André Bombeck 52f4417504 chore(release): v1.12.6 — one consistent Insights surface 2026-06-05 13:01:31 +02:00
Marc-André BombeckandClaude Opus 4.8 22ba4193b2 fix(insights): dedupe coach disclaimer and add wellness-strip error affordance
Suppress the generic Insights disclaimer footer on the Coach route, which
already renders its own composer disclaimer — the layout shell now skips
the footer when the selected segment is the coach page and keeps it
everywhere else. Thread the shared derived-batch error state and refetch
into the wellness strip so a failed batch surfaces a compact error +
Retry in place (the strip is top-of-page) instead of vanishing silently;
the one Retry recovers both the wellness and vitals strips. Adds a
scores load-error string across all locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:58:39 +02:00
Marc-André BombeckandClaude Opus 4.8 aac70ec765 fix(insights): route medication status counts through semantic colour tokens
The taken / skipped / missed counts on the medications page used raw
Dracula green / orange / red, which clear only ~1.9:1 on the white card
in light mode and fail WCAG AA for small text. Route them through the
success / warning / destructive tokens, which carry the light-mode
overrides that clear AA in both themes. The word labels stay as the
non-colour differentiator.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:58:33 +02:00
Marc-André BombeckandClaude Opus 4.8 036370fc88 fix(insights): normalize stat-strip and target panel header rhythm
The stat-strip and target-summary panels were bare card-styled divs with
a flat space-y gap, so their tile header sat at a different offset from
the Card-wrapped tiles (assessment, mood, medication) on the same subpage
spine. Wrap both in Card / CardHeader pb-2 / CardContent so the
header-to-body offset matches. All content and data-slots are preserved;
the panel semantics ride on role + aria-label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:58:29 +02:00
Marc-André BombeckandClaude Opus 4.8 f1f6018f6e fix(insights): align vitals tiles and section headings with the shared tile header
Bring the Vitals grid in line with the harmonized card language. The
sparkline-delta tile led with a muted-uppercase caption + a muted icon;
it now leads with a foreground icon and a foreground heading matching the
canonical tile header, keeping the trailing delta. The Vitalwerte and
Mobility section headings and the provisional baseline / HRV tile headers
move onto the shared tile header (foreground icon + heading) so the
Wellness, Vitals, and Mobility sections share one heading construction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:58:23 +02:00
Marc-André Bombeck 2a8be9fc4a docs(integrations): pin exact WHOOP scopes + webhook URL, add Google Health setup 2026-06-05 12:43:09 +02:00
Marc-André BombeckandClaude Opus 4.8 056581b428 i18n: drop the now-unused therapy-timeline strings
The GLP-1 dose-listing timeline was removed from the medications insight
page; prune its insights.therapyTimeline.* namespace and the
insights.subPage.therapyTimelineDescription key across all six locales.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:36:16 +02:00
Marc-André BombeckandClaude Opus 4.8 18e36e3226 insights(medications): drop the GLP-1 dose-listing timeline and densify the compliance tiles
Remove the chronological GLP-1 therapy-event list from the medications
insight page — it duplicated data already on the per-medication detail
page and added no read on this surface. The page spine collapses to
compliance grid -> target -> assessment.

Route each per-medication compliance card header through the canonical
TileHeader (Pill glyph + med name as the heading) and fold the dose,
category, and streak into one compact muted line. Tighten the card gap,
header padding, progress-bar height, and content spacing so the same
data (7d/30d bars, taken/skipped/missed, heatmap, per-med sentence)
reads in noticeably less vertical space, consistent with the rest of
the harmonised Insights tiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:36:16 +02:00
Marc-André BombeckandClaude Opus 4.8 424d6ee5e1 insights(mood): drop the per-card generic disclaimer, defer to the page footer
The better-days board, the tag-influence list, and the tag × metric
crosstab each repeated a generic associations-not-causes / talk-to-your-
doctor caveat. That sentence now lives once in the page-level Insights
footer, so the three mood cards no longer restate it; the genuinely
specific FDR caveat on the discovered-relations board stays. Drop the
now-unused betterDays / influence / crosstab disclaimer keys from every
locale and update the two component tests that pinned the removed copy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:36:12 +02:00
Marc-André BombeckandClaude Opus 4.8 fcd8eccf6e insights(mood): order classification first, route every tile through TileHeader
Reorder the mood subpage spine so the classification tiles (in-target
share + stability band) lead, then the better-days assessment, then the
descriptive breakdown sections. The classification answers where the user
stands; the better-days board, directly beneath, answers what associates
with their good days.

Every mood card header now routes through the shared TileHeader (icon +
white heading) so the subpage speaks one card language. The in-target and
stability tiles gain proper headings (new inTargetTitle / stability.title
keys across all six locales) and drop the icon-in-a-circle headline glyph;
the narrative feed and the discovered-relations board move into headed
cards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:36:12 +02:00
Marc-André BombeckandClaude Opus 4.8 85c9f3ff3f insights(mood): align mood-chart palette to Dracula tokens
The mood line chart hand-picked hex for its bands and lines: a
three-band red/orange/green ramp that skipped the yellow rung the mood
heatmap and distribution chart use, and a non-token purple primary.
Switch the bands to the same four-step red→orange→yellow→green ramp the
other two mood surfaces ride, and route the line/overlay colours through
the --dracula-* tokens (purple primary matching the health chart, pink
moving average, cyan trend) so the three mood surfaces read as one
palette and a theme change flows through a single source.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:36:12 +02:00
Marc-André BombeckandClaude Opus 4.8 b92728d5e8 insights: lift the wellness strip above the daily briefing
The wellness-score strip rendered inside the vitals dashboard, below the
daily briefing. Lift it out so it sits as its own full-width section
directly under the hero and above the briefing — the overview spine is
now hero, wellness, briefing, vitals, trends, retrospective, signals.

The page now owns the one shared derived batch and feeds the same query
instance to both the wellness strip and the vitals grid, so the lift
adds no second request. Each wellness tile and the section heading lead
with the canonical TileHeader (a per-metric icon plus the metric name in
the foreground colour), matching the rest of the Insights card language.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:33:10 +02:00
Marc-André BombeckandClaude Opus 4.8 a0883bc269 insights: render the wellness score number in the foreground colour
The centred score number painted off the band token (success/warning/
destructive), which cleared only ~1.5:1 against the dark wellness card
and read as illegible. Switch the number to the foreground colour so it
is legible on both the dark and the light card. The band semantic stays
carried by the ring arc fill, the data-band attribute, and the
aria-label, so the colour change costs no information.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 12:33:00 +02:00
Marc-André Bombeck f3f358b208 insights: consolidate the generic disclaimer to one page-level footer
Render the generic "describes your own data, not a clinical assessment or
diagnosis" line once, as a footer under every /insights route in the
layout shell, instead of repeating it across tiles and pages. Add the
insights.disclaimer.footer key in all six locales. Metric-specific caveats
stay on their own cards.
2026-06-05 12:20:59 +02:00
Marc-André Bombeck ea2ce8697e insights: lead every metric subpage with the stat strip above the chart
Render the Min/Max/Median/Mittelwert stat strip directly above the chart
on the canonical spine (intro -> stats -> chart -> target -> assessment),
the numbers-first detail-screen convention. Wire the series headings on
each subpage: blood pressure shows the full four-stat grid for both
systolic and diastolic as labelled blocks, single-series pages pass the
metric name and icon. Leave a marker for chart-brush-reactive stats as a
follow-up.
2026-06-05 12:20:54 +02:00
Marc-André Bombeck d434a6e24b insights: add canonical TileHeader and route shared tiles through it
Introduce a single TileHeader primitive (foreground icon + CardTitle,
matching the assessment-card header) and route the assessment card, the
target reference panel, and the stat strip through it so every Insights
tile leads with the same header language. The target panel now leads with
a single-word "Ziel"/"Target" heading and drops the redundant range
string (the band numbers already live on the range bar); the stat strip
labels each series via a header ("Systolisch"/"Diastolisch" for blood
pressure, the metric name for single-series metrics).
2026-06-05 12:20:48 +02:00
Marc-André Bombeck 5846ce0916 Merge pull request #262 from MBombeck/release/v1.12.5
release/v1.12.5
v1.12.5
2026-06-05 11:42:01 +02:00
Marc-André Bombeck 72146af6f9 chore(release): v1.12.5 — correct sleep summary and readiness contributors 2026-06-05 11:35:26 +02:00
Marc-André Bombeck ea30317723 fix(insights): use the reconstructed sleep night in status and null-out unestablished readiness contributors
The generic metric-status generator read SLEEP_DURATION row-per-stage and
fed the snapshot a single sleep STAGE (~16 min) as the current value, so the
sleep prose reported a fraction of an hour for a full night. Route the
SLEEP_DURATION status/snapshot through reconstructSleepNights — the same
per-night, source-deduped, midnight-aware time-asleep total the dashboard,
series, and targets surfaces use — so the snapshot's series, summary, and
latest value are all the night total in minutes (the registry unit and normal
range are already minutes). Gated to SLEEP_DURATION; no other metric's status
path changes.

The readiness wellness index could surface a contributor value of 0 for an
account that recorded resting heart rate / HRV / respiratory rate but had no
scorable baseline: a non-finite baseline center or today value fell through
clamp100 to a misleading 0 that reads as a real worst-case sub-score. Return
null instead so the client drops the contributor, and document the null-vs-0
contract: null means no usable signal (no baseline, no recent reading,
non-finite input); a finite value — including a genuine 0 — means the baseline
is established and the latest reading really deviates that far.

(cherry picked from commit 0422ac012765f30069a41f662c2b025197ed9a88)
2026-06-05 11:34:59 +02:00
Marc-André Bombeck f9daa94963 Merge pull request #261 from MBombeck/release/v1.12.4
release/v1.12.4
v1.12.4
2026-06-05 10:54:38 +02:00