Commit Graph
559 Commits
Author SHA1 Message Date
Marc-André Bombeck ccbf4e07ae Merge branch 'wave/v125-delight' into release/v1.25.0 2026-06-28 14:41:41 +02:00
Marc-André Bombeck 4bf45efcab revert(insights): drop hydration logging
Remove the in-app water-intake logging feature: the WATER_INTAKE
measurement type, the per-user daily-goal field, the goal-ring card and
its endpoint, and the related query keys, OpenAPI route, and locale
strings. The feature is dropped from this release; the baseline-drift,
breathing-screening, and labs-changes insight cards are unaffected, and
the pre-existing TOTAL_BODY_WATER body-composition type stays intact.
2026-06-28 11:39:26 +02:00
Marc-André Bombeck ec3fcea84f feat: baseline-drift, breathing-screening and lab-change insight cards
Add three read-only awareness cards to the Insights overview, each drawn
off an existing engine and each honest about empty state — the section
un-mounts rather than surfacing a husk.

- Health status: surfaces what is drifting from the user's own normal by
  folding the personal-band deviations (coincident-deviation engine) with
  the dated, sustained level shifts (changepoint detector) at
  GET /api/insights/health-status. Awareness only, never a diagnosis.
- Breathing screening: summarises the last 30 nights of the per-night
  sleep-breathing-disturbance index plus device-flagged events at
  GET /api/insights/breathing-screening. Mirrors the device's own
  classification and states plainly that it is a screening signal, not a
  diagnosis — discuss with a clinician.
- Lab changes: pairs the two most-recent numeric lab panels and reports
  each shared analyte's signed delta and reference-band standing at
  GET /api/insights/labs-changes. Neutral framing only.

Each reshaping helper is pure and unit-tested for its present/absent
states. Register the three sections in the insights-layout universe so
they merge in as default-visible, wire them into the overview and the
edit-mode title map, add the query-factory keys, document the routes in
the OpenAPI contract, and translate every new string across all six
locales.
2026-06-28 03:20:49 +02:00
Marc-André Bombeck b424c37ab0 i18n: add keys for the UX and accessibility pass
Chart and heatmap text-alternative summaries, the version-update
toast, the touch-help trigger label, list load-error messages, and
the target-editor validation hints — across all six locales.
2026-06-28 02:52:44 +02:00
Marc-André Bombeck fb986bc09d merge: bring the MCP server onto the v1.23 base
Combine the remote MCP server (stdio + remote /mcp + OAuth 2.1 + read
tools + deep-value reads + a confirmed, audience-bound write surface)
with the v1.23 account-security line (MFA, known/trusted devices,
free-text note encryption).

- schema: keep every v1.23 User relation (MFA, known/trusted devices)
  alongside the McpOAuthConnection relation; the MeasurementSource enum
  carries the MCP value.
- api-handler: the MCP audience guard (a token scoped to
  health:read/health:write is bound to /mcp and refused a REST write)
  and the step-up MFA helpers coexist.
- maintenance queues: register the MCP token-cleanup sweep next to the
  note-encryption backfill and key-rotation queues; shift it to 04:00
  so the two daily discovery ticks do not collide.
- migrations: renumber the MCP migrations to 0206/0207 so they follow
  the v1.23 chain (0199-0205).
- MCP mood writes route the note through the v1.23 at-rest cipher.
2026-06-28 00:52:04 +02:00
Marc-André Bombeck 36c899b1c8 docs(security): note that required-MFA prompts but does not wall the API
The "Require a second factor" policy redirects web users to enrollment but does
not block direct API or native (Bearer) access for an account that has not yet
enrolled — a deliberate lockout-safety choice. Make that explicit: spell it out
in the admin toggle description, add a code comment at the toggle, and add an
operator runbook (docs/ops/mfa-enforcement.md) describing the boundary.
2026-06-27 22:23:54 +02:00
Marc-André Bombeck 361505bd31 fix(privacy): de-duplicate trusted devices and handle the summary loading state
Trusted devices was mounted on both the Security section and the Privacy
dashboard. Keep it only on the Privacy dashboard, which already gathers sessions
and security activity, and drop it from the Security section. While here: the
Retention card painted an empty list while the privacy summary was loading and
on error — give it a skeleton placeholder and a one-line fallback, and align the
"What's stored" indentation with the other privacy cards.
2026-06-27 22:23:31 +02:00
Marc-André Bombeck 460e078925 fix(security): confirm before regenerating recovery codes
Regenerating recovery codes invalidates the current set immediately, so the
action now sits behind a confirmation dialog matching the disable-MFA pattern,
with a consequence line spelling out that the existing codes stop working. While
in the card: give the disable-step TOTP input the same digit-strip + 6-char cap
and exact-length submit guard as setup, swap the static disable-code id for a
useId value, and lift the copy-secret button to a 44px touch target on phones.
2026-06-27 22:23:09 +02:00
Marc-André Bombeck a81303e55c feat(privacy): add a Data & Privacy dashboard
Bring the scattered data-sovereignty controls into one /settings/privacy pane.
It assembles already-shipped pieces rather than rebuilding them: the encryption-
at-rest summary and configured retention windows (read from the server so the
copy stays truthful), links to the export surfaces (including the passphrase-
encrypted option) and the account-deletion / data-reset surface, the active-
session list, trusted devices, and the security-activity log. The backup↔
deletion lag is disclosed honestly — deletion is immediate, but encrypted
off-host backups still hold the data until they roll off retention.
2026-06-27 21:50:30 +02:00
Marc-André Bombeck c8bef61344 feat(security): let an operator require a second factor
Wire the previously-unused mfaEnforced flag into a real policy. An admin can
require a second factor instance-wide (an AppSettings toggle in the General
section) or per user (a toggle in user management); the effective requirement is
the OR of the two. When required and the account has no active factor — no
confirmed TOTP, no registered security key, and no primary passkey — the user is
walled behind a forced-enrollment interstitial after sign-in, with only the
security settings and sign-out reachable until a factor is enrolled.

The gate is surfaced like the onboarding gate: a hint cookie the proxy reads
without a DB round-trip, recomputed from the database on every sign-in and on
/api/auth/me so a tampered cookie self-corrects. Nobody is locked out — the
enrollment surface stays reachable, and toggling the policy off (or the
disable-mfa CLI) is the operator's recovery path.
2026-06-27 21:49:53 +02:00
Marc-André Bombeck 135bb04f0d feat(auth): allow trusting a device to skip the second factor
Add an opt-in "remember this device" at the second-factor step. On opt-in
a random token is minted, set in an httpOnly+Secure+SameSite=Strict cookie,
and only its HMAC hash is stored in a new TrustedDevice table. A later sign-in
from the same browser still requires the password (factor 1); a matching,
unexpired trusted device lets the login skip factor 2 only.

The token is never accepted for step-up — a trusted-device login does not stamp
mfaVerifiedAt — so a stolen device cookie cannot reach a destructive action.
Trusted devices are listed and revocable from the security surface, are revoked
wholesale when the second factor is removed or the user signs out everywhere,
and cascade away on account deletion. A recovery-code login never trusts the
browser. Migration 0204 adds the table (and the instance-wide mfa_required
policy column that the enforcement work reads).
2026-06-27 21:49:39 +02:00
Marc-André Bombeck b271a39fe4 merge: remove the bug-report integration for v1.23
# Conflicts:
#	messages/de.json
#	messages/en.json
#	messages/es.json
#	messages/fr.json
#	messages/it.json
#	messages/pl.json
#	src/app/admin/[section]/renderer.tsx
2026-06-27 20:47:24 +02:00
Marc-André Bombeck 268c5cef05 merge: integrate the v1.23 data export and encryption-status wave 2026-06-27 20:46:08 +02:00
Marc-André Bombeck c458d955e4 merge: integrate the v1.23 security settings and MFA wave
# Conflicts:
#	messages/de.json
#	messages/en.json
#	messages/es.json
#	messages/fr.json
#	messages/it.json
#	messages/pl.json
#	src/lib/openapi/routes/auth.ts
#	src/lib/query-keys/auth.ts
2026-06-27 20:46:03 +02:00
Marc-André Bombeck 402ea90d45 feat(security): add a TOTP and security-key setup hub
Introduce a dedicated /settings/security section that gathers every
account-protection control in one place: TOTP enrolment (QR plus manual
secret, confirm, recovery codes shown once with copy and download),
WebAuthn security keys as a second factor (register, list with last-used,
rename, step-up-gated removal), recovery-code count and regeneration, and
primary passkey management.

The second-factor WebAuthn path keeps its own credential store, separate
from primary passkeys, and uses non-resident keys so an account can hold
many. Relying-party resolution is shared with the passkey path. A status
endpoint backs the hub; the step-up gate now recognises a registered
security key as an active second factor so a WebAuthn-only account can
still reach the gated actions.
2026-06-27 20:40:36 +02:00
Marc-André Bombeck de33f1ad4c chore: remove the bug-report integration
Drop the in-app bug-report surface end to end: the user submission page,
the feedback intake and admin inbox, the optional GitHub-issue
escalation, the operator config, and the dedicated achievement badges.

- delete the /bugreport page and the bugreport / feedback / admin-feedback API routes
- drop the Feedback model, its enums, and the AppSettings GitHub-issue and enable columns (migration 0205)
- remove the admin Feedback section, the Bug Report integration card, and the system-status row
- drop the bug-report nav entry and the app-settings provider that only carried its flag
- remove the bug-hunter achievement badges and their metric
- prune the related i18n keys across every locale and the encrypted-column and key-rotation registries
2026-06-27 20:37:35 +02:00
Marc-André Bombeck b882698a44 feat(auth): warn on breached passwords at password set
Check a newly chosen password against HaveIBeenPwned through the k-anonymity
range API: only the first five hex characters of the SHA-1 digest leave the
host, the suffix is matched locally, and Add-Padding hides the bucket size.
A confirmed match is rejected so a fresh credential is never a known-breached
one; the check fails open, so an unreachable HIBP never blocks the operation.
Covers registration, password change, and admin reset. Existing passwords are
never retroactively blocked at login.

Also lands the six-locale strings for the password-breach notice and the
account-security surfaces that follow.
2026-06-27 20:34:20 +02:00
Marc-André Bombeck e5f9fb7c4a feat(admin): show encryption coverage and rotation progress
Add a read-only admin Encryption section that surfaces what the rotation
tooling computes: per encrypted-column rows-per-key-id, legacy-key row
counts, and overall rotation progress, plus a 'safe to drop the legacy
key' state once every column sits on the active key. GET
/api/admin/encryption/status drives it and returns only key ids and
counts — never key material. A shared scan over the canonical encrypted-
column registry backs both this view and the rotation run. Cookie-only
admin.
2026-06-27 20:27:31 +02:00
Marc-André Bombeck 0ba77778d5 feat(export): offer a passphrase-encrypted archive
Add an opt-in to download the full backup as an encrypted archive instead
of a plaintext JSON file. The user supplies a passphrase over the request;
the server derives a 256-bit key with Argon2id and seals the export bytes
with AES-256-GCM into a self-describing HLX1 archive (magic, version, KDF
params, salt, iv, tag, ciphertext), mirroring the off-host backup envelope.

POST /api/export/encrypted returns the binary archive and is step-up gated
for accounts with a second factor; single-factor accounts use a normal
session. The passphrase is never stored and never logged, and there is no
server-side recovery — the UI states this. A standalone decryptor
(scripts/decrypt-export.ts) opens the archive with the passphrase outside
the app. The plaintext full-backup route now shares one payload builder so
both variants emit the same restore-compatible shape.
2026-06-27 20:27:07 +02:00
Marc-André Bombeck 6ec0ee0215 chore(v1.22.1): tidy labs subtitle scale and drop dead sleep keys
Standardise the labs marker subtitle to text-xs across both the compact list
and the card grid, and build the localized name->slug lookup map once per
locale instead of re-scanning the catalog seeds per group on every render.

Add relative to the mobile section strips in the settings and admin shells so
the active chip's offsetLeft is measured from the strip, giving an exact
horizontal scroll target.

Drop the orphaned insights.sleep.headlineCaption and headlineCaptionSuffix
keys from all six locales; nothing references them after the sleep headline
removal.
2026-06-27 18:28:38 +02:00
Marc-André Bombeck 802ea1c60a Merge branch 'wave/v1221-c' into release/v1.22.1 2026-06-27 18:07:16 +02:00
Marc-André Bombeck acf4941be8 Merge branch 'wave/v1221-d' into release/v1.22.1 2026-06-27 18:07:07 +02:00
Marc-André Bombeck 66e3742cd2 feat(coach): add a per-message hover action row (copy, feedback, regenerate, read-aloud)
Surface an icon-only action row below each message, sharing the row with
the hover timestamp: muted until the bubble is hovered or focused on
pointer devices, and always visible on touch where there is no hover.
User turns get Copy; assistant turns get Copy, Read-aloud, a thumbs-up /
thumbs-down feedback pair (wired to the existing per-message feedback
endpoint), and Try-again. Copy strips inline chart tokens from assistant
prose so they are never pasted. Try-again resubmits the user turn that
produced the reply as a fresh turn, leaving the composer untouched.
2026-06-27 18:05:19 +02:00
Marc-André Bombeck 37339cf83e fix(coach): keep the composer placeholder on one line
Shorten the composer placeholder across all locales to a short, inviting
one-liner and clip the placeholder pseudo-element so a longer hint can
never wrap to a second line inside the single-row composer on a narrow
phone. The textarea value still wraps and auto-grows for real typing.
2026-06-27 18:03:55 +02:00
Marc-André Bombeck 3caf95160d feat(labs): describe each biomarker under its heading
Surface a short, factual subtitle under every catalog-linked lab marker on
the Labs page, mirroring the descriptive line the Insights overview carries
under its metric headings. The text states what each biomarker measures,
sourced from the seed catalog via a new labs.catalog.desc.<slug> i18n map in
all six locales.

A reading's analyte resolves back to its catalog slug by a case- and
whitespace-insensitive match against the localised catalog name; free-text
markers with no catalog match carry no subtitle rather than a fabricated one.
Rendered in both the compact list view and the card/tile view.
2026-06-27 17:58:32 +02:00
Marc-André Bombeck 89b8c0e6d7 refactor(admin): rename the availability section to Modules 2026-06-27 17:57:45 +02:00
Marc-André Bombeck ddfe9170bd feat(mcp): allow minting and consenting to health:write connector tokens
Let a user opt a connector token into logging. The settings card gains a
write toggle; the mint endpoint accepts a closed scope choice (read, or
read_write) and builds the permission array explicitly — it can mint only
[health:read] or [health:read, health:write], never a wildcard or any other
grant. The OAuth flow grants health:write only when a client requests it, and
the consent screen states in plain language that the app will be able to read
AND log/write health data when write is requested; the token endpoint grants
exactly the approved scopes and never widens. Read-only behaviour is
unchanged. Add the English and German strings for the new toggle and warning.
2026-06-27 17:38:51 +02:00
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 22eb9cba0d fix(mcp): close OAuth 2.1 resource-server gaps before the remote surface ships
Harden the remote MCP endpoint and the in-repo OAuth bridge against the
adversarial review of the milestone branch. The two ship-blockers made the
"read-only, revocable" promise false; the rest tightened the surface.

Audience binding at the resource server: a token whose only grant is the new
`health:read` scope is the MCP read credential. The REST edge now accepts it on
safe (read) methods and refuses it on writes/deletes with 403, so a connector
token can read over `/mcp` and GET routes but can never POST/DELETE the record
despite a "read-only" consent. The `/mcp` resolver is unaffected.

Revocable, theft-detectable refresh tokens: refresh artifacts now anchor to a
persistent connection row. Revoking a connection stops every future refresh and
kills its access tokens; a replayed (already-rotated) refresh is treated as
theft and revokes the whole connection. Connections surface in settings as the
unit a user can disconnect. The old access token is revoked on rotation.

Also: fail closed on the OAuth/`/mcp` surface when no canonical origin is
configured and mark the discovery documents non-cacheable; honour the global API
kill-switch on `/mcp`, `/token`, `/authorize`, and `/register`; cap the CIMD and
registration body reads while streaming instead of after; show the verified
client origin on consent; validate CIMD redirect-URI schemes; assert same-origin
on the consent POST; and add a daily prune for expired connector tokens.

New migration 0195 adds the connection anchor and links access tokens to it.
2026-06-27 15:09:11 +02:00
Marc-André Bombeck 10111852a4 feat(insights): localized Coach seeds, freshness labels, paragraph rendering
Route the composer seed questions through i18n so a German user no longer
gets an English opener when tapping Ask the Coach from a metric page or an
insight card. Replace the relative timestamp on the briefing and per-metric
cards with a freshness label that shows the time only for today, drops it for
yesterday, and renders a locale date honouring the user's date-format
preference for older entries. Render the briefing paragraph, the per-metric
and score assessments, and the period narrative through the shared paragraph
helper so longer prose shows real paragraphs instead of one run-on block.

Adds the insights.coach.seed.* and insights.updated* keys (English + German).
2026-06-27 14:40:18 +02:00
Marc-André Bombeck 06363771ee feat(settings): add an MCP connector card with health:read tokens
A dedicated Settings card co-locates the off-by-default MCP module toggle
with token management for connecting an external assistant:

- Mint / list / revoke a dedicated `health:read`-scoped token (shown once,
  copy to clipboard) via new `/api/mcp/tokens` routes — never the
  `medication:ingest` scope, never the wildcard.
- Surface the connector URL and the enable switch in one place, wired to
  the existing module-preference endpoint.
- New section wired into the Settings shell next to API & Tokens, with
  EN + DE strings (and the other locales) under `settings.mcp` and
  `settings.sections.mcp`.
2026-06-27 14:27:30 +02:00
Marc-André Bombeck 03d41bc5c0 feat(mcp): serve the read-only tools over remote Streamable HTTP at /mcp
Bridge the existing transport-agnostic MCP server factory to a Next.js
route at /mcp using the SDK's Web-standard Streamable HTTP transport, so a
remote client can connect with a manually-minted hlk_ Bearer token. The
same tool/resource layer the local stdio transport serves is reused
verbatim — no duplicated tool logic.

The route owns four gates in front of the transport:

- Auth: Authorization: Bearer hlk_… resolved by the same validator the
  HTTP edge uses, so revoked/expired/scope semantics never drift. A cookie
  is never consulted, so the cookie-only admin boundary stays unreachable
  here regardless of token scope.
- Rate limit: the Postgres limiter keyed by the <userId>:<tokenId> binding
  (mcp:<binding>), so a single shared credential cannot drain the account.
- Module gate: a new opt-in mcp module, OFF by default. With it off the
  endpoint answers 404 and reveals nothing — a self-hoster who never opts
  in gets zero new exposure. The Modules settings hub renders the toggle.
- Transport: a fresh stateless server + transport per request.

Read-only by construction (only read tools are registered) and same-origin
by construction (no CORS headers). The proxy lets /mcp skip the page cookie
redirect since it authenticates by header, mirroring the webhook surfaces.
2026-06-27 13:47:24 +02:00
Marc-André Bombeck e2c6e46f2a merge: W7 coach memory, reminders, action cards into v1.22 2026-06-27 13:39:45 +02:00
Marc-André Bombeck 5331528076 feat(coach): surface reminders in-app and add the action card
Add a "Your reminders" panel under Settings → Coach that lists the stored
reminders, highlights the due ones, and gives the user view, confirm,
done/dismiss, and delete control over their own memory. Light the Coach
launcher dot when a reminder is due, alongside the existing nudge signal.

Add the generic confirm-card component for the suggested-action frame: a
plain-text preview with a localized per-action heading and a single confirm
tap that posts the validated params to the build endpoint.

Translate the new copy in English and German.
2026-06-27 13:38:25 +02:00
Marc-André Bombeck 914fa8d36e feat(coach): add inline charts, read-aloud and message timestamps
Activate the dormant chart-token render path for the Coach: an allowlisted
metric:<TYPE> token in a settled assistant turn mounts the self-fetching
HealthChart for the user's own series, gated on the turn's grounded
provenance so a metric the reply never saw never renders and no data is
fabricated. Add a read-aloud toggle backed by the browser Speech Synthesis
API (no dependency, no egress) and a hover/tap timestamp tooltip on each
message bubble. Render message text through the shared paragraph helper.
2026-06-27 13:31:11 +02:00
Marc-André Bombeck 7d3c528157 merge: W1 insights surface (i-icon, subtitle, steps, type-click) into v1.22 2026-06-27 13:05:27 +02:00
Marc-André Bombeck 8a7a2bc2e8 Link a measurement type to its insights sub-page
Clicking a type on the measurements list now drills into that metric's
insights sub-page when one exists, falling back to the existing
?type= filtered list when it does not. A reverse type-to-slug map backs
the navigation, preferring the dedicated single-metric page so an
ambiguous type resolves correctly (PULSE to pulse, not blood-pressure;
WEIGHT to weight, not bmi). No API change.
2026-06-27 13:03:09 +02:00
Marc-André Bombeck 07b65ade88 Add a subtitle slot to the insights overview section heading
Detail pages already render a one-line description under their heading;
the overview section headings had no slot for one. Add an optional
subtitle to SectionHeading, rendered in the same muted typography, and
wire it into the obvious top-level sections: Trends (folding its existing
right-aligned caption into the slot), Your health scores, and Your
vitals.
2026-06-27 13:02:41 +02:00
Marc-André Bombeck 3ad59cde98 feat(labs): compact editable biomarker list with hide
Rebuild the biomarker manager as compact rows (name + unit inline,
reference range / panel on a muted sub-line) with a hide toggle, an
edit button, and a delete, replacing the one-card-per-marker layout.
Hidden markers collect in a separate restorable section. Filter hidden
markers out of the lab-entry picker, keeping the currently selected one
visible so an existing reading still resolves its label.

Touch targets meet the 44px mobile floor (size-11 sm:size-9).
2026-06-27 12:48:54 +02:00
Marc-André Bombeck fd2b5b06b8 i18n(coach): add settings, opener-dismiss, and conversation-group keys
Cover the new composer settings link, the seeded-opener dismiss control,
and the recency group headers (today, yesterday, this week, earlier) on
the dedicated conversation-history page, across all six locales.
2026-06-27 12:18:32 +02:00
Marc-André Bombeck df216e082b i18n: add ambient Coach presence keys across locales 2026-06-27 00:33:00 +02:00
Marc-André Bombeck 03e1935c8a merge(LEARNUI): Learn-links registry, component, and surface wiring 2026-06-26 13:23:28 +02:00
Marc-André Bombeck 6ed5b55f3d Add discreet LearnMoreLink pointer component
A small, calm anchor that links a concept out to its public /learn
guide. Fail-closed: the href is registry-backed (a closed-set value,
never user input) and an unmapped concept renders nothing. Plain
anchor, no markdown. Adds a common.learnMore label across all locales.
2026-06-26 13:22:27 +02:00
Marc-André Bombeck 174c26afc9 refactor(coach): streamline the new-chat page composer
Reduce the full-page Coach new-chat surface to a calmer layout. Drop the
toolbar Conversations and New chat buttons and place the settings gear in
the top-right corner; both removed actions remain in the composer's + menu.
Collapse the greeting to one line and remove the gear from the composer so
its front is only the + actions menu, leaving a single baseline row of
[+] textarea [mic] [send].
2026-06-26 13:15:58 +02:00
Marc-André Bombeck 9a3049a6cf fix(coach): phrase the daily-limit reset as midnight UTC
The budget-exceeded copy named "00:00 UTC", which reads as a wrong local
clock for any non-UTC user (Berlin midnight UTC is 01:00/02:00 local) and
drove confused "the limit resets at the wrong time" reports. Reword
dailyLimitBody across all six locales to "midnight UTC", matching the
existing errorBudget wording, so the figure is honest regardless of the
reader's timezone.
2026-06-26 12:52:52 +02:00
Marc-André Bombeck 93433c3197 feat(coach): add "ask the Coach about this" to insight cards
Add a discreet, on-brand action to the high-value cards (briefing,
recommendation, correlation, status/assessment, health score, period
narrative) that opens the Coach pre-scoped to the card topic with a
seeded question. The action self-gates on the operator flag and the
per-user opt-out, never tints the card, and surfaces once per card.
2026-06-26 12:38:03 +02:00
Marc-André Bombeck 9b7980d020 feat(settings): add the date-format profile control
Surface a Datumsformat select below Stundenformat with the four
preferences, in all six locales.
2026-06-26 12:15:16 +02:00
Marc-André Bombeck 1c93a670f0 feat(labs): surface low-confidence OCR rows in review
The extraction already self-scores each field; the review screen only
hinted at a shaky numeric value. Add a calm per-row low-confidence badge
when any tracked field scores below the threshold, including qualitative
rows that had no confidence cue. Informative secondary badge, no alarm
colour.
2026-06-26 09:26:59 +02:00
Marc-André Bombeck 9021b3e0ee Merge Fitbit classic Web API transport into release/v1.20.0
Retarget the Fitbit integration from the Google Health API (which needs
annual CASA review — a wall for self-hosters) to the classic Fitbit Web
API with PKCE and rotating refresh tokens, so a self-hoster can connect
with an instant developer app (migration 0191 adds the PKCE verifier).
Transport-only fork; the ingest, dedup, source-priority, backfill and
poll paths are reused. Intraday HR and skin temperature are not mapped
(no honest slot); the integration stays experimental.
2026-06-22 07:22:31 +02:00