Commit Graph
13 Commits
Author SHA1 Message Date
Marc-André Bombeck 11bafd68b5 feat(coach): one quiet status line, reply pills and memory notes in the thread
The top of an answer is one line that says what the Coach is doing right
now (thinking, fetching a metric, summarising the readings, writing),
live from the activity frames, with the seconds so far. It never opens
by itself: the trail stays closed while the turn runs, when it asks,
when it stops early, after the answer and after a reload, and opens
only on a tap. Once the answer is in, the line settles into one summary
of lookups and seconds. A table read during the turn shows as a
one-line preview with a sparkline until the answer takes it over.

Clarifying choices, follow-ups and the answers to a fact or plan
proposal share one pattern: quiet pills in the answer's column whose
tap sends the label as the person's own message. The clarification
card and the in-thread plan card are gone. A fact the Coach kept is one
meta line with Undo, an assumption one line with Change. The remember
button on a message now keeps it in the Coach's memory.
2026-10-06 20:47:17 +02:00
Marc-André Bombeck 0598b58f0e feat(coach): client slots for steps, tables, method, chips and clarification
The streaming hook handles the step, result, followUps and clarification
frames and sends followUp and clarification on the tool route only. The
bubble gains slots for the step list, the tables and the method line; the
thread offers chips under the latest assistant turn only; the conversation
shows the clarification card above the composer and marks a typed answer
as answering it. The tables of a persisted message load lazily when it
scrolls into view, seeded from the stream for the turn that produced them.
The new components render nothing yet.
2026-09-27 10:57:53 +02:00
Marc-André Bombeck 3aff2c16db fix(insights): scope generation guard to prompt fields, clear cache on restore 2026-07-28 21:11:43 +02:00
Marc-André Bombeck 1a642f1173 feat(coach): full-history search + pagination; document the labs watchdog
Coach conversation history was capped at the rail's first ~20 threads and
searched only that loaded page. The rail and the standalone conversations
page now walk the caller's entire history via cursor pagination
(useInfiniteQuery + a scroll sentinel), and search runs server-side against
the conversation title (GET /api/insights/chat?q=) instead of filtering an
already-truncated client array.

Also extends the "automatic screening" info line in the metric catalog to
name the labs "what changed since your last panel" card alongside the
rhythm/breathing/health-status watchdogs it already covered — it un-mounts
the same way when there's nothing to flag and was missing from the copy.
2026-07-18 05:06:24 +02:00
Marc-André Bombeck 502783a3ea feat(coach): attach multiple documents to a conversation
Replace the scalar document_id discriminator on a coach conversation with a
CoachConversationDocument join table and a sticky documentScoped flag, so a
conversation can be grounded on several stored documents at once while the
prompt-injection fence stays intact across the data-model change.

The fence is re-established at both enforcement points, fail-closed:

- Client: resolveCoachSendTarget routes any turn on a fenced conversation (or a
  fresh chat with staged attachments) to the new /api/insights/chat/fenced
  endpoint; the fenced body is strict and never carries scope, guided-question,
  or prefill hints.
- Server: the tool route fetches with documentScoped:false AND 404s when the
  fetched row still carries an attachment (flag/join drift), emitting a
  fence_drift audit alarm. Document text now enters an LLM prompt in exactly one
  code path — the fenced pipeline — with no tools and no health snapshot; a
  module-graph test pins that the tool loop is unreachable from it.

The fenced turn fences each document separately with a server-built, scrubbed
header line (title and filename are untrusted text), runs a per-document egress
consent check on the single picked provider, and grounds numerically over the
live attachment union only. Attachments are capped at five documents and 192 KB
of combined text with honest truncation.

Attach and detach ride dedicated endpoints: attaching validates the document is
owned, live, indexed, and within cap, then flips the conversation fenced (the
one legal, privilege-reducing transition, audit-logged); detaching removes the
document but never clears the flag. The document-sheet chat keeps its public
wire contract and shares the same fenced pipeline over the join.

Migration 0245 adds the join table and the flag, backfills every legacy
single-document chat into a one-attachment fenced conversation, and drops the
scalar column in the same transaction — no dual-truth window.
2026-07-16 23:32:28 +02:00
Marc-André Bombeck f5b0ec431e feat(coach): surface plan proposals for confirmation and add the plans page
The Coach has extracted goal / if-then plan proposals since v1.21.3 —
written as status "proposed", never activated without the user — but no
surface ever showed them, so nothing could be confirmed and the active-plan
memory block stayed permanently empty. This lands the missing user half:

- Chat thread: proposal confirm cards at the tail of the open conversation
  (proposals land asynchronously via the post-turn memory-refresh worker,
  so the block slow-polls the proposed list while the thread is mounted and
  only shows proposals born in that conversation). Accept activates the
  plan — it enters the Coach's snapshot memory from the next turn on;
  decline soft-deletes the proposal so it never activates and stops
  counting against the per-user cap. A settled card collapses to a
  one-line confirmation.
- /coach/plans: the management ledger, a sibling of /coach/conversations
  reachable from the composer's + menu. Groups proposals (confirm /
  decline), standing plans (goal met / end), and the settled history
  (remove). No prose editor by design — the extractor is the only writer
  of plan text and the PATCH contract only moves the lifecycle.
- Shared use-coach-plans hook + centralised coach-plans query keys, so the
  thread cards and the page invalidate each other's reads.
- Both coach-gate invariants (operator master flag, per-user opt-out)
  register the new page; i18n keys land in all six locales.

SSR tests pin the card contract: only same-conversation proposals render,
undecryptable rows are skipped, both actions are real buttons.
2026-07-05 20:00:38 +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 2c33faa648 chore(insights): align the ambient-Coach file comments to the shipped version
The A1–A3 source files carried v1.22.0 docstrings from before the work was
re-cut as the v1.21.2 point release. Comment-only sweep so blame and the
in-file version tags read consistently with the shipped version.
2026-06-27 00:48:40 +02:00
Marc-André Bombeck 9136a9bdba feat(coach): make launch scope visible and seed the blank state
Replace the hidden composer prefill with a visible scope affordance: when
the Coach is launched narrowed to a metric, the hero shows a scope pill
naming that metric plus a tappable seed question, and the drawer sources
rail surfaces the same scope line. When the Coach opens unscoped, resolve
today's most notable derived signal server-side and offer it as a tappable
opener; fall back to the neutral greeting when no signal crosses the gate,
so the blank box is never cold and never fabricated.

Add an internal GET /api/insights/coach/seeded-question that reuses the
briefing's derived-signal detector to select the opener server-side; the
client renders the resolved DTO.
2026-06-26 23:56:56 +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 bda06c9ec4 refactor(query-keys): route remaining bare arrays through the factory
The target editor invalidated a bare ["insights"] array on save and reset.
That literal sat outside the centralised factory, so the broad insights
fan-out it intended never matched the reads, which key through
insightsTargets/insightsRoot. Route the editor's reads and invalidations
through userThresholds(), insightsRoot(), insightsTargets() and analytics()
so a saved target actually refreshes every insights surface.

Migrate the other call sites the lint rule does not reach onto the factory:
the Coach conversation rail + thread reads, the per-metric personal-records
badge, the per-day measurement drill-down, the dashboard intake
compliance-chart fan-out, the per-medication side-effects list, the
onboarding auth invalidations, the notification-preferences read, and the
PWA persister's auth-me lookup.

Add the matching factory keys: coachConversations, coachConversation,
personalRecordsByMetric, measurementDrilldown, complianceChartInline and
medicationSideEffects. Each new key reuses the prefix its reads already use
so no invalidation shape drifts.
2026-06-19 20:57:18 +02:00
Marc-André Bombeck 3a3f88a80c feat(coach): mark an unread proactive nudge on the Coach launcher
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.
2026-06-18 21:19:27 +02:00
Marc-André Bombeck 35e8f076a2 refactor(openapi,queries): feature-located registries behind thin indexes
The five-thousand-line route registry splits into thirteen domain
modules and the query-key factory into twelve feature files — each
behind an index that preserves exact registration order, proven by a
byte-identical generated contract. The factory lint rule learns the new
home directory.
2026-06-11 00:10:00 +02:00