Commit Graph
1714 Commits
Author SHA1 Message Date
Marc-André Bombeck 643c33250b perf(settings): code-split qrcode out of the sharing chunk
The QR is only rendered after a share link is created, so import qrcode
lazily inside the effect instead of statically at module top. The library
no longer ships in the initial settings bundle.
2026-06-20 17:10:48 +02:00
Marc-André Bombeck 28dd3b10af Merge w10/settingsmisc into release/v1.18.10
Align the Vorsorge settings card to the canonical pattern, neutralize the danger
zone card, mark the bug-report feature deprecated, make ipwho.is the default geo
resolver, and keep the mobile More-hub labels on one line.
2026-06-20 16:57:55 +02:00
Marc-André Bombeck 6ee7df3098 fix(nav): keep mobile More-menu labels on one line
Trim the longest label ('Benachrichtigungen') with truncate (nowrap +
ellipsis) instead of break-words so every More-hub tile reads on a
single line on a phone, matching its neighbours. The full label stays
discoverable via the title attribute; the desktop layout is untouched.
2026-06-20 16:57:01 +02:00
Marc-André Bombeck 5b6544ec63 feat(admin): mark the feedback feature as deprecated
Add a discreet 'Deprecated' badge and a short note ('will be removed in
a future version') to the bug-report settings entry. The functionality
stays intact; this only signals the upcoming removal across all locales.
2026-06-20 16:56:55 +02:00
Marc-André Bombeck 17a7d1c747 fix(admin): neutralise the danger-zone card surface
Render the danger-zone section on the shared SettingsCard surface
(bg-card + border-border) instead of the destructive-tinted card edge.
A red card surface reads as alarming rather than premium; the danger
signal now lives only in the destructive delete button and its typed
confirmation dialog, with the AlertTriangle header icon still flagging
intent.
2026-06-20 16:56:49 +02:00
Marc-André Bombeck ed99d207e4 fix(settings): align the Vorsorge settings card with the shared pattern
Adopt the standard SettingsCard + SettingsCardHeader contract for the
three Vorsorge settings groups (view, order, manage): bordered card
surface, neutral muted section icon, text-lg title, and a short muted
description, replacing the bare sections with text-sm headings so the
surface reads like every other Settings section. Add a reorder
description string across all locales.
2026-06-20 16:56:42 +02:00
Marc-André Bombeck b042fcfc27 Merge w10/ingest into release/v1.18.10
Capture Withings sleep vitals (HR/respiratory/SpO2/sleep-HRV/score) via the sleep
summary; collapse overlapping BP sources on the cold path for web/iOS parity; add
a STRESS_SCORE source-priority ladder; add a soft-delete-aware measurements index
and cap the all-time cold scan.
2026-06-20 16:53:35 +02:00
Marc-André Bombeck 56d8cf19c5 fix(sources): ladder STRESS_SCORE so the source collapse stays deterministic
STRESS_SCORE has no source-priority ladder, so metricKeyForType returns null
and the SQL source collapse falls to an alphabetical rank-90 tiebreak. It is
safe today because the only producer is the COMPUTED HRV-derived proxy, but
the instant a device-native producer (Oura daily_stress) is enabled both rows
survive and blend nondeterministically.

Add a stress metric key with an OURA-above-COMPUTED default ladder, wire it
through metricKeyForType and RANKED_TYPES, and surface it in the Sources
settings (all six locales). A guard test asserts every RANKED_TYPES member
resolves to a non-null metric key so a future ranked type can't ship unladdered.
Regenerate the OpenAPI for the additive source-priority key.
2026-06-20 16:52:10 +02:00
Marc-André Bombeck d08e462136 Merge w10/vorsorgedash into release/v1.18.10
Move the Vorsorge cadence into the metric row, link card headings to filtered
results, tighten the daily-briefing card rhythm, and invalidate the dashboard
snapshot on briefing regenerate and layout save so the Startseite refreshes.
2026-06-20 16:51:59 +02:00
Marc-André Bombeck d819389483 Merge w10/labsui into release/v1.18.10
Turn the labs add action into a scan/manual choice; add opt-in client-side local
OCR (tesseract.js) so non-vision providers can scan reports; move biomarker
delete to the detail view; make overview tags compact; surface reference-range
confidence for new biomarkers.
2026-06-20 16:51:22 +02:00
Marc-André Bombeck 649bfb3ea8 fix(dashboard): refresh briefing and layout on the Startseite without reload
The dashboard hero reads the briefing headline and the layout from the
server snapshot. Regenerating a briefing or saving the dashboard layout
only touched their own caches, so the snapshot kept stale data until its
own window lapsed and the Startseite looked unchanged until a manual
reload. Invalidate the dashboard snapshot on briefing regenerate and on
layout save/reset.

Harmonise the daily-briefing card body rhythm with its sibling insights
cards (space-y-3, matching correlation-card) so it no longer reads taller.
2026-06-20 16:51:18 +02:00
Marc-André Bombeck 0623316c12 feat(vorsorge): move cadence into metric row and link heading to filtered results
Drop the grey cadence pill from the card heading. The cadence now reads as
part of the metric line, styled like the metric label itself (no shaded
background), via a new MedicationCardHeader metricMeta slot. Nothing else
sits beside the heading.

Link a measurement-linked reminder's heading to the measurements history
pre-filtered to its type, reusing the existing kebab target. Free-text
reminders keep a plain heading.
2026-06-20 16:51:17 +02:00
Marc-André Bombeck d65ed8b0b6 fix(labs): move delete to the detail view and shrink overview tags
Remove the inline trash icon from the lab list and tile rows; delete now
lives only on the value detail view, beside Edit, so a stray tap on the
overview cannot remove a biomarker. Make the reference-range badge beside
each analyte heading quieter on the overview (smaller text, tighter
padding, no icon) so it reads as a subtle qualifier instead of competing
with the heading.
2026-06-20 16:50:23 +02:00
Marc-André Bombeck 028b0d8685 feat(labs): unify the add action into a scan-or-manual choice
Replace the separate, capability-gated scan button with a single Add
menu: "Scan a document" (OCR) or "Add a value manually". The scan entry
appears only when the capability probe reports a usable mode, and routes
to native vision or local in-browser OCR (tesseract.js, lazy-loaded,
deu+eng, images only) per the resolved mode. Add the local-OCR opt-in
toggle to the Labs settings page with honest "less accurate — review
every value" copy.

Surface range confidence prominently on the OCR review screen for new
numeric biomarkers, where the extracted range becomes the catalog range:
flag low range-confidence or a missing range so the user verifies it.
2026-06-20 16:50:16 +02:00
Marc-André Bombeck 398f5c3b6b fix(coach): make the composer mic reliable and never a dead tap
Always render the dictation mic so the affordance stays discoverable. On
a browser without the Web Speech API (or during SSR) render it disabled
with an explanatory tooltip instead of hiding it or leaving a control
that silently does nothing on tap. When a dictation start fails — denied
microphone permission, an insecure-context self-host, or no network for
the recogniser — surface a quiet toast rather than resetting in silence.
Add voiceUnsupported + dictateError strings across all locales.
2026-06-20 16:42:10 +02:00
Marc-André Bombeck 01b73f6379 feat(coach): make the full Coach page a single full-bleed surface
Drop the inner bordered card on the dedicated /coach page so the chat
reads as one continuous conversation rather than a window inside a
window, and remove the top "back to the drawer" minimize control (it is
pointless on the full page; the bottom-right FAB drawer remains the
separate compact surface). Also remove the two starter-question
suggestion chips below the new-chat composer, leaving a greeting +
composer hero that matches the reference new-chat layout. History stays
reachable through the existing conversation rail.
2026-06-20 16:41:58 +02:00
Marc-André Bombeck 0798373877 Merge v189/recon-a into release/v1.18.9
Hard-evict the dashboard snapshot on batch and Withings background sync so the
Startseite reflects synced readings on focus; withdraw the Oura daily_stress
mapping pending source-priority wiring; make OCR in-batch dedup independent of DB
visibility; widen the vision allowlist to Claude 3; null-guard the reasoning
frame; correct OpenAPI and cache-TTL drift.
2026-06-20 13:02:04 +02:00
Marc-André Bombeck cbc2187705 fix(coach): guard empty reasoning SSE frame against literal undefined
A reasoning frame with no text appended the string "undefined" to the
thinking disclosure. Coalesce evt.text to an empty string on both the
streaming and the trailing-flush paths.
2026-06-20 12:59:29 +02:00
Marc-André Bombeck 90a9407195 fix(vorsorge): key due status on the local calendar day 2026-06-20 12:53:41 +02:00
Marc-André Bombeck a1f5ed625c fix(coach): cap starter-question chips to a single-line pill 2026-06-20 12:53:41 +02:00
Marc-André Bombeck 576264d1d9 fix(labs): raise lab-form result-type toggles to the touch floor 2026-06-20 12:53:41 +02:00
Marc-André Bombeck bf509f6b75 fix(labs): give the OCR per-row confirm a 44px touch target 2026-06-20 12:53:41 +02:00
Marc-André Bombeck ebfebe1fe3 feat(labs): add Lab-OCR scan and review screen
Add the Scan a report entry to the Labs page, gated on the capability probe.
The review dialog uploads a photo or PDF, then shows each proposed reading for
per-row confirm, edit, or discard with duplicate, new-biomarker, and
low-confidence hints in the labs design language. Only confirmed rows commit.
Add the OCR query key and the six-locale strings.
2026-06-20 12:35:32 +02:00
Marc-André Bombeck 3505ffde24 Merge v189/coachui into release/v1.18.9
Rebuild the /coach surface as a centred-hero chat with a word-by-word fade, a
single thinking disclosure, and a per-message token footer; emit token usage on
the done frame and persist it via migration 0182; plumb an additive reasoning
frame with an elapsed-time fallback for non-reasoning providers.
2026-06-20 12:14:42 +02:00
Marc-André Bombeck 144bfbe553 feat(coach): centred new-chat hero on the page surface
Replace the cramped empty thread on the /coach page with a centred hero:
a greeting, the live composer beneath it, and HealthLog's own starter
questions as chips, over a faint purple atmospheric backdrop with one
staggered entrance reveal. The hero re-parents the existing composer
rather than forking it, so dictation, auto-grow, and the send/stop
control behave identically; the first send docks the composer to the
bottom and the conversation takes over. Gated to the page surface and
the fresh new-chat state, so the drawer, guided-question, and adopt
flows are untouched.
2026-06-20 12:12:54 +02:00
Marc-André Bombeck 88ee5bb4f8 feat(coach): word-fade stream, thinking disclosure, token footer
Stream the assistant turn word-by-word with a soft motion-safe fade
(StreamedProse), reduced-motion collapses to instant. Replace the
dots-plus-word placeholder with a single thinking indicator: a live
elapsed-time line that freezes into an auto-collapsed past-tense
disclosure, which surfaces a reasoning summary when one is present and a
factual fallback otherwise — never both a spinner and a Thinking word at
once. Add a quiet per-message token footer (count plus model, no cost)
fed by the live done.usage frame and persisted tokensUsed on reload.
Add the localised strings across all six bundles.
2026-06-20 12:05:31 +02:00
Marc-André Bombeck b0e6079858 Merge v189/fresh into release/v1.18.9
Invalidate the dashboard snapshot on measurement writes and refetch on focus so
fresh and background-synced readings surface without a hard reload; expose the
no-provider state on the briefing read with a connect-a-provider hint; align the
briefing footer caption.
2026-06-20 12:01:20 +02:00
Marc-André Bombeck 5cebc168df fix(insights): flag a stale briefing that has no provider to refresh it
An account with no AI provider configured still reads the last cached
briefing — the read path serves it without ever touching a provider — so
the days-old report rendered as current. The pregenerate cron runs fine;
the cache simply can never refill without a provider. Report provider
availability from the read GET and pair the briefing's relative age with a
discreet 'this can't refresh — connect a provider' hint linking to
Settings -> AI, on both the overview hero meta row and the briefing card
footer. The hint only shows when a briefing is actually rendered; the
empty-state already owns its own no-provider branch.

Also align the briefing card footer caption to the 11px scale the
adjacent period-in-review card uses, so the two overview footers read at
one size.
2026-06-20 12:00:21 +02:00
Marc-André Bombeck 09a04f6eb7 Merge v189/labsui into release/v1.18.9
Default the labs surface to list view and reuse the medication card header for
tiles; surface confirm-gated biomarker delete on detail and list rows; add
alphabetical sort; align the labs settings page to the canonical SettingsCard.
2026-06-20 12:00:11 +02:00
Marc-André Bombeck b27d8509cd Merge v189/ill into release/v1.18.9
Defer illness recovery-gap correlation to an opt-in detail expansion; gate the
typical-gap insight behind signal-density thresholds; align the illness settings
page to the canonical SettingsCard pattern.
2026-06-20 12:00:00 +02:00
Marc-André Bombeck b60b2c2bbf refactor(labs): align Labs settings with the standard Settings pattern
Restructure the Labs settings sub-page to the canonical Settings dialect:
each block is a SettingsCard with a SettingsCardHeader (neutral icon, title,
short description), matching the AI / notifications / mood sections instead of
bare sections with text-sm headings. Surface the new alphabetical sort options
in the sort select.
2026-06-20 11:59:27 +02:00
Marc-André Bombeck 8ae498ffb2 feat(labs): surface biomarker delete on the detail page
Add a delete affordance with a confirm step to the biomarker detail header,
reusing the existing DELETE /api/biomarkers/:id endpoint. On success the
catalog and result-list queries invalidate and the view returns to the labs
surface. Removes the Settings detour for clearing a stray biomarker.
2026-06-20 11:59:19 +02:00
Marc-André Bombeck 4da155ddcb feat(labs): default to list view and reuse medication tile primitives
Set the compact list as the Labs surface default; tiles in the card view now
route through MedicationCardHeader so a lab tile reads identically to a
medication or Vorsorge tile side-by-side. Add alphabetical (A-Z / Z-A) sort
options to the per-module sort choice.
2026-06-20 11:59:13 +02:00
Marc-André Bombeck f46e9d3bb8 feat(coach): surface per-turn token usage on the chat stream
Carry an additive usage envelope on the SSE done frame
({ totalTokens, model }) and persist tokensUsed + model onto
coach_messages (migration 0182, nullable, no backfill) so the
per-message token footer survives a conversation reload. Add an
optional reasoning frame the client can render inside the thinking
disclosure when a reasoning-capable provider emits a summary; providers
without reasoning emit none. Extend CoachMessageDTO and regenerate the
OpenAPI contract. Server-authoritative: clients display the count, never
recompute it.
2026-06-20 11:58:58 +02:00
Marc-André Bombeck 696daf3b8f fix(illness): align the journal settings page with the Settings pattern
The illness settings sub-page rendered bare sections with text-sm headings
and no card surface, breaking from the canonical Settings layout the
Stimmung and Medikamente sections follow. Wrap each block in a SettingsCard
with a SettingsCardHeader (neutral icon, text-lg title, short muted
description), so the view and order controls read as first-class Settings
cards consistent with the rest of the surface.
2026-06-20 11:58:52 +02:00
Marc-André Bombeck 1de63f2b5c perf(illness): compute the recovery gap lazily and gate it on signal density
The illness list load triggered the cross-episode correlation fan-out on
every mount, so the page waited on up to 24 per-episode reads before it
painted. Move that work behind an explicit opt-in: the insights endpoint
takes an includeRecoveryGap query param (default off), the list reads the
count breakdown on a single fast query, and the recovery gap is computed
only when the viewer opens the Analyse expansion.

Tighten the typical-gap gate against coincidental data. A gap now counts
only from an episode that cleared a per-episode measurement floor
(MIN_GAP_MEASUREMENT_DAYS), at least MIN_EPISODES_FOR_TYPICAL_GAP such
episodes must qualify, and the median magnitude must reach
MIN_TYPICAL_GAP_MAGNITUDE_DAYS — a 0/+-1-day gap is the coincidental
baseline and is withheld as noise.
2026-06-20 11:58:39 +02:00
Marc-André Bombeck 9e1a256525 Merge v189/vorsorge into release/v1.18.9
Drop the Vorsorge trend strip; show last-completed as relative today/yesterday
via a shared helper; render next-due status as discreet coloured inline text.
2026-06-20 11:55:16 +02:00
Marc-André Bombeck 590b08286a feat(vorsorge): drop trend strip, relative last-done, discreet due status
Remove the 7-day bar strip under the Vorsorge card last-done line and its
backing component, query key, and i18n strings. Render the last-done value
as a relative date (today / yesterday / absolute) through a shared
relativeCalendarDate helper that reuses the medication today/yesterday keys.
Show the next-due status as discreet coloured text - green today, orange
overdue - leaving the card surface neutral.
2026-06-20 11:54:11 +02:00
Marc-André Bombeck 979d1ced8d fix(insights): soften Coach FAB glyph to the on-accent token
Drop the pure-white Coach floating-action-button icon for the button's own
text-background on-accent token, so the glyph reads as a calmer mark on the
purple-to-pink gradient instead of a harsh white. Update the colocated
assertion to pin the glyph token.
2026-06-20 11:49:33 +02:00
Marc-André Bombeck a5b3715a46 feat(labs): record qualitative lab results
Add first-class qualitative lab findings (positive / negative / borderline
serology) alongside the existing numeric path. A LabResult is now EITHER a
numeric reading (value + unit, optional reference bounds) OR a qualitative one
(valueText, e.g. "negativ"), never both, never neither.

- schema: make `value` nullable, add `value_text`; migration 0181 is additive
  (DROP NOT NULL + ADD COLUMN), every existing numeric row is untouched.
- validation: numeric-XOR-qualitative refine on create + update; unit/bounds
  apply to the numeric path only; valueText capped at 120 chars. The single
  resource PUT rejects a cross-type edit.
- serialise: carry valueText through both DTOs; a qualitative row reports the
  neutral `unknown` range verdict (no number to classify).
- FHIR: a qualitative result emits valueCodeableConcept — SNOMED qualifier
  value for confidently-known terms (Negative 260385009, Positive 10828004,
  Not detected 260415000, Detected 260373001), text-only for borderline /
  grenzwertig and any unrecognised term. The raw text always rides .text.
- UI: a numeric/qualitative toggle in the lab form; list, history, detail,
  chart, sparkline, and the doctor-report PDF all render qualitative readings
  as their text and never plot or divide on a null value.
- i18n: new labs.form keys across all six locales.
- openapi: response carries a nullable value + valueText; regenerated.
2026-06-20 11:17:43 +02:00
Marc-André Bombeck 1f4ba5939d style: apply prettier across the tree
One-shot prettier pass so the new format:check gate starts from a clean
baseline. Formatting only — no behaviour change. Generator-owned and
vendored artifacts stay untouched via .prettierignore.
2026-06-19 22:20:31 +02:00
Marc-André Bombeck 68033b2d66 refactor(onboarding): point consumers at the kebab-case component files
Update the onboarding step page, baseline form, and the anamnesis-card
spec to import from the renamed kebab-case files. Export identifiers stay
unchanged.
2026-06-19 22:17:34 +02:00
Marc-André Bombeck 922f770b41 refactor(medications): rename PascalCase component files to kebab-case
Bring the medication card, scheduling, wizard, and detail component
files in line with the kebab-case file-naming convention. Component
export identifiers are unchanged; only filenames and the import paths
that point at them move.
2026-06-19 22:17:13 +02:00
Marc-André Bombeck d75a538614 Show the QR, passphrase, and protected badge in the owner UI
On a successful create the Sharing section now renders the QR code (from
the #k= deep link), the passphrase as copyable text, and the link, with a
clear shown-once notice. The active-link list carries a small passphrase
indicator. Add the new strings (QR label, passphrase label, copy buttons,
hints, protected badge, and the public unlock prompt) across all six
locale bundles.
2026-06-19 22:03:53 +02:00
Marc-André Bombeck 5783acacbb Gate the public share view behind the passphrase
A protected share link no longer renders the record until the passphrase
is verified. The public page checks the gate state first (without bumping
access counters), and for a link with a passphrase hash it serves a gate
island unless a valid, token-scoped unlock cookie is present. A legacy
link with a null passphrase hash renders exactly as before.

The unlock route verifies the submitted passphrase with a constant-time
compare against the stored hash, is rate-limited by share-unlock token
and IP before any compare, and on success sets a short-lived (30 min)
httpOnly, SameSite=Strict, Secure cookie scoped to that one token's view
path. Every failure class answers one blunt 401; no-store and the flat
404 for unknown links are preserved. The gate island auto-submits the
#k= fragment (scrubbing it from history first) or prompts for input.
2026-06-19 22:03:46 +02:00
Marc-André Bombeck 0518b26d08 feat(settings): restore the clinician share-link section
Bring back the Settings → Sharing ("Freigabe") section: the owner mints
a time-boxed, revocable, read-only share link to their health record at
the public /c/[token] view. The backing model, the /api/share-links
lifecycle routes, and the public clinician view were left in place; this
restores the owner Settings surface and re-wires it.

Place the slug directly after gesundheitsakte and before export — sharing
the health record belongs next to the health-record export. The entry is
always available (no module gate), like account and export.

Render the card through the shared SettingsCard primitive so it matches
every sibling section; drop the section's own page title (the shell
supplies the heading and subtitle). Route the list, create, and revoke
calls through the api-fetch helper instead of raw fetch, and align the
touch targets with the mobile sizing contract.
2026-06-19 21:38:40 +02:00
Marc-André Bombeck 4daa5a428f Merge wave-mobile-settings: SettingsCard primitive + phone tap targets
Replace the hand-rolled Settings card divs across every section with a
shared SettingsCard wrapping the Card primitive, eliminating the sm-vs-md
padding split and the primitive bypass, and raise the Settings touch
controls to a 44px floor that condenses at sm+.
2026-06-19 21:30:35 +02:00
Marc-André Bombeck e0c1601a36 fix(settings): raise account form CTAs to the 44px touch floor
The profile save, add-passkey, change-password trigger, and password
dialog submit ran at 40 px on phones; apply min-h-11 sm:min-h-9.
2026-06-19 21:28:34 +02:00
Marc-André Bombeck d9393a2148 fix(settings): raise primary controls to the 44px touch floor
Apply the canonical min-h-11 sm:min-h-9 (and min-w-11 for icon-only
controls) to the primary Settings controls that sat at 32-40 px on
phones: the AI fallback-chain reorder/remove/save/add icons, provider
form save/test/connect actions, coach-memory forget, avatar replace and
remove, section save/cancel/reset CTAs, the export and import action
rows, the About external links, and the thresholds editor row. Collapse
the injection-sites checkbox grid to one column on phones and stack the
password-change grid below lg.
2026-06-19 21:27:05 +02:00
Marc-André Bombeck a371678460 test(coach): guard composer controls against the sub-44px floor
Assert the send and stop controls render the `size-11 sm:size-9` tap-
target pattern so a future restyle cannot quietly drop them back to the
36px size the audit flagged. Renders the real component and matches the
emitted class string, the same model as the bottom-nav size test.
2026-06-19 21:18:27 +02:00