Commit Graph
1215 Commits
Author SHA1 Message Date
Marc-André Bombeck 9c1c0d86a9 Merge v1.25.1: clear the in-memory query cache at every session boundary 2026-06-28 21:48:16 +02:00
Marc-André Bombeck ea380d6f68 fix(auth): clear the in-memory query cache at every session boundary
The root QueryClient is created once and survives every client-side
navigation, so logout then login on the same browser left the previous
account's non-user-scoped health-data queries in memory and the next
account read them before any refetch landed. Wipe the entire in-memory
cache on logout, on session expiry, and at the start of a fresh login,
alongside the existing IndexedDB and service-worker cache wipe, so no
session ever inherits another account's data.
2026-06-28 21:37:55 +02:00
Marc-André Bombeck dda75abf38 Merge v1.25.1: reset scroll to top on forward navigation app-wide 2026-06-28 21:31:00 +02:00
Marc-André Bombeck 6b7e6f3332 Merge v1.25.1: wellbeing check-in as a standalone module page 2026-06-28 21:30:53 +02:00
Marc-André Bombeck 0109fbbc4c Merge v1.25.1: biomarker detail mirrors the Insights metric sub-page 2026-06-28 21:30:48 +02:00
Marc-André Bombeck 883b6bb5e6 Merge v1.25.1: inbound original-document view/download + age-stratified grip/waist bands 2026-06-28 21:28:23 +02:00
Marc-André Bombeck 37c977e3a4 feat(mental-health): make the wellbeing check-in a standalone module page
Move the PHQ-9 / GAD-7 screener off `/insights/mental-wellbeing` to a
top-level `/mental-wellbeing` route so it no longer borrows the Insights
tab strip and layout shell. The page now uses the standard module-page
frame at the wider content width the other module pages use.

Replace the standing screening disclaimer block and the opt-in/private
line in the header with an info icon after the page heading whose
hover/focus tooltip carries the screening-not-a-diagnosis note. Drop the
now-orphaned `disclaimer` / `optInNote` strings across all six locales
and add the `infoTooltip` string in their place.

The questionnaire, scoring, severity result, PHQ-9 item-9 crisis-resource
handling, focus-on-submit, and 44px touch targets are unchanged. Repoint
the module nav entry and the registry comments at the new route.
2026-06-28 20:48:39 +02:00
Marc-André Bombeck 65daf11be5 fix(ui): reset scroll to top on forward navigation app-wide
The authenticated shell keeps a single <main id="main-content"> mounted
across every route change, so its scroll offset persisted between routes:
scrolling down in one section (e.g. Settings) and navigating to another
opened the next view already scrolled.

Reset the scroll container to the top on each pathname change, with a
window.scrollTo fallback for the body-scrolled shells. Back/forward
navigation skips the reset so the browser's own scroll restoration stands:
popstate arms a one-shot skip cleared on a macrotask so it can never stick.
2026-06-28 20:40:23 +02:00
Marc-André Bombeck ad43eaeff0 Mirror the metric sub-page layout on the biomarker detail
Bring the lab biomarker detail in line with the Insights metric pages:

- Lead with the static catalog description directly beneath the heading,
  the explainer-caption position the metric pages use.
- Drop the early big-value + reference-badge block that did not match the
  assessment-card styling.
- Split numeric and qualitative markers: numeric keeps the stat strip and
  chart, qualitative shows only the latest result text (no strip, no chart,
  no range badge). The assessment card now renders for any marker with
  readings and self-suppresses for qualitative-only markers.
- Move the full reading history off the detail page onto a dedicated
  /labs/[biomarkerId]/values route, reached by a top-right list control,
  matching the metric pages' show-all-values affordance.
2026-06-28 20:37:01 +02:00
Marc-André Bombeck 08314c7c09 feat(documents): view and download the original inbound document
The inbound-document store keeps the uploaded doctor letter encrypted at
rest (InboundDocument.contentEncrypted, AES-256-GCM), but there was no way
to read the original back — the decrypt helper had been dropped as unused.

- Restore decryptDocumentFromBytes in the document store, fail-closed on a
  bad / missing key id.
- Add GET /api/documents/inbound/[id]/original: owner-scoped (userId-narrowed
  query), module-gated, returns the decrypted bytes with the stored MIME
  type. A PDF / image is served inline so the browser can render it; other
  types download as an attachment. The filename is sanitised before it
  reaches the Content-Disposition header. The bytes are PHI — never logged,
  never served cross-user, and a decrypt error fails closed (500) rather than
  returning ciphertext. Cache-Control is private, no-store.
- Surface a 'View original' action in the document review screen.
- Document the route in the OpenAPI registry and regenerate the spec.
- Cover the route with a test: owner-scoped success, attachment fallback,
  404 for another user's document, and fail-closed on decrypt error.

i18n: viewOriginal across all six locales.
2026-06-28 20:33:29 +02:00
Marc-André Bombeck d3967bcdd0 Surface pre-existing conditions in the medical-history section
The chronic-conditions and Coach-focus self-context read as foundational
medical history, so the Anamnese section now surfaces them as a read-only
shared view next to allergies and family history, with a link to their single
editing home in personal context. Personal context links back to the medical
history. The card is coach-gated and reads through the existing
/api/coach/about-me query key, so nothing is moved or duplicated and there is
no schema change.
2026-06-28 20:29:21 +02:00
Marc-André Bombeck cab0e50fff Collapse the active-sessions and security-activity lists by default
Both account-security lists now render behind a disclosure control and open
only on click. The trigger carries aria-expanded and aria-controls and is
keyboard-operable; the region is gated with hidden. State is UI-only and not
persisted.
2026-06-28 20:29:15 +02:00
Marc-André Bombeck d15dd26ebf Drop the Coach-hero medical disclaimer
The new-chat hero carried a standing self-description line. Remove that
instance; the greeting and composer stay. The MedicalDisclaimer component
and its insights/labs/medications placements are unchanged.
2026-06-28 20:29:10 +02:00
Marc-André Bombeck 0f2b548138 style: apply prettier 3.9.1 formatting
Mechanical reflow from the prettier 3.9.1 bump; no behaviour change.
2026-06-28 19:26:16 +02:00
Marc-André Bombeck 4ae783064d fix(ci): clear the v1.25 release-branch gate failures
- Reword two LOINC verification comments in the signals registry so they
  no longer trip the forbidden-marker grep, keeping the meaning intact.
- Resolve the dead-code gate: gate the seeded-question opener on the
  per-user ambient-suggestions opt-out (wiring the previously orphaned
  resolveCoachAmbientSuggestionsEnabled, server-authoritative), drop the
  unread decryptDocumentFromBytes helper and its now-unused import, and
  remove the unused SignalKind type alias.
- Delegate undici's fetch to the global stub in the provider-chain
  integration test so the egress-pinned client HTTP is mocked again.
- Defer the JSON example-download cleanup a tick so revoking the blob URL
  no longer races the browser download start (stabilises the e2e).
- Sync the service-worker version fallback to v1.25.0.
2026-06-28 19:23:57 +02:00
Marc-André Bombeck 3d304acf1e merge: QA fix polish into release/v1.25.0 2026-06-28 18:36:56 +02:00
Marc-André Bombeck 8e4c107a18 merge: QA fix mental-health into release/v1.25.0 2026-06-28 18:36:26 +02:00
Marc-André Bombeck a69697b50f fix(insights): wire grip / pain / waist / waist-to-height across web and MCP
Grip strength, pain (0–10 NRS), waist circumference, and waist-to-height
existed in the measurement enum, signal registry, and FHIR export but had
no web entry, no detail page, single non-sex-aware bands, and a split-brain
MCP exposure. This closes those gaps.

Web capture: the four signals join the manual measurement form, so a
web-only self-hoster can log them (the iOS client and the measurements API
already could).

Detail pages: flip detailPage on for the four signals and mount each on the
shared HealthKit metric template (chart, stat strip, Coach-read strip,
assessment card) via routed sub-pages and tab-strip pills, the same detail
spine every other metric carries.

Sex-aware bands: grip strength (EWGSOP2 men 27 / women 16 kg) and waist
(WHO men 94 / women 80 cm) now classify by the profile sex through the
age/sex norm resolver the assessment already threads; the resolver also
returns a sex-specific, age-independent band when no date of birth is set.
Fix the waist-to-height boundary so the NICE >= 0.5 rule is correct (the
last in-range value is 0.49).

MCP single source of truth: decouple the registry mcp facet from the Coach
snapshot — a signal can be MCP-readable without being on the Coach surface.
Mark the four physical signals mcp:true, derive the MCP rich-reads clinical
set from the registry flag instead of a duplicate allowlist, and relax the
invariant to coachSnapshot implies mcp (not the reverse). PHQ-9 / GAD-7 and
the environmental signals stay mcp:false and unreachable. compare_metric and
detect_changepoints now advertise clinical-signal support to match
get_metric_baseline.
2026-06-28 18:33:53 +02:00
Marc-André Bombeck 13ee5e2df4 fix(labs): paginate the biomarker reading feed
The detail page fetched a single limit=500 window and sorted client-side,
silently truncating any marker that crossed 500 readings. Switch to an
offset-paginated useInfiniteQuery: it loads a page at a time, a 'Load more'
control reveals the rest, and the chart and stat strip render the
accumulated set so loading more extends the trend.
2026-06-28 18:30:39 +02:00
Marc-André Bombeck e43089df39 fix(settings/environment): submit-on-Enter city search, zero-result feedback, validated timezone
Wrap the home and travel city-search inputs in a form so Enter runs the
search instead of doing nothing, and show a 'no matching places' line when
a geocode returns nothing rather than leaving the user without feedback.
Validate the home timezone against the runtime IANA set (a bogus zone
would silently mis-key every stored environment day). Grow the result-row
and travel-remove controls to the 44px touch floor.
2026-06-28 18:30:21 +02:00
Marc-André Bombeck a3b32524f7 fix(documents): skeleton loading, calmer review badge, larger touch targets
The inbound-document list and review panels used centered spinners; swap
them for the app's Skeleton pattern so the resolved content lands without
a layout jump. The low-confidence review badge moves from destructive red
to a calm amber, matching the surface's non-alarming posture. Approve /
reject / edit / save controls grow to the 44px touch floor, the upload
file input gains an accessible label, and each document row exposes
aria-expanded.
2026-06-28 18:29:45 +02:00
Marc-André Bombeck 38c00100d0 fix(insights): sleep-debt explainer on the resolved card; Skeleton primitive for the narrative loader
The sleep-debt computed-engine InfoHint only rendered in the still-learning
state — but the resolved full-debt card is the common case and where the
figure most needs the 'this is our own estimate, not a wearable's native
number' context. Show it there too. Separately, swap the period-narrative
loading shell's hand-rolled muted divs for the shared Skeleton primitive
so its shimmer matches every other loader.
2026-06-28 18:29:39 +02:00
Marc-André Bombeck 8e238d1834 a11y: lift the shared Coach icon and info-hint to the 44px touch floor
The top-right Coach hand-off icon button and the info-hint trigger sat
below the WCAG 2.5.5 44px minimum on touch. Grow both hit boxes to 44px
on touch while keeping the visible footprint unchanged — the Coach button
is a fill-less ghost, and the info-hint offsets its padding with an equal
negative margin so inline layout is untouched.
2026-06-28 18:29:33 +02:00
Marc-André Bombeck 5f0ca355f9 fix(mental-health): a11y on the check-in result and option controls
On submit the form (and its submit button) unmounts, dropping focus to the
body — move focus to the result heading so keyboard/AT users land on the new
content. Announce the item-9 crisis-resource card via role="alert" so a
screen-reader user hears the resources. Bump the PHQ/GAD scale and
functional-difficulty option buttons to the 44px touch-target floor.
2026-06-28 18:25:43 +02:00
Marc-André Bombeck f62ba50ff0 feat(mental-health): gate the PHQ-9/GAD-7 check-in behind an opt-in module
The mental-wellbeing screener surface shipped built but unreachable: no nav
entry, no module key, ungated. Add a `mentalHealth` module key (opt-in,
default OFF — a depression/anxiety self-assessment is at least as sensitive
as mood) to the registry, the module-hub icon map, and the exhaustive module
maps. Give the check-in a nav home at /insights/mental-wellbeing, born-gated
on the module so it appears only once the account opts in, and redirect the
page home on a direct hit when the module is off.

Item answers stay encrypted and off the AI Coach / MCP regardless of the
toggle; this only controls whether the surface is visible and reachable.
2026-06-28 18:25:27 +02:00
Marc-André Bombeck 4f39058629 Merge branch 'feat/v125-env-location-conservative' into release/v1.25.0 2026-06-28 17:47:35 +02:00
Marc-André Bombeck 3bd52a322b feat(self-description): add the non-diagnostic / intended-purpose standard
Introduce one canonical "what this is / is not, not a medical device"
statement plus a layered, calm disclaimer set, and apply it on the
surfaces that earn a context note.

- New shared `MedicalDisclaimer` component renders the copy as plain
  React text (no markdown renderer, per the XSS posture). Variants:
  intendedPurpose, coach, insights, labs, medications, emergency,
  dataPosture.
- New `selfDescription` i18n namespace across all six locales: the
  intended-purpose line, the per-surface notes, the emergency note, and
  an explicit data-posture statement (self-hosted, data stays on your
  instance, encrypted at rest, BYOK / local model, no third-party
  analytics).
- Apply once per surface, layered not plastered: a calm standing line on
  the Coach new-chat hero, one framing line in the Insights layout shell
  (covers the briefing and trends), a labs note (ranges vary, only a
  clinician interprets), a neutral medications note, and the data-posture
  line where the provider key is configured. Mental-wellbeing keeps its
  existing crisis copy; records keep their existing note.
- Extend the Coach system prompt (EN + DE) with a self-description ground
  rule so the Coach answers "what are you / where does my data go"
  honestly and stays non-diagnostic.

App-authored copy was already observational; the few diagnose/treat
tokens are negations, idioms, or framed as the user's own self-reported
statement, so no softening was required.
2026-06-28 17:43:31 +02:00
Marc-André Bombeck bacd591dee feat(environment): frame overrides as location periods and surface effective-from
Settings → Environment now reads the override list as "location periods": a place
plus a date range that covers a trip OR corrects a stretch of earlier history,
overriding the home for the days it spans. Show the home's effective-from date
and prefill the backfill start with it, so the default span matches what the
server will actually store. Strings updated across all six locales.
2026-06-28 17:36:21 +02:00
Marc-André Bombeck a27a1ee71d Merge feat/v125-inbound-documents into release/v1.25.0
Inbound documents module (migration 0218).

# Conflicts:
#	messages/de.json
#	messages/en.json
#	messages/es.json
#	messages/fr.json
#	messages/it.json
#	messages/pl.json
#	prisma/schema.prisma
#	src/lib/modules/__tests__/gate.test.ts
#	src/lib/query-keys/index.ts
2026-06-28 17:05:50 +02:00
Marc-André Bombeck a1b4f0d00e Merge feat/v125-environmental into release/v1.25.0
Environmental context module (migration 0217).
2026-06-28 17:02:36 +02:00
Marc-André Bombeck 088cad6c64 feat(environment): add opt-in module, settings surface, API, and nightly fetch
Add the opt-in Environment module (default off), its Settings surface (home
location search, travel overrides, backfill, with Open-Meteo attribution), the
gated API routes (overview, home, geocode, travel CRUD, backfill), the nightly
pg-boss fetch job with a lookback window plus on-demand backfill, the query
keys, six-locale strings, and the regenerated OpenAPI. The module performs an
outbound weather fetch tied to a coarse location, so it ships dark and the user
turns it on deliberately; off means no job, no row, no egress.
2026-06-28 16:58:52 +02:00
Marc-André Bombeck cdee3e3efc feat(environment): register env signals and wire weather into correlation discovery
Register the seven environmental-exposure signals through the signal registry
as a new "environment" kind (correlation-eligible; off Coach/MCP/FHIR in v1),
and fold their daily series into the correlation engine as lagged behaviour
channels. Weather/daylight now pair against mood, sleep, and vitals through
the existing FDR / effect-size / shrinkage gates, so a thin series degrades to
absent rather than a spurious link. All env channels share one ENVIRONMENT
family so two same-day fields never lag-correlate against each other.
2026-06-28 16:58:30 +02:00
Marc-André Bombeck 422778f679 feat(documents): module gate, nav, and review-then-confirm UI
Add the opt-in inboundDocuments module (off by default, like the MCP
endpoint): ingesting a clinical document egresses it to the OCR/vision
provider, so the surface ships dark and the user turns it on from the
Modules hub. The born-gated /documents page carries the prominent
non-diagnostic disclaimer (we read, we don't interpret; nothing is saved
until you approve it; only your clinician can interpret these results),
uploads a document, and walks each extracted fact: source provenance and
confidence are shown, low-confidence facts are blocked from approval until
edited, and the confirm step commits the approved facts.
2026-06-28 16:57:16 +02:00
Marc-André Bombeck e216a9d678 feat(coach): warm, localized proactive nudge with opt-in AI enrichment
Rework the proactive Coach nudge so it reads like one human checking in on
another instead of a terse English alert.

Copy: the title is a name-led greeting that rotates day to day ("Guten
Morgen, …" / "Hey …"; the 05:15 frame makes a morning opener safe) and the
body is one calm observation plus a gentle invite — never an imperative.
Drop the verbatim quoting of the user's decrypted self-context sentence:
the focus is referenced only by presence, abstractly, and is no longer
decrypted at all. Remove the 80-char mid-word clamp; each channel composes
to a length budget instead.

Cadence: suppress the nudge when the user already talked to the Coach in
the last ~24 h. With the existing rolling frequency cap (never two in a
window) and the no-trigger-no-nudge rule, the surface stays anti-nag.

AI enrichment (opt-in via the coach prefs blob, default off): when a
provider is healthy the body is composed through the model under hard
guards — a per-user budget gate, a tight per-call timeout, a per-tick
ceiling so the sequential tick can't stall across the user base, and the
outbound safety screen on the output. Any error, timeout, budget refusal
or unsafe reply falls silently back to the deterministic template, which is
always the default and the fail-closed fallback.

Localized greetings, per-trigger bodies and the focus note across all six
locales; the prefs opt-in is surfaced in the notification settings.
2026-06-28 16:50:49 +02:00
Marc-André Bombeck 65f1358179 Merge branch 'feat/v125-coach-insights-rest' into release/v1.25.0 2026-06-28 16:10:48 +02:00
Marc-André Bombeck ce2bddcc51 Merge branch 'feat/v125-records' into release/v1.25.0
# Conflicts:
#	docs/api/openapi.yaml
#	messages/de.json
#	messages/en.json
#	messages/es.json
#	messages/fr.json
#	messages/it.json
#	messages/pl.json
#	scripts/rotate-encryption-key.ts
#	src/lib/crypto/encrypted-columns.ts
#	src/lib/openapi/routes/index.ts
#	src/lib/query-keys/index.ts
2026-06-28 16:10:23 +02:00
Marc-André Bombeck c322a68d19 feat(mental-health): screener UI, locale-aware copy, and OpenAPI surface
Add the opt-in PHQ-9 / GAD-7 questionnaire surface at /insights/mental-wellbeing:
instrument picker, 0–3 item scale, server-scored result with the validated
severity band, the verbatim Pfizer/PHQ attribution, the screening-not-diagnosis
disclaimer, and a calm crisis-resource card on a positive item-9. The surface
deliberately does NOT mount the shared Coach-launch card — mental-health item
content stays out of the AI.

- i18n: full mentalHealth block plus the six new measurement-type labels and the
  longevity-marker catalogue names/descriptions across all six locales
  (de/en/es/fr/it/pl). Instrument item wording uses the validated German text and
  the canonical English elsewhere (re-wording invalidates the score).
- OpenAPI: regenerate the contract for /api/mental-health/assessments (response
  excludes raw item answers by construction).
- Exclude the new manual / instrument types from the HealthKit-counterpart guard.
2026-06-28 16:05:51 +02:00
Marc-André Bombeck d570d558fa feat(insights): make sleep debt a configurable source ladder with a computed explainer
Generalise sleep debt onto the existing per-metric source-priority picker
(a new sleepDebt ladder, native-above-computed, default WHOOP > Oura > Polar >
computed) instead of assuming one provider. The rhythm read resolves the active
source off the ladder and carries it on the DTO; when the active source is our
own COMPUTED engine — every user today, since no provider ships a native
sleep-debt balance — the Sleep page explains what the figure means and that it
differs from a wearable's native number. The computed model itself is unchanged
and correct (a short rolling balance of need minus sleep, surplus-credited);
only stale comments that still referenced the old 14-night window are corrected.
2026-06-28 16:04:22 +02:00
Marc-André Bombeck c862b4880f feat(coach): add an opt-out for proactive ambient suggestions
Add a per-user toggle that turns off the proactive ambient example suggestions
(the daily seeded opener on the Coach hero). The flag rides the existing coach
notification-prefs blob (coach.ambientSuggestions, default on) — no schema
change — and a shared hook makes it the single source every ambient suggestion
surface consults, so the seeded opener and its fetch both stand down when off.
2026-06-28 16:04:22 +02:00
Marc-André Bombeck e55058a1ce fix(insights): stop the period review card leaking methodology and raw enums
The weekly/monthly review card rendered an internal methodology line plus the
raw MeasurementType tokens it drew on ('Based on: WEIGHT, BLOOD_PRESSURE_SYS…'),
which read as broken and added no value. Drop the provenance caption entirely so
no enum token is ever user-facing, and hide the card whenever there is no real
generated narrative (no 'preparing' husk), consistent with the hide-when-empty
pattern. The query keeps polling in the background, so the card appears the
moment a narrative lands.
2026-06-28 16:04:22 +02:00
Marc-André Bombeck fba8553192 fix(insights): remove the trajectory continuation tile from metric pages
The short-horizon trajectory tile read as a wall of text with no clear value
on the metric pages. Drop it from the weight page and the shared HealthKit
metric page (the opt-in forecast slot is removed entirely, not left dead), and
delete the now-orphaned tile + fan components. The trajectory engine stays for
the Coach narrative; only the metric-page surfacing is removed.
2026-06-28 16:04:22 +02:00
Marc-André Bombeck da132a75dd feat(coach): wire the composer mic to real dictation and hide it when unusable
The composer mic advertised dictation but errored on tap in environments where
the Web Speech API cannot serve. Gate the control on the API being present AND
a secure context, map the locale to a region-qualified recognition language,
and retire the button for the session on a permission refusal. When dictation
is genuinely unavailable the mic hides rather than showing a control that
errors on every tap.
2026-06-28 16:04:22 +02:00
Marc-André Bombeck 9d22412caf fix(coach): size the example opener to its text and keep it single-line
The ambient example opener was stretched to the full composer width, leaving
a large empty tail that read as broken. Size it to its text instead (capped at
the full width) and guarantee a single line: the question truncates with an
ellipsis on a narrow phone rather than wrapping or forcing a horizontal scroll.
2026-06-28 16:04:22 +02:00
Marc-André Bombeck a37883e3f8 feat(records): surface allergies + family history in Settings
Add a new "Medical history" (Anamnese) Settings section that hosts the two
structured-record managers — allergies and family history — each a
list + add/edit sheet + delete-with-confirm built on the shared query-key
factory and the ResponsiveSheet pattern. It sits between the illness journal
and the preventive-care reminders and is always shown (not module-gated, like
Vorsorge), since these are foundational health-profile records.

The section carries a plain non-diagnostic note ("your own records, not a
medical diagnosis"). Full i18n across all six locales (de/en/es/fr/it/pl).
2026-06-28 16:03:25 +02:00
Marc-André Bombeck e4875db93e feat(signals): register the v1.25 clinical signals through the registry
Add the clinical-signals wave's new measurement types and wire them through
the signal registry so FHIR, correlation eligibility, ingestion, and the
measurement-list surfaces derive from one definition each:

- MeasurementType: PHQ9_SCORE, GAD7_SCORE, GRIP_STRENGTH, PAIN_NRS,
  WAIST_CIRCUMFERENCE, WAIST_TO_HEIGHT (+ AssessmentInstrument enum and the
  MentalHealthAssessment model that backs the screener scores).
- Registry: the six measurement/score signals plus the longevity lab panel as
  kind:"biomarker" entries; the FHIR golden fixture is regenerated in lockstep.
  Grip-strength and waist-to-height carry a null LOINC (NEEDS-VERIFY) and emit a
  local text concept until a stable code is confirmed.
- FHIR: add the survey observation category for the screener / pain signals.
- Labs: extend the biomarker catalogue with ApoB, Lp(a), fasting insulin, and
  the omega-3 index; add LOINC codes for those plus GGT and hs-CRP; add a
  computed HOMA-IR helper.
- Exhaustive maps updated to keep the enum coverage, category, list-meta, and
  personal-record drift guards green.
2026-06-28 15:47:25 +02:00
Marc-André Bombeck 73ec05de26 fix(insights): merge the sleep page's two assessments into one
The sleep page rendered an assessment under the duration/architecture charts
and a second one under the sleep-quality scores. The quality assessment mostly
never generated — most accounts carry no wearable sleep score — so it sat at the
foot of the page as a duplicate empty card or a redundant connect-a-provider
prompt.

Drop the quality-block assessment so the duration card is the page's single
assessment; the quality block keeps its score tiles. Remove the now-unused
grounded-note fallback and its deterministic grading helper, and the orphaned
quality-assessment message keys across every locale.
2026-06-28 15:13:32 +02:00
Marc-André Bombeck f599ba9a92 feat(insights): move the assessment-card Coach hand-off to a shared top-right icon
The per-metric assessment card carried the Coach hand-off as a bottom-right
text button, present on only some pages. Pin it to the card header's top-right
as a single icon button (no text label) with an accessible name and a tooltip,
and wire every assessment surface to it: the bespoke slug pages
(weight, BMI, pulse, blood pressure, mood), the generic metric scaffold, the
medication-compliance card, and the biomarker detail page already on the shared
card. Each opens the Coach with a localised, auto-sent opener narrowed to that
metric's snapshot source.

The button self-gates on the Coach triple (provider mounted, operator flag,
per-user opt-out) and the card only mounts it on the populated branch, so it
never advertises a control that cannot work. The auto-sent opener now
interpolates the localised measurement name rather than a raw token.
2026-06-28 15:13:23 +02:00
Marc-André Bombeck c0a91dbbcd Merge branch 'fix/375-generated-narratives' into release/v1.25.0 2026-06-28 14:42:46 +02:00
Marc-André Bombeck 32885d1218 Merge branch 'fix/374-share-render' into release/v1.25.0 2026-06-28 14:42:26 +02:00
Marc-André Bombeck 95ee088646 Merge branch 'feat/biomarker-detail-page' into release/v1.25.0 2026-06-28 14:42:04 +02:00