feat(daily): react in place when new data lands

Today gains two additive-nullable digest fields. `justIn` names the day's
newest arrival while it is still news — under three hours old, after which the
record is simply current rather than new. `reactionLine` carries a single
generated sentence about that arrival and replaces the hero's standing lead for
the rest of the local day, never appending a second paragraph, so the hero stays
one lead line tall and nothing below it shifts.

Nothing here polls. The open dashboard picks the change up on the cadence it
already runs — a state change, not a notification.

The generated sentence is bounded before it is written: the arrival spine's
unique row per kind per local day IS the throttle, the worker re-checks the
same claim before spending, and the reservation is reconciled on every exit
including each failure path. Without a provider, without consent, or against an
exhausted budget the row stays line-less and the surface falls back to the
deterministic lead — the chip and the provisional-to-final flip are unaffected.

The chip's time string is formatted client-side after mount; `justIn.at`
crosses the wire as an ISO instant so the server never renders a local
wall-clock time the browser would disagree with.
This commit is contained in:
Marc-André Bombeck committed 2026-07-19 13:56:21 +02:00
1 parent f9112925d2
commit ca170925dc
21 files changed
+836 -19

No files matched your search

+22
View File
@@ -11,6 +11,7 @@
import { z } from "zod/v4";
import type { ZodOpenApiObject } from "zod-openapi";
import { ARRIVAL_KINDS } from "@/lib/arrivals/types";
import { PRIORITY_ITEM_KINDS } from "@/lib/daily/priority-item";
import { dismissPriorityItemSchema } from "@/lib/validations/daily";
import { dataEnvelope, moduleDisabledResponse, stdResponses } from "./shared";
@@ -95,6 +96,27 @@ const dailyDigestResponse = z
worthALook: z
.array(priorityItemSchema)
.describe("Bounded 0–3 rail items, never padded."),
justIn: z
.object({
kind: z
.enum([...ARRIVAL_KINDS])
.describe("Closed arrival kind that landed."),
at: z
.string()
.describe(
"ISO-8601 instant of the newest sample. NEVER pre-formatted server-side — the client formats it in its own locale and timezone.",
),
})
.nullable()
.describe(
"The day's newest data arrival while it is still news (under three hours old), else null. Additive since v1.31.0.",
),
reactionLine: z
.string()
.nullable()
.describe(
"One-sentence generated reaction to that arrival, standing for the rest of the local day. Null whenever no line was generated (no provider, no consent, budget exhausted, or generation failed) — consumers fall back to `briefingLead` / `line`. Additive since v1.31.0.",
),
})
.meta({
id: "DailyDigest",