Commit Graph
10 Commits
Author SHA1 Message Date
Marc-André Bombeck b217535b62 chore(v1.22): clear the QA punch-list across Coach, insights, settings
Code-review and design follow-ups before the v1.22 cut. No behaviour change
beyond the items below.

Coach / insights:
- Coach SSE route: a non-provider error raised inside the open producer (e.g. a
  DB failure persisting the reply) now records on the wide event and forwards to
  GlitchTip instead of masquerading as a provider outage; it still surfaces an
  error frame since the stream is open.
- Lab ↔ outcome correlations: hedge faint-tier pairs ("a faint hint, if
  anything … too small to lean on") so the narrated confidence never outruns the
  effect, matching the daily engine.
- Reminder sweep: mint the plan-review reminder and clear the plan's reviewDate
  in one transaction so a half-applied write can't duplicate the reminder.
- Fold the two raw provider-fallback runners onto one shared chain walker.

Design / accessibility:
- Lift sub-44px tap targets to the 44px mobile floor (suggested-action card,
  Coach bubble affordances), brighten the resting timestamp clock for non-text
  contrast, and route the lab chart height through the shared constant.
- Replace ad-hoc arbitrary type sizes with the scale token and static size-11
  icon buttons with the named icon-lg variant.
- Settings: add the missing About card-header descriptions, normalise the
  low-stock numeric widths, and relax the notification-status rows from nested
  cards to a divided list.

Housekeeping:
- Drop three unused exported types (and the now-orphaned source const), re-stamp
  v1.21.5 comments to v1.22, and register the live-judge eval harness as a knip
  entry so the script-only module resolves.
2026-06-27 16:29:58 +02:00
Marc-André Bombeck b91649d6de feat(ai): UI-configurable response timeout, local streaming, dedicated OCR provider
Add three per-user AI settings, all configured in the app (no new env vars),
behind one migration (0198):

- Response timeout (seconds). A per-user upstream timeout threaded onto the
  provider call. A local/self-hosted backend can take more than the legacy
  60 s default to load a model on the first request, which timed the Coach
  out before the first token. Defaults to a generous ~180 s when unset;
  validated to 10–600 s.

- True token streaming for the local OpenAI-compatible client. The client now
  POSTs with stream:true and parses the SSE delta chunks, emitting tokens as
  they arrive, with a per-idle-gap timeout instead of a whole-call ceiling and
  a graceful fallback to the buffered path when a server cannot stream. The
  Coach route runs the provider call inside the response stream behind a
  keepalive heartbeat so a reverse proxy no longer idle-drops the connection
  during model load or generation. Grounding, refusal, and sentinel-stripping
  still run on the complete reply before any token reaches the client.

- Dedicated document-scan (Lab-OCR) provider. Off by default — the OCR path
  uses the main provider chain. When enabled, lab-report processing resolves a
  separate provider/model/key, with a hint when the chosen model is not
  vision-capable. The new key column is AES-256-GCM at rest and registered in
  the encrypted-columns registry and the rotation script.

i18n: EN + DE translations for the new Settings → AI strings (other locales
carry interim copies to keep locale parity green).
2026-06-27 15:21:40 +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 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 e31f9690e2 refactor(settings): compose single-card sections from SettingsCard
Swap the hand-rolled bg-card border-border rounded-xl border p-4 sm:p-6
container for SettingsCard across the single-card sections (integration
cards, notification channel cards, coach prefs, cycle, modules, mood
reminder, thresholds, dashboard-layout, about-me, low-stock, and the
health-record export panel). Drops the sm: padding split and the
primitive bypass; landmark sections keep their element via as="section".
2026-06-19 21:14:57 +02:00
Marc-André Bombeck dadb6e2d34 fix(settings): finish the D0-D8 settings/admin frame cleanup
- Drop the leading blurb paragraph from the Channels + Integrations
  connection panels so each body starts at its first card (D0 alignment).
- Correct three stale CORE/always-on comments: medications has been a
  fail-open toggleable module since D3; CORE is now weight/BP/pulse only.
- Remove the dead variant/isSubtab branch from SourcesSection — no callers
  after the D4 split; render the standalone header unconditionally.
- Render the admin AI-quality card frame + header once and branch only the
  body across loading/error/empty/data, matching the coach-feedback card.
- Rename the Coach activation card's section id from the legacy
  settings-ai-* namespace to settings-coach-activate-* after the S5 move.
- Gate the medication low-stock reminder card on the medications module so
  it hides when the account turns medications off, mirroring mood/coach.
2026-06-16 23:12:36 +02:00
Marc-André Bombeck 9b191a208d feat(settings): round-2 information-architecture rework
Apply the v1.18.0 walkthrough notes across the settings surface:

- Drop the explanatory top-text block from every settings section so
  content starts at one consistent vertical height (the sr-only heading
  stays for the accessibility tree).
- Move "Restart onboarding tour" out of Account into Advanced.
- Module hub: drop the explanatory line above the first card.
- Split the Integrations page into three left-side entries — the
  Connections content is now the Integrationen page itself, with
  Channels and Sources (source weighting) as their own entries. Drop the
  /settings/sources redirect (it is a real route again); Channels is a
  new entry.
- Notifications: drop the top text, the channels / inbox cross-links,
  and the embedded Vorsorge editor (it has its own page). Keep the mood,
  low-stock, and proactive-coach reminder cards.
- Rename the Layout hub entry to "Layout".
- Flip the Coach toggle to "Activate Coach" (default on, opt-out) over
  the same single disableCoach column — one source of truth.
- Move the AI "About me" context into Account → Profil, before
  cycle tracking.
- Gesundheitsakte: replace the gradient hero with a normal card.

i18n across all six locales for the renamed / added labels; settings
tests updated for the new structure.
2026-06-16 21:47:00 +02:00
Marc-André Bombeck f9876f9492 feat(consent): mint AI consent receipt from the web client
The server-side gate requires an active ConsentReceipt before any health
snapshot egresses via the operator's server-managed key, but the web
client never minted one — so web users on a shared-key deployment hit the
fail-closed no-key fallback on every AI surface.

Add ensureWebAiConsentReceipt() and POST /api/consent/ai/web: an
idempotent ai_full grant mirroring the iOS master grant (a user with an
active receipt is a no-op). The AI-settings card calls it on mount — the
web equivalent of the iOS shell-mount heal — so existing accounts gain a
receipt without a re-consent step.

Surface an honest signal: resolveReadOnlyStatusMiss now returns a
consent-missing outcome when a provider is configured but the gate would
block egress, distinct from no-provider, and skips the wasted enqueue.

Document the receipt precondition + the consent surface in the OpenAPI
contract (it shipped without a bump): the /api/consent/ai family, a
Consent tag, and a 403 consent.ai.required response on the AI-egress
route.
2026-06-14 10:25:18 +02:00
Marc-André Bombeck 40f7196c20 feat(medications,coach,admin,perf): era corrections, a truthful weekly status and one card language everywhere
The dashboard snapshot fires the moment login succeeds instead of
waiting for the page chunk (start ~670 to ~200 ms throttled). Guided
follow-ups are personalised from the user's snapshot and an answer gets
the coach's streamed reaction before the next question. Archived
schedule eras can be corrected through a superseding revision that
keeps the original as an audit trail, era writes serialise per
medication, and duplicate dose times collapse in validation. A rolling
medication whose next dose is tomorrow can no longer read as heavily
overdue — the card status derives strictly from the served next-due —
and the adherence bars keep a fixed geometry in every state. The
server-wide assistant key gains its admin card, the one duplicated
coach preference collapses onto a single owner, twenty-seven admin and
settings card headers move onto the shared pattern with sixty
containers normalised, and the remaining hydration warnings are gone.
2026-06-11 13:57:15 +02:00
Marc-André Bombeck 3b480b9885 perf(i18n,shell): ship one locale and stop blocking the first paint
The client bundle carries only the active locale — resolved on the
server and handed to the provider with no hydration flash, halving the
dashboard route to under a megabyte parsed (288 KiB gzip). The auth
shell renders the app chrome immediately instead of a spinner while the
session resolves in parallel, the widgets request folds into the
snapshot it duplicated, and insight pills prefetch their sub-pages —
a first visit to a sub-page lands in about ninety milliseconds instead
of nearly a second.
2026-06-11 02:19:27 +02:00