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:
Marc-André Bombeck committed 2026-08-14 06:37:30 +02:00
1 parent 4eeaec74aa
commit d2ff7cb769
14 files changed
+61 -92

No files matched your search

@@ -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}
+4 -1
View File
@@ -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,
+4
View File
@@ -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",
+2
View File
@@ -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);