Plans are the person's own record, like the stored facts and
conversations. The list and delete routes no longer ask the Coach
module, and the plans page turns read-only instead of redirecting to
Insights: every plan can be read and removed, while confirming or moving
one stays Coach use and is refused by the PATCH route as before.
The Today hero and the check-in hook imported the check-in intent constants
from digest.ts, which — since the milestone slice — transitively reaches the
database through the streak baseline engine. That pulled the Postgres driver
into the client bundle (a Turbopack 'Can't resolve net/tls/dns' build failure
the worktree build couldn't surface). Move the four constants to a tiny,
dependency-free coach-checkin-intents module; the client imports them from
there, and digest.ts (the server builder) stays server-only.
Every accepted coach plan now earns a gentle weekly check-in. Confirming a
plan (proposed → active) without an explicit review date defaults one to a
week out, so a plan is always reviewed, not only the coach's own experiments.
The daily digest emits a single `coach_checkin` rail item when a standing
plan's review has come due — its pinned review date, or a defaulted
activation + 7 days for plans that predate it, or the read-back state a
reviewed plan carries. Capped at one check-in a day across all plans
(earliest-due wins); the rest wait for a following day. It reads existing
plan state only — no fresh generation, nothing warms on mount, and it is
gated on the coach module.
The card offers three one-tap actions through the existing plan-lifecycle
route: keep it (re-arm for another week), adjust it (open the coach), or let
it go (a guilt-free, respected end — the calm inversion of a streak). An
ignored check-in sits quiet and stops resurfacing after about two cycles;
its status is never changed behind the user's back.
Copy lands in all six locales.
Groundwork for the plan confirm/manage UI. The CoachPlan pipeline has been
complete on the server since v1.21.3/v1.22 — extractor writes proposals
(status "proposed", per-user cap, de-dup, encrypted prose), the PATCH
confirms proposed -> active, buildCoachPlansBlock injects the top-6 active
plans into the snapshot, and the review sweep drives the reviewDate
lifecycle — but no surface ever listed or confirmed a proposal, so the
extraction spend had no user-visible outcome.
Two additive contract changes the UI needs:
- GET /api/coach/plans accepts ?scope=open|past|all (open = proposed +
active + review_due, past = met + abandoned + reviewed, all = no status
clause) so the management page reads its ledger in one round-trip.
Mutually exclusive with ?status=; both omitted keeps today's
non-terminal default unchanged.
- Plan items now carry sourceConversationId so the chat thread can show
only the proposals born in the open conversation.
OpenAPI regenerated; route tests cover the scope mappings, the
status+scope 422, and the provenance passthrough.
Mirror the about-me management routes: the PATCH (confirm/update) and DELETE
on /api/coach/plans/[id] now apply a per-user mutation limit, closing the
consistency gap with the rest of the Coach management stack. Owner-scoped and
cheap, so the limit only caps a runaway client loop.
Add a CoachPlan model: durable, user-scoped if-then implementation plans
the Coach proposes and the user confirms. The background memory-refresh
pass extracts plans the user agreed to and writes them as "proposed"; only
the user-facing PATCH on /api/coach/plans/[id] activates one. The free-text
cue / action / target are encrypted at rest with the shared Bytes codec;
metric, status and dates stay plain so the injection picker ranks without a
per-row decrypt. Active plans fold into the Coach snapshot's memory block
and survive the prompt-budget drop alongside facts. List, confirm and
soft-delete routes are Coach-gated and narrow the owner from the session.
Key the dashboard-snapshot query cell by locale so a locale switch reads
freshly localised tile copy on its own cell instead of serving the prior
locale's prose until the slot goes stale. The zero-arg key stays
byte-identical, so every prefix-based invalidation keeps reaching the
locale-keyed cells; the offline-banner and preloader match on the prefix.
Migration 0194 (additive CREATE TABLE). Encryption-rotation registry and
script cover the three new Bytes columns.