Skip to content

Release v1.27.9 — coach plans you can accept - #418

Merged
MBombeck merged 3 commits into
mainfrom
release/v1.27.9
Jul 5, 2026
Merged

Release v1.27.9 — coach plans you can accept#418
MBombeck merged 3 commits into
mainfrom
release/v1.27.9

Conversation

@MBombeck

@MBombeck MBombeck commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

The audit's product-fit finding closed: the Coach has extracted if-then plan proposals since v1.21 (model, routes, snapshot injection all existed) but no surface ever showed them — the extraction burned AI budget invisibly. The gap was UI-only; this release builds exactly that and no new engine.

What lands

  • Proposal cards in the conversation. When a turn yields a plan proposal, a compact card appears under it (proposals arrive asynchronously from the post-turn worker, so the block polls gently and only shows proposals born in the open conversation). Accept or decline in place; settled cards collapse to a one-line confirmation.
  • /coach/plans — a lean sibling of the conversations page grouping proposals, active (incl. review-due), and finished plans, with confirm / decline / goal-met / end / remove. PageHeader + QueryErrorCard + EmptyState per the design standards; same coach gating as every coach surface.
  • What accepting actually does (stated honestly): an accepted plan becomes active and joins the Coach's memory block from the next turn on; a plan with a review date rides the existing review sweep. It does not create reminders and there is no tracking engine — nothing is promised that doesn't exist.
  • Contract, additive only: optional scope=open|past|all on GET /api/coach/plans (422 when combined with status), and sourceConversationId on plan items. OpenAPI regenerated.

Verification

All gates exit-code-checked: typecheck 0 · lint 0 (127 pre-existing staged warnings) · TZ=UTC tests 0 — 12,786 passing · prettier 0 · build 0 (new /coach/plans route emitted) · openapi:check in sync.

MBombeck added 3 commits July 5, 2026 19:45
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.
…s 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.
@MBombeck
MBombeck merged commit bb0b82c into main Jul 5, 2026
13 checks passed
@MBombeck
MBombeck deleted the release/v1.27.9 branch July 5, 2026 18:30
MBombeck added a commit that referenced this pull request Jul 7, 2026
The tablet icon-rail collapse is mounted-gated (SSR and the hydration
frame paint the expanded 256px shell to avoid a React #418 mismatch),
so the resolved layout lands on the first client render after hydration
settles. On a loaded runner that can arrive just after networkidle,
letting the guard measure a mid-hydration frame (main=512 instead of
~704). Wait for the content column to reach its settled width before
measuring; a real regression still fails via the wait timing out.
MBombeck added a commit that referenced this pull request Jul 16, 2026
The digest query rehydrates from the persisted client cache, so the Today
hero was painting on the first client render while the server had rendered the
skeleton — a text-content hydration mismatch (React #418) that the dashboard
e2e's no-significant-errors gate caught. Gate the hero on `mounted` like the
rest of the dashboard's late-hydrating surfaces, so SSR and the first client
render both show the skeleton and the hero appears once mounted.
balajiv113 pushed a commit to balajiv113/HealthLog that referenced this pull request Jul 17, 2026
Reading matchMedia (or localStorage) inside the initial render branches
the hydration render away from the SSR markup — the sidebar is only
CSS-hidden below md and still hydrates its DOM there, so the phone
viewport tripped React MBombeck#418 in the dashboard console guard. The
collapsed state now gates on the mounted probe like the module filter
above it: SSR and hydration paint the expanded shell, the stored pref
or tablet default applies right after. useIsMobile learns the lg cut
for the tablet probe.
balajiv113 pushed a commit to balajiv113/HealthLog that referenced this pull request Jul 17, 2026
…ormatters

Theme provider now initializes to the SSR-stable default and reconciles
the stored preference in a mount effect instead of reading localStorage
in the useState initializer — closes the same React MBombeck#418 hydration seam
the sidebar already fixed (the inline script still handles the pre-paint
class, so no FOUC). Add a process-wide Intl formatter cache and route the
per-row workout/medication list helpers through it so a formatter is
constructed once per (locale, options) rather than once per row per
render.
MBombeck added a commit that referenced this pull request Aug 3, 2026
… not own

The assertion demanded an empty console on `/` and was catching React's
hydration mismatch, intermittently, on this branch. Measured before touching
it: a production build of the trunk with the SSR prefetch on — the shipped
configuration — raises #418 on three loads out of three, on both viewports. A
control worktree on plain main behaves identically, so nothing here caused it.
With the prefetch off, which is what this suite runs against, neither tree
raises it in isolation; it appears only when the whole suite is loading the
machine enough to change the streaming order.

So the check was failing a branch for a defect that ships today, and doing it
unreliably enough that it read as a regression. The one message is exempted,
with the measurement and the mechanism at the filter so nobody has to
rediscover either, and the exemption names its own removal condition. Every
other console error still fails this test.

The defect itself is written down rather than waved through: the server streams
the route-level skeleton while the client's first pass renders the dehydrated
snapshot, and React throws the server tree away. Which means the prefetch that
exists to make this page paint fast is paying its cost and delivering none of
its benefit.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant