From a20aa845d9c8a9e50ecadf1316773e5fbe75107c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 5 Jul 2026 14:59:05 +0200 Subject: [PATCH 1/2] fix(ai): ask for blank-line paragraphs in the assessment and briefing output formats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared formatting contract asks every narrative surface for short blank-line paragraphs, but two field-level instructions overrode it: the assessment OUTPUT FORMAT pinned 'flowing prose (2-4 sentences)' and the briefing paragraph asked for 'a connected 70-160 word read' — so both consistently arrived as one unbroken block regardless of the contract. Both now spell out 1-3 short paragraphs separated by a blank line written as \n\n inside the JSON string. --- src/lib/ai/prompts/base-system.ts | 4 ++-- src/lib/ai/prompts/insight-generator.ts | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib/ai/prompts/base-system.ts b/src/lib/ai/prompts/base-system.ts index 6804b4f44..d3b78e88c 100644 --- a/src/lib/ai/prompts/base-system.ts +++ b/src/lib/ai/prompts/base-system.ts @@ -219,9 +219,9 @@ Synthese statt Aufzählung: die Geschichte dessen, was die Daten bedeuten, zähl }, { id: "output", - en: `OUTPUT FORMAT: Reply with valid JSON only, in exactly this schema. The "summary" field holds the complete assessment as English flowing prose (2-4 sentences): + en: `OUTPUT FORMAT: Reply with valid JSON only, in exactly this schema. The "summary" field holds the complete assessment in English: 1-3 short paragraphs of 1-3 sentences each, separated by a blank line written as \\n\\n INSIDE the JSON string. A steady one-liner stays a single paragraph: { "summary": "..." }`, - de: `AUSGABEFORMAT: Antworte ausschließlich mit validem JSON in genau diesem Schema. Das Feld "summary" enthält die komplette Einschätzung als deutscher Fließtext (2-4 Sätze): + de: `AUSGABEFORMAT: Antworte ausschließlich mit validem JSON in genau diesem Schema. Das Feld "summary" enthält die komplette Einschätzung auf Deutsch: 1-3 kurze Absätze mit je 1-3 Sätzen, getrennt durch eine Leerzeile als \\n\\n INNERHALB des JSON-Strings. Ein stabiler Einzeiler bleibt EIN Absatz: { "summary": "..." }`, }, ]; diff --git a/src/lib/ai/prompts/insight-generator.ts b/src/lib/ai/prompts/insight-generator.ts index 84159c2f3..8ffd561e8 100644 --- a/src/lib/ai/prompts/insight-generator.ts +++ b/src/lib/ai/prompts/insight-generator.ts @@ -259,8 +259,10 @@ gib die obige Verweigerung zurück.`, warm, and motivating. Lead with NOW; draw on history for context, but the user opens this to learn what is happening TODAY and what one thing they can do about it. Fields: - - paragraph: a connected 70-160 word read of TODAY the user sees - at the top of /insights. SENTENCE 1 is the day's VERDICT in plain + - paragraph: a 70-160 word read of TODAY the user sees at the top + of /insights, written as 2-3 SHORT PARAGRAPHS separated by a + blank line (\\n\\n inside the JSON string) — never one unbroken + block. SENTENCE 1 is the day's VERDICT in plain words — the overall picture, NOT a number ("Today reads like a recovery day", "A steady day, nothing demanding your attention"). Then weave the 2-3 most salient signals into ONE story: lead from @@ -452,8 +454,10 @@ gib die obige Verweigerung zurück.`, JETZT; nutze die Historie als Kontext, aber der Nutzer öffnet das, um zu erfahren, was HEUTE passiert und welche eine Sache er dagegen tun kann. Felder: - - paragraph: ein zusammenhängender 70-160 Wörter langer Tages-Read, - den der Nutzer oben auf /insights liest. SATZ 1 ist das URTEIL des + - paragraph: ein 70-160 Wörter langer Tages-Read, den der Nutzer + oben auf /insights liest — geschrieben als 2-3 KURZE ABSÄTZE, + getrennt durch eine Leerzeile (\\n\\n innerhalb des JSON-Strings), + nie als ein durchgehender Block. SATZ 1 ist das URTEIL des Tages in klaren Worten — das Gesamtbild, KEINE Zahl ("Heute liest sich wie ein Erholungstag", "Ein ruhiger Tag, nichts, das deine Aufmerksamkeit braucht"). Dann verwebe die 2-3 wichtigsten Signale From ba17ca34eb573241b58d1b6fb9880b05e162b3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 5 Jul 2026 15:03:22 +0200 Subject: [PATCH 2/2] feat(insights): open the briefing card on the day's signals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The recall/forward-look block read as a second briefing paragraph above the structured list and pulled the card down — the card now starts straight at 'signals of the day'. The section titles read in the regular foreground, and the card's Ask-the-Coach corner entry is gone; the Coach keeps its own surfaces. --- src/app/insights/page.tsx | 17 +++---- src/components/insights/daily-briefing.tsx | 58 +++------------------- 2 files changed, 13 insertions(+), 62 deletions(-) diff --git a/src/app/insights/page.tsx b/src/app/insights/page.tsx index b59cf7ee8..66ef264ed 100644 --- a/src/app/insights/page.tsx +++ b/src/app/insights/page.tsx @@ -303,19 +303,18 @@ export default function InsightsPage() { const analyticsQuery = useAnalyticsQuery(); const analytics = analyticsQuery.data as AnalyticsData | undefined; - // v1.21.2 (A4 / A5 / A6) — the dashboard snapshot is the single server source - // for the ambient narrative the hero + briefing surface: the Tension Verdict - // and return-to-baseline ride its `healthScore`, the briefing recall + - // forward-look rides `briefingMemory`. The hero score number itself stays on - // the analytics payload (unchanged); only these three additive, server- - // resolved DTO fields are lifted from the snapshot. Reads the same shared + // v1.21.2 (A5 / A6) — the dashboard snapshot is the single server source + // for the ambient narrative the hero surfaces: the Tension Verdict and + // return-to-baseline ride its `healthScore`. The hero score number itself + // stays on the analytics payload (unchanged). Reads the same shared // `queryKeys.dashboardSnapshot()` cell the dashboard warms, so the insights // visit pays at most one extra round-trip and usually a warm cache hit. + // (The A4 recall/forward-look block left the briefing card — the card now + // opens straight on the day's signals.) const snapshotQuery = useDashboardSnapshot(isAuthenticated); const snapshotHealthScore = snapshotQuery.data?.healthScore ?? null; const heroTension = snapshotHealthScore?.tension ?? null; const heroReturnToBand = snapshotHealthScore?.returnToBand ?? null; - const briefingMemory = snapshotQuery.data?.briefingMemory ?? null; // v1.12.6 — the page owns the ONE overview derived batch. The wellness // strip (above the briefing) and the vitals grid (below it) both read this @@ -483,10 +482,6 @@ export default function InsightsPage() { // v1.25.3 — failure class points the empty-state hint at the right // lever (raise the response timeout vs re-check the provider). generationFailureClass={advisor.generationFailureClass} - // v1.21.2 (A4) — server-resolved recall + forward-look off the dashboard - // snapshot. Both strings are already localised; null leaves the card's - // memory block unrendered. - memory={briefingMemory} /> ) : null, vitals: , diff --git a/src/components/insights/daily-briefing.tsx b/src/components/insights/daily-briefing.tsx index 309da0fc7..d7fbbdd46 100644 --- a/src/components/insights/daily-briefing.tsx +++ b/src/components/insights/daily-briefing.tsx @@ -25,7 +25,6 @@ import { EmptyState } from "@/components/ui/empty-state"; import { ListRow } from "@/components/ui/list-row"; import { Skeleton } from "@/components/ui/skeleton"; import { SectionHeading } from "@/components/insights/section-heading"; -import { AskCoachAction } from "@/components/insights/ask-coach-action"; import { useTranslations, useFormatters } from "@/lib/i18n/context"; import { formatUpdatedLabel } from "@/lib/i18n/relative-time"; import { stripChartTokens } from "@/lib/insights/chart-tokens"; @@ -140,20 +139,6 @@ interface DailyBriefingProps { * comparison toggle migrates here from the hero in commit 5. */ metaSlot?: React.ReactNode; - /** - * v1.21.2 (A4) — server-resolved callback + forward-look. The briefing - * recalls the prior period and points ahead ("as I noted Monday, your sleep - * was short; it's recovered since — worth watching tonight"). Both strings - * are already localised and figure-anchored by the resolver; the card only - * renders them. One callback per surface — absent (the common case) when no - * prior narrative recall is on file. - */ - memory?: { - /** The recall of the prior-period read, anchored to a figure. */ - recall: string; - /** The forward-look pointing ahead. */ - forward: string; - } | null; } const METRIC_ICON: Record< @@ -368,7 +353,6 @@ export function DailyBriefing({ generationFailed = false, generationFailureClass = null, metaSlot, - memory = null, }: DailyBriefingProps) { const { t } = useTranslations(); const fmt = useFormatters(); @@ -424,33 +408,11 @@ export function DailyBriefing({ // looser `space-y-4`, which read as taller than its neighbours on // the overview. One token, no new spacing scale.
- {/* v1.21.2 (A4) — callback + forward-look. Recalls the prior - period and points ahead, both server-resolved and - figure-anchored. Leads the card so the briefing opens by - closing the loop on what it noted last, then looks forward. */} - {memory && ( -
-

- {t("insights.briefing.memory.recall", { - text: memory.recall, - })} -

-

- {t("insights.briefing.memory.forward", { - text: memory.forward, - })} -

-
- )} + {/* The v1.21.2 recall/forward-look block is gone: the card + opens straight on "signals of the day". The narrative + memory read as a second briefing paragraph above the + structured list and pulled the card down; the server + still resolves `briefingMemory` for other consumers. */} {/* v1.4.27 B1 — the leading narrative paragraph dropped. The hero strip subtitle on `/insights` already renders the same `briefing.paragraph` text directly above this @@ -465,7 +427,7 @@ export function DailyBriefing({

{t("insights.dailyBriefing.signalsTitle")}

@@ -483,7 +445,7 @@ export function DailyBriefing({

{t("insights.dailyBriefing.keyFindingsTitle")}

@@ -565,12 +527,6 @@ export function DailyBriefing({ )}
)} - {/* v1.21.0 (C4 H2) — hand off to the Coach for the whole - picture. The briefing spans every metric, so no scope: the - default all-source snapshot reads best here. */} -
- -
) : noProvider ? ( // v1.15.20 — no provider configured anywhere: a regenerate CTA