feat(coach): New conversation in the chat, a foldable list, and the day beside it

New conversation leaves the conversations panel's header and floats in
the conversation itself: a round 56 px button with a plus, the floating
Coach button's face, named "New conversation". It sits in the composer's
row, so it always belongs to the chat column, never to the list or a
docked day beside it. Where the column is wide it takes the bottom right
corner beside the composer; where the composer fills the width (a phone,
the drawer, a narrow column) it rises above the composer's edge. The
drawer shows it once there is a conversation to leave, in place of the
plus in its header. An empty conversation shows nothing either way.

The docked conversations panel folds like the day: its header opens with
a fold control, and folded it keeps a 48 px edge whose button opens it
again (aria-expanded, focus moved between the two, Escape inside folds
it). The top bar's toggle remains for the sheet below 1280 px. On /coach
the day docks right of the panel. Below 1600 px only one of the two is
open: a day opening folds the list without touching the remembered
choice, and opening the list folds the day to its edge. From 1600 px both
stay open. Switching or starting a conversation keeps an open day in the
URL.

The empty conversation drops everything under the composer: the daily
seeded opener and the scope pill are gone, and with them the only reader
of GET /api/insights/coach/seeded-question and of the
coach.ambientSuggestions preference. The route, its OpenAPI entry, the
preference and its Settings switch go too.
This commit is contained in:
Marc-André Bombeck committed 2026-10-09 13:44:58 +02:00
1 parent 9d5a529d53
commit 08f532b428
29 files changed
+659 -1338

No files matched your search

-77
View File
@@ -11624,30 +11624,6 @@ paths:
"403": *a11
"422": *a4
"429": *a5
/api/insights/coach/seeded-question:
get:
tags:
- Insights
summary: Today's suggested Coach opener
description: "Resolves today's single most notable derived wellness signal into a tappable opener for the Coach's
blank-chat hero, using the same confidence-gated detector the daily briefing uses. The selection happens
SERVER-side and the client renders it rather than recomputing. `signal: null` is the ordinary answer and means
the hero keeps its neutral greeting — never a fabricated opener. Two different causes produce that null and are
indistinguishable on the wire: nothing crossed the notability gate, or the account has turned proactive
suggestions off, in which case the detector does not run at all. The opener exists only to open the Coach, so it
is served only while the `coach` AI capability is available; otherwise the answer is the same neutral `signal:
null` and `ai` says why. Never refused for an AI reason. Shared analytics-read budget. Cookie or Bearer auth;
not delegable."
responses:
"200":
description: The opener, or the neutral null.
content:
application/json:
schema:
$ref: "#/components/schemas/CoachSeededQuestionEnvelope"
"401": *a1
"422": *a4
"429": *a5
/api/insights/narrative:
get:
tags:
@@ -27376,8 +27352,6 @@ components:
enum:
- weekly
- biweekly
ambientSuggestions:
type: boolean
nudgeAiComposed:
type: boolean
measurementReminder:
@@ -60734,57 +60708,6 @@ components:
description: The two server-authoritative lines a metric sub-page renders above its chart. Pure compute over the
baseline and correlation engines — no provider call, no cache table — so web and native decode the same resolved
DTO.
CoachSeededQuestionEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/CoachSeededQuestion"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
CoachSeededQuestion:
type: object
properties:
signal:
anyOf:
- type: object
properties:
sourceMetric:
type: string
description: Sentinel id the client keys its localised copy on (`readiness` / `recovery`).
score:
type: number
description: The latest 0..100 score.
band:
type: string
description: "`yellow` or `red`. Green never surfaces — a good day is not notable."
required:
- sourceMetric
- score
- band
additionalProperties: false
- type: "null"
description: Null whenever nothing crossed the detector's confidence and notability gate, AND whenever the account has
turned proactive suggestions off, AND whenever the `coach` capability is unavailable (then `ai` says why) —
all mean the hero keeps its neutral greeting rather than showing a fabricated opener.
ai:
description: The `coach` capability the opener exists for.
$ref: "#/components/schemas/AiCapabilityState"
required:
- signal
- ai
additionalProperties: false
description: Today's single most notable derived wellness signal, resolved server-side into a tappable opener for the
Coach's blank-chat hero. The client renders the resolved DTO and never recomputes the selection.
NarrativeEnvelope:
type: object
properties: