mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 22:32:04 +00:00
fix(measurements,coach): restore the live-fallback spread band and the stream lifecycle
The tiered rollup read emits minValue/maxValue; readLiveDaily omitted them, so on a rollup-cold account or a coverage miss the chart silently dropped the min/max spread band for the same underlying data — contradicting the file's own byte-for-byte parity claim. The live aggregate now selects MIN/MAX alongside the average. useSendCoachMessage never aborted its in-flight reader on unmount. The drawer works around that through handleOpenChange, but the full-page /coach surface passes no registerReset, so navigating away mid-stream left the SSE loop alive: the provider kept generating and billing, and trailing setState fired on an unmounted component. Switching conversations reset every piece of local state except the send state, so the previous thread's last assistant or error bubble rendered appended to the newly selected thread until the next send. The two lifecycle invariants are pinned structurally, not behaviourally — the repo ships no renderHook. That is weaker, and the header says so; both defects were a missing call, which is what a structural guard does catch.
This commit is contained in:
1 parent
9d03fe13f0
commit
d103699099
5 files changed
+145
-2
No files matched your search
@@ -175,6 +175,8 @@ describe("readDailySeries — rollup read throw falls back to live SQL (F-DB-2)"
|
||||
bucket_start: new Date("2026-06-10T00:00:00.000Z"),
|
||||
avg: 80.5,
|
||||
cnt: 2,
|
||||
min_value: 79.8,
|
||||
max_value: 81.2,
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -189,6 +191,10 @@ describe("readDailySeries — rollup read throw falls back to live SQL (F-DB-2)"
|
||||
// The read did NOT throw — it fell through to live SQL and served a row.
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toMatchObject({ type: "WEIGHT", value: 80.5, count: 2 });
|
||||
// The live fallback must carry the min/max spread the rollup path emits —
|
||||
// otherwise a rollup-cold account silently loses the chart's spread band
|
||||
// for the same underlying data.
|
||||
expect(result[0]).toMatchObject({ minValue: 79.8, maxValue: 81.2 });
|
||||
expect(mocks.queryRaw).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
|
||||
@@ -253,7 +253,14 @@ async function readLiveDaily(opts: {
|
||||
buildSourceRankCase(priorityJson, 'm."type"', 'm."source"'),
|
||||
);
|
||||
const buckets = await prisma.$queryRaw<
|
||||
Array<{ type: string; bucket_start: Date; avg: number; cnt: number }>
|
||||
Array<{
|
||||
type: string;
|
||||
bucket_start: Date;
|
||||
avg: number;
|
||||
cnt: number;
|
||||
min_value: number | null;
|
||||
max_value: number | null;
|
||||
}>
|
||||
>`
|
||||
WITH canon AS (
|
||||
SELECT DISTINCT ON (m."type", date_trunc(${truncUnitLiteral}, m."measured_at"))
|
||||
@@ -272,7 +279,9 @@ async function readLiveDaily(opts: {
|
||||
m."type"::text AS type,
|
||||
date_trunc(${truncUnitLiteral}, m."measured_at") AS bucket_start,
|
||||
${aggregator} AS avg,
|
||||
COUNT(*)::int AS cnt
|
||||
COUNT(*)::int AS cnt,
|
||||
MIN(m."value") AS min_value,
|
||||
MAX(m."value") AS max_value
|
||||
FROM measurements m
|
||||
JOIN canon c
|
||||
ON c.t = m."type"
|
||||
@@ -296,5 +305,15 @@ async function readLiveDaily(opts: {
|
||||
value: Number(b.avg),
|
||||
measuredAt: b.bucket_start.toISOString(),
|
||||
count: Number(b.cnt),
|
||||
// The rollup path emits these; the live fallback used to omit them, so a
|
||||
// rollup-cold account silently lost the min/max spread band on the chart
|
||||
// for the same underlying data — contradicting this file's own
|
||||
// byte-for-byte parity claim.
|
||||
minValue: b.min_value === null ? undefined : Number(b.min_value),
|
||||
maxValue: b.max_value === null ? undefined : Number(b.max_value),
|
||||
// The rollup path emits these; the live fallback used to omit them, so a
|
||||
// rollup-cold account silently lost the min/max spread band on the chart
|
||||
// for the same underlying data — contradicting this file's own
|
||||
// byte-for-byte parity claim.
|
||||
}));
|
||||
}
|
||||
Reference in new issue
Block a user