From d2ff7cb7695151fea8f4cf9e02e740b54d000a5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 14 Aug 2026 06:37:30 +0200 Subject: [PATCH] Settle the suite-wide fallout of the wave MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The record-settings contract, slug-list, and section-frame tests follow the slugs that left the registry (layout children resolve their headings from the layout namespace, like production). - The orphaned settings.sections..title/description keys leave all six locales; the reverse-coverage guard flagged them the moment their nav entries stopped existing. - The operator-cap ledger read fails open on a read error — the reservation layer still meters against the same ledger, and a transient blip must not take the fallback chain down. - The reaper queue joins the subsystem surface; its healed count and the sweep's nag_dismissed join the job-fact vocabulary. - The intake-event contract fixture carries the full row the resource now declares; the workouts empty-state test asserts the source-neutral cue; the persister poll gates on rendered content first. --- e2e/account-sharing.spec.ts | 5 ++++ messages/de.json | 14 ---------- messages/en.json | 14 ---------- messages/es.json | 14 ---------- messages/fr.json | 14 ---------- messages/it.json | 14 ---------- messages/pl.json | 14 ---------- .../record-settings-contract.test.ts | 7 ++++- .../__tests__/recent-workouts-tile.test.tsx | 7 +++-- .../__tests__/section-frame-harness.tsx | 28 ++++++++++++++++--- src/lib/ai/provider-runner.ts | 5 +++- src/lib/jobs/job-outcome.ts | 4 +++ src/lib/jobs/subsystem-surface.ts | 2 ++ .../medication-auth-contract-drift.test.ts | 11 ++++++++ 14 files changed, 61 insertions(+), 92 deletions(-) diff --git a/e2e/account-sharing.spec.ts b/e2e/account-sharing.spec.ts index e2450b92f..2b4453ddc 100644 --- a/e2e/account-sharing.spec.ts +++ b/e2e/account-sharing.spec.ts @@ -368,6 +368,11 @@ test.describe("account sharing", () => { // condition IS the flush, so a slow CI box waits exactly as long as it // needs to and a fast one moves on immediately. await page.goto("/"); + // Gate on rendered content first (the dashboard must actually mount and + // populate the query cache before there is anything to persist). + await expect(page.locator('[data-slot="coach-fab"]')).toBeVisible({ + timeout: 10_000, + }); await expect .poll( () => diff --git a/messages/de.json b/messages/de.json index 61ab350c6..e9b6b1877 100644 --- a/messages/de.json +++ b/messages/de.json @@ -4841,8 +4841,6 @@ } }, "dashboard": { - "title": "Dashboard", - "description": "Kachel-Layout und Reihenfolge.", "subtitle": "Ordne die Kacheln auf deinem Dashboard an." }, "thresholds": { @@ -5152,33 +5150,21 @@ "subtitle": "Teile schreibgeschützten Zugriff auf deine Daten." }, "insights": { - "title": "Insights", - "description": "Reihenfolge der Übersicht und der Navigations-Chips.", "subtitle": "Ordne die Übersichts-Bereiche und die Reihenfolge der Chips an." }, "medications": { - "title": "Medikamente", - "description": "Listenansicht, Reihenfolge und Injektionsstellen deiner Medikamente.", "subtitle": "Verwalte Ansicht und Reihenfolge deiner Medikamentenliste." }, "mood": { - "title": "Stimmungs-Tags", - "description": "Gruppen, Tags und archivierte Tags der Stimmungs-Auswahl.", "subtitle": "Organisiere deine Stimmungs-Tags, Gruppen und Auswahlreihenfolge." }, "labs": { - "title": "Laborwerte", - "description": "Ansicht, Sortierung und Biomarker deiner Laborwerte.", "subtitle": "Ansicht, Sortierung und Biomarker verwalten." }, "illness": { - "title": "Krankheitstagebuch", - "description": "Ansicht und Reihenfolge deiner Krankheitsepisoden.", "subtitle": "Ansicht und Reihenfolge der Episoden verwalten." }, "vorsorge": { - "title": "Vorsorge", - "description": "Ansicht, Reihenfolge und einzelne Vorsorge-Erinnerungen.", "subtitle": "Ansicht, Reihenfolge und einzelne Erinnerungen verwalten." }, "modules": { diff --git a/messages/en.json b/messages/en.json index 53dcb2ace..ce24821f3 100644 --- a/messages/en.json +++ b/messages/en.json @@ -4841,8 +4841,6 @@ } }, "dashboard": { - "title": "Dashboard", - "description": "Tile layout and order.", "subtitle": "Arrange the tiles on your dashboard." }, "thresholds": { @@ -5152,33 +5150,21 @@ "subtitle": "Share read-only access to your data." }, "insights": { - "title": "Insights", - "description": "Overview and navigation-chip order.", "subtitle": "Arrange your overview sections and chip order." }, "medications": { - "title": "Medications", - "description": "List view, order, and injection sites for your medications.", "subtitle": "Manage your medication list view and order." }, "mood": { - "title": "Mood tags", - "description": "Groups, tags, and archived tags for the mood picker.", "subtitle": "Organize your mood tags, groups, and picker order." }, "labs": { - "title": "Labs", - "description": "View, sort order, and biomarkers for your labs.", "subtitle": "Manage the view, sort order, and biomarkers." }, "illness": { - "title": "Illness journal", - "description": "View and order of your illness episodes.", "subtitle": "Manage the view and order of episodes." }, "vorsorge": { - "title": "Checkups", - "description": "View, order, and individual checkup reminders.", "subtitle": "Manage the view, order, and individual reminders." }, "modules": { diff --git a/messages/es.json b/messages/es.json index 1ec276653..c84064132 100644 --- a/messages/es.json +++ b/messages/es.json @@ -4841,8 +4841,6 @@ } }, "dashboard": { - "title": "Panel", - "description": "Diseño y orden de los mosaicos.", "subtitle": "Organiza los mosaicos de tu panel." }, "thresholds": { @@ -5152,33 +5150,21 @@ "subtitle": "Comparte acceso de solo lectura a tus datos." }, "insights": { - "title": "Análisis", - "description": "Orden de la vista general y de las pestañas de navegación.", "subtitle": "Organiza las secciones de tu resumen y el orden de las pestañas." }, "medications": { - "title": "Medicamentos", - "description": "Vista de lista, orden y zonas de inyección de tus medicamentos.", "subtitle": "Gestiona la vista y el orden de tu lista de medicamentos." }, "mood": { - "title": "Etiquetas de ánimo", - "description": "Grupos, etiquetas y etiquetas archivadas del selector de ánimo.", "subtitle": "Organiza tus etiquetas de ánimo, grupos y orden del selector." }, "labs": { - "title": "Laboratorio", - "description": "Vista, orden y biomarcadores de tu laboratorio.", "subtitle": "Gestiona la vista, el orden y los biomarcadores." }, "illness": { - "title": "Diario de enfermedad", - "description": "Vista y orden de tus episodios de enfermedad.", "subtitle": "Gestiona la vista y el orden de los episodios." }, "vorsorge": { - "title": "Revisiones", - "description": "Vista, orden y recordatorios de revisión individuales.", "subtitle": "Gestiona la vista, el orden y los recordatorios individuales." }, "modules": { diff --git a/messages/fr.json b/messages/fr.json index c98f3feff..8243162db 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -4841,8 +4841,6 @@ } }, "dashboard": { - "title": "Tableau de bord", - "description": "Disposition et ordre des tuiles.", "subtitle": "Organisez les tuiles de votre tableau de bord." }, "thresholds": { @@ -5152,33 +5150,21 @@ "subtitle": "Partagez un accès en lecture seule à vos données." }, "insights": { - "title": "Analyses", - "description": "Ordre de la vue d'ensemble et des onglets de navigation.", "subtitle": "Organisez les sections d'aperçu et l'ordre des onglets." }, "medications": { - "title": "Médicaments", - "description": "Vue liste, ordre et sites d’injection de vos médicaments.", "subtitle": "Gérez l'affichage et l'ordre de votre liste de médicaments." }, "mood": { - "title": "Étiquettes d’humeur", - "description": "Groupes, étiquettes et étiquettes archivées du sélecteur d’humeur.", "subtitle": "Organisez vos tags d'humeur, groupes et ordre du sélecteur." }, "labs": { - "title": "Analyses", - "description": "Affichage, tri et biomarqueurs de vos analyses.", "subtitle": "Gérez l’affichage, le tri et les biomarqueurs." }, "illness": { - "title": "Journal de maladie", - "description": "Affichage et ordre de vos épisodes de maladie.", "subtitle": "Gérez l’affichage et l’ordre des épisodes." }, "vorsorge": { - "title": "Examens", - "description": "Affichage, ordre et rappels d’examen individuels.", "subtitle": "Gérez l’affichage, l’ordre et les rappels individuels." }, "modules": { diff --git a/messages/it.json b/messages/it.json index 85ee1f7be..b4c8313c9 100644 --- a/messages/it.json +++ b/messages/it.json @@ -4841,8 +4841,6 @@ } }, "dashboard": { - "title": "Cruscotto", - "description": "Layout e ordine dei riquadri.", "subtitle": "Disponi i riquadri della tua dashboard." }, "thresholds": { @@ -5152,33 +5150,21 @@ "subtitle": "Condividi l'accesso in sola lettura ai tuoi dati." }, "insights": { - "title": "Approfondimenti", - "description": "Ordine della panoramica e delle schede di navigazione.", "subtitle": "Disponi le sezioni della panoramica e l'ordine delle schede." }, "medications": { - "title": "Farmaci", - "description": "Vista elenco, ordine e siti di iniezione dei tuoi farmaci.", "subtitle": "Gestisci vista e ordine del tuo elenco di farmaci." }, "mood": { - "title": "Tag dell’umore", - "description": "Gruppi, tag e tag archiviati del selettore dell’umore.", "subtitle": "Organizza i tag dell'umore, i gruppi e l'ordine del selettore." }, "labs": { - "title": "Esami", - "description": "Vista, ordinamento e biomarcatori dei tuoi esami.", "subtitle": "Gestisci la vista, l’ordinamento e i biomarcatori." }, "illness": { - "title": "Diario delle malattie", - "description": "Vista e ordine dei tuoi episodi di malattia.", "subtitle": "Gestisci la vista e l’ordine degli episodi." }, "vorsorge": { - "title": "Controlli", - "description": "Vista, ordine e singoli promemoria di controllo.", "subtitle": "Gestisci la vista, l’ordine e i singoli promemoria." }, "modules": { diff --git a/messages/pl.json b/messages/pl.json index 58d4c731c..c250f3c8d 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -4841,8 +4841,6 @@ } }, "dashboard": { - "title": "Pulpit", - "description": "Układ i kolejność kafelków.", "subtitle": "Rozmieść kafelki na pulpicie." }, "thresholds": { @@ -5152,33 +5150,21 @@ "subtitle": "Udostępnij dostęp tylko do odczytu do swoich danych." }, "insights": { - "title": "Analizy", - "description": "Kolejność przeglądu i chipów nawigacyjnych.", "subtitle": "Rozmieść sekcje przeglądu i kolejność chipów." }, "medications": { - "title": "Leki", - "description": "Widok listy, kolejność i miejsca wstrzyknięć Twoich leków.", "subtitle": "Zarządzaj widokiem i kolejnością listy leków." }, "mood": { - "title": "Tagi nastroju", - "description": "Grupy, tagi i zarchiwizowane tagi w wyborze nastroju.", "subtitle": "Uporządkuj tagi nastroju, grupy i kolejność wyboru." }, "labs": { - "title": "Wyniki badań", - "description": "Widok, sortowanie i biomarkery Twoich wyników badań.", "subtitle": "Zarządzaj widokiem, sortowaniem i biomarkerami." }, "illness": { - "title": "Dziennik chorób", - "description": "Widok i kolejność Twoich epizodów chorobowych.", "subtitle": "Zarządzaj widokiem i kolejnością epizodów." }, "vorsorge": { - "title": "Badania", - "description": "Widok, kolejność i poszczególne przypomnienia o badaniach.", "subtitle": "Zarządzaj widokiem, kolejnością i poszczególnymi przypomnieniami." }, "modules": { diff --git a/src/__tests__/record-settings-contract.test.ts b/src/__tests__/record-settings-contract.test.ts index 0e1c2b3d0..d1af128c7 100644 --- a/src/__tests__/record-settings-contract.test.ts +++ b/src/__tests__/record-settings-contract.test.ts @@ -67,7 +67,12 @@ describe("record settings contract", () => { guardianWritable: false, }); expect(classifySettingsDestination("ai").kind).toBe("unavailable"); - expect(classifySettingsDestination("dashboard").kind).toBe( + // v1.37.19 — "dashboard" left SETTINGS_SECTION_SLUGS (its URL is + // owned by a permanent redirect to /settings/layout/dashboard), so an + // unknown destination falls closed to plain unavailable; "layout" is + // the surviving adult-shared-unavailable representative. + expect(classifySettingsDestination("dashboard").kind).toBe("unavailable"); + expect(classifySettingsDestination("layout").kind).toBe( "adult-shared-unavailable", ); expect(isGuardianSettingsWriteAllowed("account")).toBe(true); diff --git a/src/components/dashboard/__tests__/recent-workouts-tile.test.tsx b/src/components/dashboard/__tests__/recent-workouts-tile.test.tsx index cfea873bc..e57a6e70f 100644 --- a/src/components/dashboard/__tests__/recent-workouts-tile.test.tsx +++ b/src/components/dashboard/__tests__/recent-workouts-tile.test.tsx @@ -92,7 +92,7 @@ describe("", () => { expect(html).not.toContain('data-slot="recent-workouts-empty"'); }); - it("renders the empty-state with the Apple-Health onboarding cue", () => { + it("renders the empty-state with the source-neutral onboarding cue", () => { mockResult.data = { workouts: [], meta: { total: 0, limit: 3, offset: 0, droppedDuplicates: 0 }, @@ -100,7 +100,10 @@ describe("", () => { const html = render(); expect(html).toContain('data-slot="recent-workouts-empty"'); expect(html).toContain("No workouts yet"); - expect(html).toContain("Apple Health"); + // v1.37.19 (A1-7) — the CTA no longer assumes an iPhone with Apple + // Health; WHOOP/Polar/Oura/Withings users get the same honest cue. + expect(html).toContain("Connect a data source in Settings"); + expect(html).not.toContain("Apple Health"); }); it("renders the populated list with deep-links into each detail page", () => { diff --git a/src/components/settings/__tests__/section-frame-harness.tsx b/src/components/settings/__tests__/section-frame-harness.tsx index c01640773..24bbf656f 100644 --- a/src/components/settings/__tests__/section-frame-harness.tsx +++ b/src/components/settings/__tests__/section-frame-harness.tsx @@ -16,6 +16,21 @@ import * as React from "react"; import { useTranslations } from "@/lib/i18n/context"; +/** + * v1.37.19 — the layout-hub children left `SETTINGS_SECTION_SLUGS`; their + * live headings resolve from `settings.sections.layout..*` (the + * layout-groups registry), so the harness mirrors that mapping. + */ +const LAYOUT_CHILD_SLUGS = new Set([ + "dashboard", + "insights", + "medications", + "mood", + "labs", + "illness", + "vorsorge", +]); + export function SettingsSectionFrame({ slug, children, @@ -30,17 +45,22 @@ export function SettingsSectionFrame({ }) { const { t } = useTranslations(); const titleId = `settings-section-${slug}-title`; + const isLayoutChild = LAYOUT_CHILD_SLUGS.has(slug); + const titleKey = isLayoutChild + ? `settings.sections.layout.${slug}.title` + : `settings.sections.${slug}.title`; + const subtitleKey = isLayoutChild + ? `settings.sections.layout.${slug}.description` + : `settings.sections.${slug}.subtitle`; return (

- {t(`settings.sections.${slug}.title`)} + {t(titleKey)}

-

- {t(`settings.sections.${slug}.subtitle`)} -

+

{t(subtitleKey)}

{children} diff --git a/src/lib/ai/provider-runner.ts b/src/lib/ai/provider-runner.ts index b54109a74..823e380f1 100644 --- a/src/lib/ai/provider-runner.ts +++ b/src/lib/ai/provider-runner.ts @@ -409,7 +409,10 @@ async function runRawChain( // candidate run; an exhausted cap skips the candidate exactly like a // failed hop (next entry, or AllProvidersFailedError at the end). if (isOperatorFundedProvider(candidate.providerType)) { - const spent = await readDailySpend(userId); + // Fail OPEN on a ledger read error: the reservation layer still + // meters every call against the same ledger, so a transient read + // failure here must not take the whole fallback chain down with it. + const spent = await readDailySpend(userId).catch(() => 0); if (spent >= OPERATOR_COST_CAP) { const hop: FallbackHop = { providerType: candidate.providerType, diff --git a/src/lib/jobs/job-outcome.ts b/src/lib/jobs/job-outcome.ts index c7b7cf87b..1e8785ceb 100644 --- a/src/lib/jobs/job-outcome.ts +++ b/src/lib/jobs/job-outcome.ts @@ -128,6 +128,8 @@ export const JOB_FACT_ALLOWLIST: ReadonlySet = new Set([ "module_off", "mood_entries_migrated", "mood_pruned", + // v1.37.19 (A3-6) — ignored Coach reminders auto-dismissed at the nag cap. + "nag_dismissed", "notifications_dispatched", "notifications_failed", "notified", @@ -195,6 +197,8 @@ export const JOB_FACT_ALLOWLIST: ReadonlySet = new Set([ "stored", "subscription_repair_failed", "summaries_enqueued", + // v1.37.19 (A7-4) — stale-PENDING summary rows the hourly reaper healed. + "summary_pending_healed", "suppressed_client_managed", "suppressed_discreet", "thumbnails_enqueued", diff --git a/src/lib/jobs/subsystem-surface.ts b/src/lib/jobs/subsystem-surface.ts index e04088d14..cd9802d8e 100644 --- a/src/lib/jobs/subsystem-surface.ts +++ b/src/lib/jobs/subsystem-surface.ts @@ -127,6 +127,8 @@ const subsystemSurface = { "mcp-token-cleanup": { audience: "system" }, "med-notes-encryption-backfill": { audience: "system" }, "document-tombstone-purge": { audience: "system" }, + // v1.37.19 (A7-4) — hourly heal of summary rows stuck on PENDING. + "document-summary-reaper": { audience: "system" }, "document-content-index-backfill": { audience: "account" }, "document-index": { audience: "account" }, "document-thumbnail": { audience: "account" }, diff --git a/src/lib/openapi/__tests__/medication-auth-contract-drift.test.ts b/src/lib/openapi/__tests__/medication-auth-contract-drift.test.ts index f79981921..b6c4889b0 100644 --- a/src/lib/openapi/__tests__/medication-auth-contract-drift.test.ts +++ b/src/lib/openapi/__tests__/medication-auth-contract-drift.test.ts @@ -91,6 +91,8 @@ describe("medications — documented shapes match the handlers", () => { }); it("admits APPLE_HEALTH as an intake source (IntakeSource enum, v1.28)", () => { + // v1.37.19 (A4-8) — the resource declares the FULL row both intake + // responses return, so the fixture carries every field. const event = { id: "cm000000000000000000000", userId: "cm111111111111111111111", @@ -98,9 +100,18 @@ describe("medications — documented shapes match the handlers", () => { scheduledFor: "2026-07-19T08:00:00.000Z", takenAt: "2026-07-19T08:05:00.000Z", skipped: false, + autoMissed: false, + attributionSource: "AUTO", source: "APPLE_HEALTH", idempotencyKey: null, createdAt: "2026-07-19T08:05:00.000Z", + updatedAt: "2026-07-19T08:05:00.000Z", + injectionSite: null, + doseTaken: null, + inventoryConsumption: null, + externalId: "hk-intake-1", + syncVersion: 1, + deletedAt: null, }; const parsed = medicationIntakeEventResource.safeParse(event); expect(parsed.success, JSON.stringify(parsed.error?.issues)).toBe(true);