mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
Settle the suite-wide fallout of the wave
- 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.<module>.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.
This commit is contained in:
1 parent
4eeaec74aa
commit
d2ff7cb769
14 files changed
+61
-92
No files matched your search
@@ -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(
|
||||
() =>
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -92,7 +92,7 @@ describe("<RecentWorkoutsTile>", () => {
|
||||
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("<RecentWorkoutsTile>", () => {
|
||||
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", () => {
|
||||
|
||||
@@ -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.<module>.*` (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 (
|
||||
<section aria-labelledby={titleId} className="space-y-6">
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<h1 id={titleId} className="text-2xl font-bold tracking-tight">
|
||||
{t(`settings.sections.${slug}.title`)}
|
||||
{t(titleKey)}
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{t(`settings.sections.${slug}.subtitle`)}
|
||||
</p>
|
||||
<p className="text-muted-foreground text-sm">{t(subtitleKey)}</p>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -128,6 +128,8 @@ export const JOB_FACT_ALLOWLIST: ReadonlySet<string> = 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<string> = 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",
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user