Wave A removed the inline ask-the-Coach / suggested-questions block from
the overview hero (Coach is reached via the FAB + drawer). Update the
stale assertion that still expected onAskCoach/onPickPrompt + the launch
context on the page.
Remove the single-night "Last night" hypnogram from the sleep overview: its
per-stage breakdown (time + %) duplicated the phase-distribution stacked bar
directly below it. The /api/sleep/night route stays (the dashboard reads it);
only the redundant surface is gone.
Promote the chronotype to the prominent bottom block of the view. Split it out
of the rhythm section (now the sleep-debt headline alone) into its own section:
a large band label with the natural sleep-midpoint clock beneath it and an
expandable disclosure for social jetlag + MSFsc. It reads the same
["sleep-rhythm"] cache, so it adds no round-trip, and keeps the calm
"still learning your rhythm — N of M nights" state.
Fix the sleep-quality assessment slot. It disabled the status fetch whenever the
AI narrative was absent, so the card could not read hasProvider and fell back to
its no-provider default — surfacing the "connect an AI provider" CTA even for
accounts that have a provider connected, stacked under the grounded note. The
card now stays enabled and reports the truthful state: no provider shows its
clean AI-settings CTA alone; a connected provider with no text yet shows the
grounded note; warm or preparing AI text wins. Exactly one block fills the slot.
Redesign the Coach frontend toward a calm, prompt-first reading
experience:
- Narrow the reading column to a centred max-w-2xl with a roomier
vertical rhythm so the conversation reads like a document rather than
a packed log.
- Make the page-surface conversation rail collapsible and collapsed by
default. A single labelled toggle in the rail strip opens it (lg+),
the rail heading carries a matching close control, and the column
folds to zero width with a smooth grid transition. Below lg the
bottom tray is unchanged. The fullscreen header keeps only the
minimize control.
- Replace the boxy default scrollbar on the thread, history list, and
composer with a thin, rounded, subtle bar across WebKit and Firefox,
scoped to the components.
- Polish the pre-stream beat: the Thinking indicator now pairs bouncing
dots with the label, and the assistant turn fades in so the hand-off
to streamed tokens reads as one motion.
- Return voice dictation to the composer via the Web Speech API. The
mic mounts only where the browser supports recognition, dictates into
the textarea, and is keyboard- and screen-reader-accessible.
New labels land in all six locale bundles.
The score dials wore a token-tinted drop-shadow bloom, a conic specular
sheen, a breathing pulse on the green ring, and a dark-to-neon two-stop
arc gradient. Against the flat 2-D Recharts charts that read as a glowing
3-D gauge and made the overview restless.
Make the calm treatment the default everywhere, not only behind the `flat`
prop:
- Drop the bloom, the sheen, and the pulse for every ring. `.wellness-ring-arc`
is now a plain flat stroke; the sheen overlay paints nothing. The shared
cycle ring inherits the same quiet.
- Mute the arc colours toward the chart palette: the two gradient stops sit a
hair apart for a whisper of depth instead of a neon sweep, pulled down and
desaturated per hue in both the Dracula and Alucard sets. Band semantics
(green/yellow/red thresholds) are unchanged.
- `flat` now only drops the entrance sweep + count-up, so the dashboard hero
ring paints its value at once. The /insights strip keeps the subtle spring
fill; prefers-reduced-motion still zeroes it.
Score and band logic, thresholds, and the data-band/aria contract are
untouched.
Enrich the Vorsorge cards with a discreet 7-day trend strip and a path
into the underlying readings.
- A quiet seven-bar strip of the reminder metric's last seven readings
sits under the metric context on both the dashboard summary card and
the full-grid cards, mirroring how a dashboard tile carries a small
trend. Neutral muted bars, no status colour. A free-text reminder or a
metric with fewer than two readings shows nothing, so card height stays
stable. The strip reads a bounded last-7 window per type through the
existing measurements endpoint, cached by type under the measurements
invalidation prefix.
- The card kebab gains a 'Show measurements' item for measurement-linked
reminders that opens /measurements pre-filtered to that type.
- The measurements list accepts a ?type=<MEASUREMENT_TYPE> deep link that
seeds the existing client-side type filter while leaving the rail
interactive; unknown tokens are dropped. The ?add= behaviour is
unchanged.
- New labels added across all six locales.
Render the new signalsOfDay list at the top of the daily-briefing card — a
present-focused row (headline + concrete nudge) sharing the tone bar, metric
icon, and sub-page routing of the key-finding row, with matching typography
(text-sm headline, text-xs muted body) so the card reads consistently with the
rest of the insight surfaces. Add the signalsTitle string across all six
locales.
Remove the "Ask the coach" action button and the guided-questions chip strip
from the overview hero band: the Coach stays the bottom-right drawer mounted by
the insights layout shell. The hero is now greeting + subtitle + baseline meta
+ the optional Health Score panel, and no longer reads the coach flags or the
launch context. Update the coach-cascade / disable-cascade fixtures and the
hero-strip slot tests to pin the affordances absent.
Compute a present-focused "signals of the day" block in extractFeatures from
the in-memory measurement set (no extra query): the freshest reading vs the
user's own 7d/30d windows, the normal-swing verdict, an emerging slope, and a
recent anomaly per salient metric, ranked by clinical priority and capped at
five. Add a signalsOfDay array to the dailyBriefing schema feeding the
briefing alongside the existing keyFindings cap, and rewrite the briefing
prompt (EN + DE) to lead with NOW: a warmer, motivating read that surfaces the
emerging signal and gives one concrete, doable nudge while still drawing on the
tiered history.
The signals block lands inside the compacted features payload, so it also
seeds insightsSnapshotHash — a fresh daily signal now changes the fingerprint
and forces the daily 04:30 warm to regenerate instead of re-stamping a
days-old narrative. Bump PROMPT_VERSION to 4.29.0.
Introduce buildTieredSeries(): a five-band progressive data-context helper —
0-14d raw daily, 14-30d DAY, 30-90d WEEK, 90d-1y MONTH, >1y YEAR — plus a
per-metric min/max-envelope anomaly list so a peak from months ago survives a
coarse bucket. Reuses probeRollupCoverage / ensureUserRollupsFresh with the
existing replace-fall-back-on-miss contract; readers never throw, so a
coverage gap simply yields an empty band. Caps each metric near 600 tokens to
stay within the existing context budget. Shared across the Coach snapshot and
the daily briefing.
The health-record section (PDF + FHIR R4 export) was the only entry-point
gated on the doctorReport module's nav toggle, so flipping that opt-out in
the Modules hub made the whole section vanish from Settings with no
in-context way back. Drop the nav gate: the entry-point now always shows.
The doctorReport module still governs the data layer — the server-side
/api/export/health-record route stays the hard enforcement (403
module.disabled over Bearer when opted out). Assert the entry shows for a
default, empty, and opted-out modules map.
The message thread used h-full inside a plain-block wrapper, so its height
resolved against an auto-height parent: the scroll container grew with its
content instead of scrolling, pushing the composer down and leaving a stray
scrollbar on the page.
Make the thread wrapper a flex column and switch the thread (and its empty
state) from h-full to min-h-0 flex-1, so the scroll region takes a bounded
track from the flex parent. The conversation now scrolls cleanly inside the
card while the composer stays pinned to the bottom.
Mirror the medication card's name + cadence + status layout:
- the cadence ("alle 7 Tage") renders as a chip directly to the right of the
metric name via a new MedicationCardHeader nameChip slot, instead of on the
state-badge row below the category;
- the due / overdue status renders as plain text, not a chip;
- the "zuletzt erledigt" line always renders, falling back to an em dash when
there is no last-done value, instead of being hidden.
The green action button is unchanged. Also drop the per-module tour trigger
from this surface (the tour is first-time-only now).
The guided tour now exists only as the first-time auto-start after onboarding.
Remove every in-app trigger that re-opened it: the per-module "show this tour"
button on the dashboard, measurements, mood, medications, labs, illness, cycle,
achievements, insights, and coach surfaces; and the Settings replay card.
Delete the now-orphaned ModuleTourTrigger component, the tour-restart worker,
the healthlog:module-tour and healthlog:tour-restart event listeners, the
single-stop overlay path, and the force-launch session marker. The first-time
auto-launch gate and the tour overlay itself are unchanged. Prune the orphaned
i18n keys across all locales and update the affected tests.
The settings and admin shells reserved the page-heading height in the left
nav with a hidden replica block. The replica wrapped differently in the 220px
nav column than the real heading in the wide content column, so the first nav
item sat too low and drifted across locales.
Hoist the heading into the shell and place it in its own grid row spanning only
the content column: row 1 holds the heading, row 2 holds the nav and the cards.
The nav's first item and the first card now start the same grid row, so they
align by construction regardless of how the title or subtitle wraps. Drop the
spacer component.
Bump undici 8.4.1 -> 8.5.0 (GHSA-vmh5-mc38-953g, TLS certificate
validation bypass in the SOCKS5 ProxyAgent) and nodemailer 9.0.0 -> 9.0.1
(GHSA-p6gq-j5cr-w38f), both direct dependencies. Clears the production
high-severity dependency audit; no API change.
buildTargetBands was a same-file alias of buildDashboardBands, which the
dead-code gate flags as a duplicate export. Keep the historic name only as
a re-export alias from the snapshot module, so callers and the parity test
are unchanged while bands.ts exposes a single builder.
Cover the whole release — offline reads, cited reference ranges + Coach
grounding, responsible safety alerts + diabetes opt-in, the module tour,
the module-consistency and settings/admin IA pass, sleep/coach polish,
and the reminder completion route — and correct the additive migration
note (0173–0177).
Drop the health-score card's inert onAskCoach prop and its now-dead
pass-through from the hero strip, the buildDashboardBands re-export that no
caller reached through the snapshot module, an unused tour-progress status
type, and an orphan service-worker cache helper.
Drop the overstated claim that the due-reminder green button mirrors the
medication card (the med take button is always the default tone), fix the
module-tour anchor attribute named in a doc comment, and feed the active
section title into the settings nav spacer so its heading line wraps the same
way a long localized title does.
The dashboard recomputed the client band fallback on every render even when
the server snapshot supplied bands, and the module-list-prefs hook reparsed
its stored JSON into a fresh object each render, defeating downstream
memoisation. Wrap both in useMemo keyed on their real inputs.
Remove three diabetes settings strings whose web toggle was never built, four
dead reference-state explainer keys with no consumer, and the generic
reminder-toggle aria label that the per-row aria-labelledby wiring replaced.
The backend and resolver behind the diabetes preference stay in place.
The diabetes PATCH parsed its body without a size cap; bound it through
safeJson like the sibling routes. Label each reminder's enable/disable switch
by its own name span so screen readers hear the reminder rather than one
generic label repeated per row. Point the chronotype and illness disclosure
triggers at the regions they reveal via aria-controls.
The one-time disclaimer acknowledgment route shipped without an OpenAPI
entry, leaving a gap in the contract the iOS client consumes. Add the POST
path and regenerate the spec.
The proactive-nudge frequency gate only counted delivered push attempts. A
user with no push channel never wrote a delivered row, so the cap was inert
for them: every cron tick that hit a persistent trigger minted a fresh nudge
conversation in the rail.
Count a recently persisted nudge conversation toward the same rolling window
so the once-per-window contract holds regardless of push outcome. Add a
covering index on coach messages for the assistant-message scans the FAB poll
and the gate both run, and drop the unused providerType parameter from the
persistence helper now that the tag is fixed.
The per-metric inline coach launch icon was removed from the insights
sub-page header; the floating FAB is now the single entry point. The spec
still asserted the removed icon, so every inline-launch case would fail.
Rewrite the per-slug and drawer-open cases to assert the inline icon is
absent and to open the drawer via the FAB (the pattern the surviving unread
case already used), and update the docstring. Route mocks and the hydration
gate stay as they were.
The Vorsorge "Jetzt messen" / mark-done button hard-coded white text on a
solid green fill. In the dark theme the fill is the bright Dracula green, so
white-on-green reads about 1.37:1 — below the WCAG AA floor.
Add a theme-aware `--success-foreground` token (near-black glyph on the dark
fill, white on the darker light-mode green) mirroring the existing
`--primary-foreground` pattern, and use `text-success-foreground` on both the
card and list variants. Green stays only on the action button; the card
surface remains neutral.
Add the GET /api/insights/coach/nudge-status unread signal and the POST
/api/insights/coach/seen open-stamp to the OpenAPI contract and regenerate
the spec.
Remove the page-level Insights disclaimer footer and the health-score and
correlation card disclaimers now that the not-a-diagnosis acknowledgment
is shown once at onboarding and the legal text stays on the privacy page.
Device-provenance, contraception-safety, statistical-method and
shared-report notes stay where they convey specific information.
Teach SettingsSectionFrame an explicit title/subtitle mode alongside the
slug-driven mode, and have the per-module settings pages (Vorsorge,
Illness, Labs) delegate to it so every settings surface renders the same
heading frame.
Add POST /api/measurement-reminders/{id}/complete so a client can mark a
measurement reminder done on the server, rather than only dismissing the
notification locally. The route is the explicit user-action equivalent of
the cron auto-resolve: it routes through the one shared satisfaction
primitive, stamps lastSatisfiedAt, re-anchors the server-authoritative
nextDueAt, and fires no notification of its own.
Idempotent via the primitive's forward-only guard — completing an
already-completed or auto-satisfied reminder returns 200 with
completed=false so a repeat tap need not be special-cased. Owner-scoped;
a cross-user or tombstoned id 404s and never satisfies. No schema change:
lastSatisfiedAt already records the completion instant.
Extend the OpenAPI contract with the new path and the completion result
schema, and cover the route with unit tests (marks done, idempotent
no-op, owner-scoped 404, no double-notify).
Replace the per-page and per-chart disclaimer banners with a single
acknowledgment at the start of onboarding: HealthLog is a private tracking
tool, not a clinical assessment or diagnosis; talk to a doctor if worried.
- record the acknowledgment per user (disclaimer_acknowledged_at/_version,
migration 0175) mirroring the research-mode acknowledgment precedent
- gate the welcome "Get started" on the checkbox; expose the timestamp on
/api/auth/me so a returning user is never re-asked
- keep the full legal text reachable on the public privacy page
- add a wrench beside each module's add button linking to the module's own
settings page; rename every add button to a plain "hinzufügen"
- give each module a settings page: view (cards/list) + manual reorder; Labs
also gets a sort-order choice (by last reading) and biomarker CRUD/reorder
- collapse the Illness "abgeschlossen" list by default
- rebuild the Vorsorge card on the medication card's building blocks: a green
"Jetzt messen" action, the interval as a chip beside the name, and an
in-interval progress signal; drop the kebab "anpassen" and stray actions
- persist per-module view/order/sort client-side; add reverse-coverage-clean
strings across all six locales
Introduce a shared SettingsSectionFrame (the settings twin of the admin
renderer's SectionFrame) that emits the canonical visible heading +
subheading, and route all 19 settings sections through it. Every settings
page now shows a real h1 + subtitle in the reference Übersicht style
instead of an invisible sr-only title; the section bodies drop their own
headings and off-style intro paragraphs (the explanatory copy folds into
the frame subtitle).
- Route the admin About page through SectionFrame so it carries the same
visible heading as every other admin section.
- Rename the AI settings section from KI-Auswertung to KI-Anbieter across
six locales: the page configures the provider and key, not an analysis.
- Add a heading-block spacer to the settings and admin sidebars so the
first menu item lines up with the top of the first card.
- Remove the always-on core-domains card from Module: a tracking area that
can't be turned off doesn't need listing.
- Move the proactive-coach control out of Benachrichtigungen into Coach.
- Move admin module availability out of the Coach section into its own
page: it gates every module, not just the coach.
- Give the Layout Insights editor a proper Insights heading.
Adds settings.sections.<slug>.subtitle and the new admin keys across all
six locales; removes the now-unused module core-domain strings.
The overview charted each trend but never offered a way into the metric's
detail page — the steps page in particular exists and is used, yet Insights
never linked to it. Give every Trends-row chart a routed detail target and a
discreet "view details" link below the caption, so a metric the row surfaces
(steps included, when the briefing flags it) drills into /insights/<slug>.
Read next to the page's "Erholung" heading, the "Recharge" / "Strain & load" /
"Daily heart & energy" sub-headings were redundant chrome. Remove them and
render the present signal blocks as one flat, evenly-spaced stack — each block
already carries its own title + explainer, so nothing loses context. The group
structure stays internally to drive block order and the data gate. Promote the
block title to an h2 so the page heading outline stays nested (h1 then h2) with
no skipped level, and drop the now-orphaned group i18n keys.
Every metric sub-page header carried a Coach launch icon. With the Coach
FAB anchored bottom-right on every authenticated page, that header icon
was a second entry point to the same surface. Remove it from the shared
sub-page shell so the FAB is the single Coach launcher app-wide. The
coachLaunch prop stays accepted on the shell (the metric pages still pass
it) but no longer renders a control, so no per-page churn is needed.
The Coach page read as cut off against other pages and the left
conversation scrollbar looked broken.
Width: the page sat inside the shared max-w-screen-xl content frame with
a tall pb-20 FAB gutter that does nothing on /coach (the FAB hides
there). Break out of the frame so the chat claims the full content width
and viewport height, edge-to-edge on small screens and a contained card
from sm up, and centre the thread on a comfortable reading measure.
Scrollbar: the page-surface history rail was wrapped in an overflow-auto
container while the rail itself is a flex column with its own
overflow-auto list — two nested scroll regions. The inner list's height
never resolved, so its scrollbar rendered against an indeterminate track
and the search field scrolled away. Drop the outer scroll: the rail
keeps a fixed search header and one internal list-scroll.
Add a discreet unread dot to the Coach FAB when the Coach has something
the user has not opened yet, and clear it when they open the Coach.
The signal is server-authoritative: a new coach_last_seen_at column on
users records the last time the Coach was opened, and the launcher's
status endpoint reports unread when the newest assistant message is
newer than that stamp — so the dot works even with no push channel and
clears across every device, web and iOS. Opening the Coach (FAB or the
/coach page) stamps the column via a new /api/insights/coach/seen
endpoint; the launcher keeps a local stamp only for an instant paint.
The dot is a calm cyan, not an alarming red, per the card-status rule.