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.
This commit is contained in:
Marc-André Bombeck committed 2026-10-06 20:47:17 +02:00
1 parent 4298207500
commit 11bafd68b5
37 files changed
+3495 -1597

No files matched your search

+1 -16
View File
@@ -14,12 +14,7 @@
* prose editor by design: the PATCH contract never accepts the encrypted
* free-text fields.
*/
import {
useMutation,
useQuery,
useQueryClient,
type UseQueryOptions,
} from "@tanstack/react-query";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { queryKeys } from "@/lib/query-keys";
import { apiDelete, apiGet, apiPatch } from "@/lib/api/api-fetch";
@@ -67,22 +62,12 @@ async function fetchPlans(filter?: PlanFilter): Promise<CoachPlanDTO[]> {
export function useCoachPlans(opts?: {
filter?: PlanFilter;
enabled?: boolean;
/**
* Optional poll interval (ms), or TanStack's interval callback so a caller
* can bound its poll (the chat thread caps attempts — see
* `nextProposalPollInterval` in `plan-proposal-card.tsx`). Proposals land
* asynchronously (the memory-refresh worker runs after the turn); the
* management page reads once. TanStack pauses the interval in background
* tabs by default, so the poll never runs unwatched.
*/
refetchInterval?: UseQueryOptions<CoachPlanDTO[]>["refetchInterval"];
}) {
const filter = opts?.filter;
return useQuery({
queryKey: queryKeys.coachPlans(filterKey(filter)),
queryFn: () => fetchPlans(filter),
enabled: opts?.enabled ?? true,
refetchInterval: opts?.refetchInterval,
});
}