Merge L1/L2 cleanup into release/v1.19.2

Correct the sleep-rhythm peer-card comment to state plainly that each
card reads its own window (the captions disclose the per-card night
count); no computation changed. Remove the dead streaming.startedAt and
streaming.reasoning state left after the thinking-disclosure removal.
This commit is contained in:
Marc-André Bombeck committed 2026-06-21 20:57:35 +02:00
commit ede01e5700
3 files changed
+23 -59

No files matched your search

@@ -62,10 +62,10 @@ import {
import type { CoachConversationDetailDTO } from "@/lib/ai/coach/types";
import type { CoachStreamingMessage } from "../use-coach";
// v1.18.9 — `CoachStreamingMessage` gained `usage` / `startedAt` /
// `reasoning` (per-message token footer + thinking disclosure). The
// fixtures below predate those fields; this builder fills the defaults so
// each test only spells out the fields it cares about.
// v1.18.9 — `CoachStreamingMessage` gained a `usage` field for the
// per-message token footer. The fixtures below predate it; this builder
// fills the defaults so each test only spells out the fields it cares
// about.
function streaming(
partial: Partial<CoachStreamingMessage>,
): CoachStreamingMessage {
@@ -77,8 +77,6 @@ function streaming(
messageId: null,
errorCode: null,
usage: null,
startedAt: null,
reasoning: "",
...partial,
};
}
@@ -199,19 +199,6 @@ export interface CoachStreamingMessage {
* bubble; persisted bubbles read `CoachMessageDTO.tokensUsed` instead.
*/
usage: CoachUsage | null;
/**
* v1.18.9 — epoch-ms timestamp the send fired, so the thinking
* disclosure can show a live "thinking for N s" counter that freezes
* to a past-tense summary once the first token lands. Null before a
* turn starts.
*/
startedAt: number | null;
/**
* v1.18.9 — optional reasoning-summary text from the additive
* `reasoning` frame, rendered inside the thinking disclosure when a
* reasoning-capable provider emits it. Empty string when none.
*/
reasoning: string;
}
/**
@@ -253,8 +240,6 @@ const EMPTY_STREAMING: CoachStreamingMessage = {
messageId: null,
errorCode: null,
usage: null,
startedAt: null,
reasoning: "",
};
export interface SendCoachMessageParams {
@@ -349,10 +334,6 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) {
messageId: null,
errorCode: null,
usage: null,
// v1.18.9 — anchor the thinking-disclosure elapsed timer to the
// moment the send fired.
startedAt: Date.now(),
reasoning: "",
});
// v1.4.47 W8 — pre-check navigator.onLine so an airplane-mode
@@ -367,8 +348,6 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) {
messageId: null,
errorCode: "coach.network",
usage: null,
startedAt: null,
reasoning: "",
});
return null;
}
@@ -403,8 +382,6 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) {
messageId: null,
errorCode: "coach.network",
usage: null,
startedAt: null,
reasoning: "",
});
return null;
}
@@ -418,8 +395,6 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) {
messageId: null,
errorCode: `coach.http.${response.status}`,
usage: null,
startedAt: null,
reasoning: "",
});
return null;
}
@@ -458,8 +433,6 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) {
messageId: null,
errorCode: structured ?? `coach.http.${response.status}`,
usage: null,
startedAt: null,
reasoning: "",
});
// v1.16.4 — KEEP the optimistic user bubble: a rejected turn
// (budget gate, 4xx) has no persisted twin coming, and dropping
@@ -479,7 +452,6 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) {
let lastError: string | null = null;
let messageId: string | null = null;
let collectedUsage: CoachUsage | null = null;
let collectedReasoning = "";
try {
while (true) {
@@ -512,16 +484,10 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) {
}));
break;
case "reasoning":
// v1.18.9 — additive reasoning-summary frame. Append so
// multiple chunks accumulate; the disclosure renders it
// when present, else falls back to the elapsed-time label.
// Guard the text: a `reasoning` frame with no `text` must not
// append a literal "undefined".
collectedReasoning += evt.text ?? "";
setStreaming((prev) => ({
...prev,
reasoning: prev.reasoning + (evt.text ?? ""),
}));
// v1.19.1 — the thinking disclosure was removed, so the
// additive `reasoning` frame is accepted off the wire and
// dropped. Kept as an explicit no-op case so the wire frame
// stays a recognised type rather than slipping to a default.
break;
case "done":
resolvedConversationId = evt.conversationId;
@@ -544,8 +510,6 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) {
collectedUsage = evt.usage ?? null;
} else if (evt.type === "error") {
lastError = evt.code;
} else if (evt.type === "reasoning") {
collectedReasoning += evt.text ?? "";
}
}
} catch (err) {
@@ -554,7 +518,7 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) {
}
}
setStreaming((prev) => ({
setStreaming({
content: collectedContent,
metricSource: collectedProvenance,
suggestion: collectedSuggestion,
@@ -562,12 +526,7 @@ export function useSendCoachMessage(opts: UseSendCoachMessageOptions = {}) {
messageId,
errorCode: lastError,
usage: collectedUsage,
// v1.18.9 — keep the send-anchored timestamp so the disclosure can
// freeze the elapsed time to a past-tense "thought for N s" once
// the turn settles.
startedAt: prev.startedAt,
reasoning: collectedReasoning,
}));
});
if (resolvedConversationId) {
// Invalidate the freshly-persisted conversation + the rail so
+13 -6
View File
@@ -75,10 +75,14 @@ export interface SleepDebtDto {
* Wire DTO for the average sleep per night over the rhythm window.
*
* v1.19.1 — a third peer card next to sleep-debt + chronotype. The mean of the
* canonical per-night asleep totals over the same scorable-night set the debt
* and chronotype read, so the three cards can never contradict one another.
* Carries the same calm `partial` state under a minimum-night threshold so it
* never asserts an average off one or two thin nights.
* canonical per-night asleep totals over the full scorable span (up to
* `DEFAULT_WINDOW_DAYS`). Each of the three cards reads its OWN sub-window —
* sleep-debt caps to 14 nights, chronotype to `CHRONOTYPE_WINDOW_NIGHTS`, this
* average over every scorable night — so the figures are each internally honest
* rather than identical; the card caption discloses its `nightsCounted` so the
* reader sees exactly which span produced the number. Carries the same calm
* `partial` state under a minimum-night threshold so it never asserts an average
* off one or two thin nights.
*/
export interface AverageSleepDto {
state: "partial" | "ready";
@@ -261,8 +265,11 @@ export function computeSleepRhythmFromNights(
}));
const chronotype = computeChronotype(chronoNights);
// Average sleep per night over the same scorable set the debt + chronotype
// read, so the three peer cards share one window and can't disagree.
// Average sleep per night over the full scorable span. Each of the three peer
// cards reads its OWN window — debt slices its trailing 14 nights, chronotype
// its trailing CHRONOTYPE_WINDOW_NIGHTS, this average every scorable night —
// so the figures are each internally honest rather than identical; each card's
// caption shows its own nightsCounted so the reader sees the span behind it.
const averagePerNight = computeAverageSleep(scorable);
return {