From c86c7d1a790b8c6a326619ae9781b705e7f962bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 04:15:11 +0200 Subject: [PATCH 1/9] =?UTF-8?q?refactor(lint):=20enforce=20the=20lib=20?= =?UTF-8?q?=E2=86=92=20components=20and=20client=20=E2=86=92=20server=20bo?= =?UTF-8?q?undaries?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the in-repo rule `healthlog/import-boundaries`: - a file under src/lib may not import src/components or src/app (alias, relative, re-export and dynamic forms, type-only included); - a "use client" file may not make a value import of the database client, the crypto module, the Prisma client entry, the job queue, pg, pg-boss or next/headers. Type-only imports and the Prisma enums entry stay legal. The four lib files that still import upward are listed in the rule's allow map with a reason each; a listed file that stops importing upward is reported, so the map can only shrink. Five of the nine upward imports are gone by moving pure modules to their layer, without behaviour change: - supply-runway estimate → src/lib/medications/supply-runway.ts (read by the low-stock job and the medication list read); - weekday BYDAY tokens → src/lib/medications/scheduling/weekday-tokens.ts (read by the medication extraction prompt); - settings section slugs → src/lib/settings/section-slugs.ts (read by the record-settings classification); - the command palette index → src/components/command-palette/ (it is a client index built from component registries). Proof the rule can fail: an import of @/components/ui/card appended to src/lib/medications/supply-runway.ts reported libUpward, and a planted "use client" file importing @/lib/db reported clientServerOnly; both were removed again. --- e2e/mobile-route-overflow.spec.ts | 2 +- .../__tests__/import-boundaries.test.mjs | 147 +++++++++++++ eslint-plugins/healthlog/import-boundaries.js | 199 ++++++++++++++++++ eslint-plugins/healthlog/index.js | 4 + eslint.config.mjs | 21 ++ src/__tests__/settings-legacy-routes.test.ts | 2 +- src/app/api/medications/[id]/glp1/route.ts | 2 +- src/app/api/medications/[id]/route.ts | 2 +- .../sleep/__tests__/empty-state-href.test.ts | 2 +- src/app/settings/[section]/page.tsx | 2 +- src/components/admin/section-slugs.ts | 2 +- .../__tests__/build-index.test.ts | 2 +- .../__tests__/command-palette.test.tsx | 2 +- .../command-palette/build-index.ts | 4 +- .../command-palette/command-palette.tsx | 2 +- .../command-palette/use-palette-index.ts | 2 +- .../sleep-overview-empty-href.test.ts | 2 +- .../__tests__/medication-form-v15.test.tsx | 6 +- .../detail/medication-detail-tabs.tsx | 2 +- .../medications/glp1-medication-card.tsx | 2 +- .../medications/medication-card.tsx | 2 +- .../medications/medication-table.tsx | 2 +- .../medications/scheduling/cadence-picker.tsx | 4 +- .../medications/scheduling/legacy-bridge.ts | 4 +- .../medications/scheduling/types.ts | 14 +- .../medications/wizard/nl-bridge.ts | 2 +- .../wizard/steps/step6-sub-cadence.tsx | 4 +- .../__tests__/sections-i18n-parity.test.ts | 2 +- .../settings/__tests__/sections.test.tsx | 2 +- .../settings/record-settings-section-gate.tsx | 2 +- .../settings/settings-section-loader.tsx | 2 +- src/components/settings/settings-shell.tsx | 2 +- src/lib/ai/coach/medication-extract-prompt.ts | 2 +- src/lib/jobs/medication-low-stock.ts | 2 +- .../__tests__/supply-runway.test.ts | 2 +- src/lib/medications/list-read.ts | 2 +- .../medications/scheduling/weekday-tokens.ts | 17 ++ .../medications}/supply-runway.ts | 0 src/lib/record-settings/classification.ts | 2 +- .../settings/section-slugs.ts | 0 40 files changed, 429 insertions(+), 49 deletions(-) create mode 100644 eslint-plugins/healthlog/__tests__/import-boundaries.test.mjs create mode 100644 eslint-plugins/healthlog/import-boundaries.js rename src/{lib => components}/command-palette/__tests__/build-index.test.ts (99%) rename src/{lib => components}/command-palette/build-index.ts (98%) rename src/{components => lib}/medications/__tests__/supply-runway.test.ts (99%) create mode 100644 src/lib/medications/scheduling/weekday-tokens.ts rename src/{components/medications/detail => lib/medications}/supply-runway.ts (100%) rename src/{components => lib}/settings/section-slugs.ts (100%) diff --git a/e2e/mobile-route-overflow.spec.ts b/e2e/mobile-route-overflow.spec.ts index a0d0397b8..127ad3a3c 100644 --- a/e2e/mobile-route-overflow.spec.ts +++ b/e2e/mobile-route-overflow.spec.ts @@ -5,7 +5,7 @@ import type { APIRequestContext } from "@playwright/test"; import { ADMIN_SECTION_SLUGS } from "@/components/admin/section-slugs"; import { MODULE_SETTINGS_PAGE_IDS } from "@/components/settings/module-settings-page-ids"; -import { SETTINGS_SECTION_SLUGS } from "@/components/settings/section-slugs"; +import { SETTINGS_SECTION_SLUGS } from "@/lib/settings/section-slugs"; import { expect, test } from "./setup/test"; import { MOBILE_ROUTES_STORAGE_STATE_PATH } from "./setup/global-setup"; diff --git a/eslint-plugins/healthlog/__tests__/import-boundaries.test.mjs b/eslint-plugins/healthlog/__tests__/import-boundaries.test.mjs new file mode 100644 index 000000000..992691be5 --- /dev/null +++ b/eslint-plugins/healthlog/__tests__/import-boundaries.test.mjs @@ -0,0 +1,147 @@ +/** + * Unit tests for the `healthlog/import-boundaries` rule: src/lib never + * imports components or app, and a "use client" file never makes a value + * import of a server-only module. Covers alias and relative specifiers, + * type-only imports, dynamic imports, re-exports, the allow map and its + * stale-entry report. + */ + +import { describe, it } from "vitest"; +import { RuleTester } from "eslint"; +import nextTs from "eslint-config-next/typescript"; +import rule from "../import-boundaries.js"; + +// The TypeScript parser the app lints with, taken from the same config so +// `import type` reaches the rule as it does in `pnpm lint`. +const tsParser = nextTs.find((c) => c.languageOptions?.parser).languageOptions + .parser; + +RuleTester.describe = describe; +RuleTester.it = it; +RuleTester.itOnly = it.only; + +const ruleTester = new RuleTester({ + languageOptions: { + parser: tsParser, + ecmaVersion: 2023, + sourceType: "module", + parserOptions: { ecmaFeatures: { jsx: true } }, + }, +}); + +const LIB_FILE = "/repo/src/lib/medications/example.ts"; +const CLIENT_FILE = "/repo/src/components/medications/example.tsx"; + +ruleTester.run("import-boundaries", rule, { + valid: [ + // lib importing lib, a package, and itself relatively. + { code: 'import { a } from "@/lib/x";', filename: LIB_FILE }, + { code: 'import { z } from "zod";', filename: LIB_FILE }, + { code: 'import { b } from "../y";', filename: LIB_FILE }, + // components importing components and lib is the right direction. + { + code: 'import { C } from "@/components/ui/card";', + filename: CLIENT_FILE, + }, + // Tests are out of scope. + { + code: 'import { C } from "@/components/ui/card";', + filename: "/repo/src/lib/medications/__tests__/x.test.ts", + }, + // An allowed lib file that still imports upward. + { + code: 'import { C } from "@/components/ui/card";', + filename: LIB_FILE, + options: [{ allow: { "src/lib/medications/example.ts": "reason" } }], + }, + // A server component (no directive) may import the database client. + { + code: 'import { prisma } from "@/lib/db";', + filename: "/repo/src/app/page.tsx", + }, + // A client file may import types from server modules and the enums entry. + { + code: '"use client";\nimport type { MeasurementType } from "@/generated/prisma/client";', + filename: CLIENT_FILE, + }, + { + code: '"use client";\nimport { type BackupRestoreJobView } from "@/lib/jobs/backup-restore";', + filename: CLIENT_FILE, + }, + { + code: '"use client";\nimport { MeasurementType } from "@/generated/prisma/enums";', + filename: CLIENT_FILE, + }, + // A string that only looks like the directive is not one. + { + code: 'const x = "use client";\nimport { prisma } from "@/lib/db";', + filename: CLIENT_FILE, + }, + ], + invalid: [ + { + code: 'import { C } from "@/components/ui/card";', + filename: LIB_FILE, + errors: [{ messageId: "libUpward" }], + }, + { + code: 'import type { P } from "@/app/settings/page";', + filename: LIB_FILE, + errors: [{ messageId: "libUpward" }], + }, + // Relative specifiers resolve to the same layer check. + { + code: 'import { C } from "../../components/ui/card";', + filename: LIB_FILE, + errors: [{ messageId: "libUpward" }], + }, + { + code: 'export { C } from "@/components/ui/card";', + filename: LIB_FILE, + errors: [{ messageId: "libUpward" }], + }, + { + code: 'const m = await import("@/components/ui/card");', + filename: LIB_FILE, + errors: [{ messageId: "libUpward" }], + }, + // An allowed file that stopped importing upward must leave the list. + { + code: 'import { a } from "@/lib/x";', + filename: LIB_FILE, + options: [{ allow: { "src/lib/medications/example.ts": "reason" } }], + errors: [{ messageId: "staleAllow" }], + }, + // Server-only modules in a client file. + { + code: '"use client";\nimport { prisma } from "@/lib/db";', + filename: CLIENT_FILE, + errors: [{ messageId: "clientServerOnly" }], + }, + { + code: '"use client";\nimport { encrypt } from "@/lib/crypto";', + filename: CLIENT_FILE, + errors: [{ messageId: "clientServerOnly" }], + }, + { + code: '"use client";\nimport { MeasurementType } from "@/generated/prisma/client";', + filename: CLIENT_FILE, + errors: [{ messageId: "clientServerOnly" }], + }, + { + code: '"use client";\nimport { enqueue } from "@/lib/jobs/boss-instance";', + filename: CLIENT_FILE, + errors: [{ messageId: "clientServerOnly" }], + }, + { + code: '"use client";\nimport { cookies } from "next/headers";', + filename: CLIENT_FILE, + errors: [{ messageId: "clientServerOnly" }], + }, + { + code: '"use client";\nimport { Pool } from "pg";', + filename: CLIENT_FILE, + errors: [{ messageId: "clientServerOnly" }], + }, + ], +}); diff --git a/eslint-plugins/healthlog/import-boundaries.js b/eslint-plugins/healthlog/import-boundaries.js new file mode 100644 index 000000000..09f6806bd --- /dev/null +++ b/eslint-plugins/healthlog/import-boundaries.js @@ -0,0 +1,199 @@ +/** + * @fileoverview ESLint rule — import direction between the layers of `src`. + * + * Two boundaries, both checked per import statement so the mistake shows up + * in the editor rather than in a later sweep: + * + * 1. `src/lib/**` never imports from `src/components/**` or `src/app/**`. + * The direction is `app → components → hooks → lib`. A lib module that + * reaches upward drags a component folder into server jobs and makes + * the layers impossible to split. Type-only imports count too: the + * dependency is the same, only erased at build time. + * + * 2. A `"use client"` file never makes a value import of a server-only + * module: the database client, the crypto module, the Prisma client + * entry, the job queue, `pg`, `next/headers`. Type-only imports are + * erased and stay legal, and the Prisma `enums` entry is browser-safe. + * The transitive version of this check is + * `src/__tests__/client-bundle-server-import-guard.test.ts`, which walks + * the whole client graph; this rule is the direct, local half. + * + * Existing lib → components imports are listed in the `allow` option, file + * by file, each with the reason it has not moved yet. A listed file that no + * longer imports upward is reported too, so the list can only shrink. + * + * WHAT THIS DOES NOT CATCH + * + * A dependency through a re-export in a package outside `src`, and a + * `require()` call. Neither occurs in the tree today. + */ + +"use strict"; + +const path = require("node:path"); + +function toPosix(filename) { + return filename.replace(/\\/g, "/"); +} + +function isTestFile(filename) { + return ( + /\/__tests__\//.test(filename) || + /\.(test|spec)\.[cm]?[jt]sx?$/.test(filename) || + /\/__mocks__\//.test(filename) + ); +} + +/** Path of `filename` relative to the repository's `src/`, or null. */ +function srcRelative(filename) { + const idx = filename.lastIndexOf("/src/"); + return idx === -1 ? null : filename.slice(idx + 1); +} + +/** Where a specifier lands inside `src/`, or null for a package. */ +function targetInSrc(spec, filename) { + if (spec.startsWith("@/")) return `src/${spec.slice(2)}`; + if (spec.startsWith(".")) { + const abs = toPosix(path.resolve(path.dirname(filename), spec)); + return srcRelative(abs); + } + return null; +} + +const SERVER_ONLY_PACKAGES = [ + /^pg$/, + /^pg-boss$/, + /^@prisma\//, + /^next\/headers$/, +]; + +const SERVER_ONLY_SRC = [ + /^src\/lib\/db(-session)?$/, + /^src\/lib\/crypto(\/|$)/, + /^src\/lib\/jobs\//, + /^src\/generated\/prisma\/(client|internal|models)(\/|$)/, +]; + +function isServerOnly(spec, filename) { + if (SERVER_ONLY_PACKAGES.some((re) => re.test(spec))) return true; + const target = targetInSrc(spec, filename); + if (!target) return false; + const bare = target.replace(/\.(ts|tsx|js|mjs)$/, ""); + return SERVER_ONLY_SRC.some((re) => re.test(bare)); +} + +function isTypeOnly(node) { + if (node.importKind === "type" || node.exportKind === "type") return true; + const specifiers = node.specifiers ?? []; + return ( + specifiers.length > 0 && + specifiers.every((s) => s.importKind === "type" || s.exportKind === "type") + ); +} + +function hasUseClient(program) { + for (const stmt of program.body) { + if (stmt.type !== "ExpressionStatement" || !stmt.directive) break; + if (stmt.directive === "use client") return true; + } + return false; +} + +/** @type {import("eslint").Rule.RuleModule} */ +const importBoundariesRule = { + meta: { + type: "problem", + docs: { + description: + "Keep src/lib below components and app, and server-only modules out of client files.", + }, + schema: [ + { + type: "object", + properties: { + allow: { + type: "object", + additionalProperties: { type: "string", minLength: 1 }, + }, + }, + additionalProperties: false, + }, + ], + messages: { + libUpward: + "src/lib must not import {{target}}. Move the shared piece into src/lib (or the importer into the component folder); an exception goes into the rule's `allow` map in eslint.config.mjs with its reason.", + staleAllow: + "This file is in the import-boundaries `allow` map but no longer imports from src/components or src/app. Remove the entry so the list keeps shrinking.", + clientServerOnly: + 'A "use client" file must not import {{spec}}: it is server-only and would ship to the browser. Import a type with `import type`, or move the call behind an API route or a server component.', + }, + }, + + create(context) { + const filename = toPosix(context.filename ?? context.getFilename?.() ?? ""); + const rel = srcRelative(filename); + if (!rel || isTestFile(filename)) return {}; + + const allow = (context.options[0] && context.options[0].allow) || {}; + const inLib = rel.startsWith("src/lib/"); + const allowed = inLib && Object.hasOwn(allow, rel); + let upwardSeen = false; + let isClient = false; + + function check(node, source) { + if (!source || typeof source.value !== "string") return; + const spec = source.value; + if (inLib) { + const target = targetInSrc(spec, filename); + if ( + target && + (target.startsWith("src/components/") || + target.startsWith("src/app/")) + ) { + upwardSeen = true; + if (!allowed) { + context.report({ + node: source, + messageId: "libUpward", + data: { target: spec }, + }); + } + } + } + if (isClient && !isTypeOnly(node) && isServerOnly(spec, filename)) { + context.report({ + node: source, + messageId: "clientServerOnly", + data: { spec }, + }); + } + } + + return { + Program(program) { + isClient = hasUseClient(program); + }, + ImportDeclaration(node) { + check(node, node.source); + }, + ExportNamedDeclaration(node) { + if (node.source) check(node, node.source); + }, + ExportAllDeclaration(node) { + check(node, node.source); + }, + ImportExpression(node) { + if (node.source && node.source.type === "Literal") { + check(node, node.source); + } + }, + "Program:exit"(program) { + if (allowed && !upwardSeen) { + context.report({ node: program, messageId: "staleAllow" }); + } + }, + }; + }, +}; + +module.exports = importBoundariesRule; diff --git a/eslint-plugins/healthlog/index.js b/eslint-plugins/healthlog/index.js index ef0b03df6..590d89d7e 100644 --- a/eslint-plugins/healthlog/index.js +++ b/eslint-plugins/healthlog/index.js @@ -21,6 +21,8 @@ * toLocaleString(). * - `no-raw-card-shell` — a card surface is a or a shell * primitive, not bg-card + border + rounded-xl. + * - `import-boundaries` — src/lib never imports components or app, and + * a "use client" file imports no server module. */ "use strict"; @@ -36,6 +38,7 @@ const noUtcDayKey = require("./no-utc-day-key.js"); const noPlanningTags = require("./no-planning-tags.js"); const noUnlocalisedDecimal = require("./no-unlocalised-decimal.js"); const noRawCardShell = require("./no-raw-card-shell.js"); +const importBoundaries = require("./import-boundaries.js"); module.exports = { rules: { @@ -55,5 +58,6 @@ module.exports = { "no-planning-tags": noPlanningTags, "no-unlocalised-decimal": noUnlocalisedDecimal, "no-raw-card-shell": noRawCardShell, + "import-boundaries": importBoundaries, }, }; diff --git a/eslint.config.mjs b/eslint.config.mjs index a6bf8aac0..ce53edc99 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -176,6 +176,27 @@ const eslintConfig = defineConfig([ ], }, ], + // Layer direction: src/lib never imports src/components or src/app, and + // a "use client" file makes no value import of the database client, + // crypto, the Prisma client entry, the job queue, pg or next/headers. + // The files below still import upward; each says why it has not moved. + // A listed file that stops importing upward fails until it is removed, + // so the map only shrinks. See the rule header. + "healthlog/import-boundaries": [ + "error", + { + allow: { + "src/lib/insights/metric-catalog.ts": + "reads SUB_PAGE_TABS, which is declared inside the client tab strip component together with its labels and order", + "src/lib/insights/target-adjust-context.tsx": + "a React provider that renders TargetEditSheet; it belongs under components but has importers across 21 insight pages", + "src/lib/errors/error-toast.tsx": + "a React toast renderer that renders ErrorIdLine; it belongs under components but is imported by most client surfaces", + "src/lib/module-list-prefs.ts": + "a client hook module that raises the shared outcome toast; it moves with the module list settings surface", + }, + }, + ], }, }, // v1.28.17 — every recharts-rendering component funnels through the diff --git a/src/__tests__/settings-legacy-routes.test.ts b/src/__tests__/settings-legacy-routes.test.ts index c04cbe5ad..870b11045 100644 --- a/src/__tests__/settings-legacy-routes.test.ts +++ b/src/__tests__/settings-legacy-routes.test.ts @@ -19,7 +19,7 @@ import { dirname, join } from "node:path"; import { describe, expect, it } from "vitest"; import nextConfig from "../../next.config"; -import { SETTINGS_SECTION_SLUGS } from "@/components/settings/section-slugs"; +import { SETTINGS_SECTION_SLUGS } from "@/lib/settings/section-slugs"; import { LAYOUT_GROUP_IDS } from "@/components/settings/layout-groups"; import { MODULE_SETTINGS_PAGE_IDS } from "@/components/settings/module-settings-page-ids"; import { diff --git a/src/app/api/medications/[id]/glp1/route.ts b/src/app/api/medications/[id]/glp1/route.ts index ffeff1384..eb5781d23 100644 --- a/src/app/api/medications/[id]/glp1/route.ts +++ b/src/app/api/medications/[id]/glp1/route.ts @@ -34,7 +34,7 @@ import { estimateRunwayDays, lowStockTriggerDays, type RunwaySchedule, -} from "@/components/medications/detail/supply-runway"; +} from "@/lib/medications/supply-runway"; import { resolveLowStockRunwayDays, resolveReorderLeadDays, diff --git a/src/app/api/medications/[id]/route.ts b/src/app/api/medications/[id]/route.ts index e711a713d..a85ee9561 100644 --- a/src/app/api/medications/[id]/route.ts +++ b/src/app/api/medications/[id]/route.ts @@ -54,7 +54,7 @@ import { assertMedicationOwnership } from "@/lib/medications/route-guards"; import { effectiveUnitsPerDose, estimateUnitsRunwayDays, -} from "@/components/medications/detail/supply-runway"; +} from "@/lib/medications/supply-runway"; import { serializeScheduleUnitsPerDose } from "@/lib/medications/schedule-units-dto"; import { hhmmToMinutesOrNull } from "@/lib/medications/scheduling/hhmm"; import { getUserTodayBounds } from "@/lib/tz/local-day"; diff --git a/src/app/insights/sleep/__tests__/empty-state-href.test.ts b/src/app/insights/sleep/__tests__/empty-state-href.test.ts index 58613b6ba..0e3d1f414 100644 --- a/src/app/insights/sleep/__tests__/empty-state-href.test.ts +++ b/src/app/insights/sleep/__tests__/empty-state-href.test.ts @@ -10,7 +10,7 @@ import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { isSettingsSectionSlug } from "@/components/settings/section-slugs"; +import { isSettingsSectionSlug } from "@/lib/settings/section-slugs"; describe("sleep empty-state CTA href", () => { it("links to /settings/integrations (a real settings slug)", () => { diff --git a/src/app/settings/[section]/page.tsx b/src/app/settings/[section]/page.tsx index c58be6aeb..5c17bd69f 100644 --- a/src/app/settings/[section]/page.tsx +++ b/src/app/settings/[section]/page.tsx @@ -3,7 +3,7 @@ import { notFound } from "next/navigation"; import { SETTINGS_SECTION_SLUGS, isSettingsSectionSlug, -} from "@/components/settings/section-slugs"; +} from "@/lib/settings/section-slugs"; import { SettingsShell } from "@/components/settings/settings-shell"; import { SettingsSectionLoader, diff --git a/src/components/admin/section-slugs.ts b/src/components/admin/section-slugs.ts index 55ac2e4f7..e9c61c703 100644 --- a/src/components/admin/section-slugs.ts +++ b/src/components/admin/section-slugs.ts @@ -1,6 +1,6 @@ /** * Server-safe constants for the v1.5 admin split. Mirrors the pattern in - * `src/components/settings/section-slugs.ts` so server-rendered code paths + * `src/lib/settings/section-slugs.ts` so server-rendered code paths * (`generateStaticParams()`, the page guard, route-existence tests) can * import the slug list without dragging the `` client bundle * into the server graph. diff --git a/src/lib/command-palette/__tests__/build-index.test.ts b/src/components/command-palette/__tests__/build-index.test.ts similarity index 99% rename from src/lib/command-palette/__tests__/build-index.test.ts rename to src/components/command-palette/__tests__/build-index.test.ts index 7540fd70b..0dfdf379b 100644 --- a/src/lib/command-palette/__tests__/build-index.test.ts +++ b/src/components/command-palette/__tests__/build-index.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest"; import { buildPaletteIndex, type PaletteIndexInput, -} from "@/lib/command-palette/build-index"; +} from "@/components/command-palette/build-index"; const t = (key: string) => key; diff --git a/src/components/command-palette/__tests__/command-palette.test.tsx b/src/components/command-palette/__tests__/command-palette.test.tsx index aeeb64b5f..c8c36fa01 100644 --- a/src/components/command-palette/__tests__/command-palette.test.tsx +++ b/src/components/command-palette/__tests__/command-palette.test.tsx @@ -3,7 +3,7 @@ import { renderToStaticMarkup } from "react-dom/server"; import { Activity, Moon, Plus, Smile } from "lucide-react"; import { I18nProvider } from "@/lib/i18n/context"; -import type { PaletteEntry } from "@/lib/command-palette/build-index"; +import type { PaletteEntry } from "@/components/command-palette/build-index"; import type { SearchResponse } from "@/lib/search/contract"; vi.mock("next/navigation", () => ({ diff --git a/src/lib/command-palette/build-index.ts b/src/components/command-palette/build-index.ts similarity index 98% rename from src/lib/command-palette/build-index.ts rename to src/components/command-palette/build-index.ts index 68e2bf471..112c8ac74 100644 --- a/src/lib/command-palette/build-index.ts +++ b/src/components/command-palette/build-index.ts @@ -24,8 +24,8 @@ import { SETTINGS_CARDS, SETTINGS_LAYOUT_PAGES, SETTINGS_MODULE_PAGES, -} from "./settings-cards"; -import { PALETTE_SYNONYMS } from "./synonyms"; +} from "@/lib/command-palette/settings-cards"; +import { PALETTE_SYNONYMS } from "@/lib/command-palette/synonyms"; /** * The command palette's index: every place and action this session may diff --git a/src/components/command-palette/command-palette.tsx b/src/components/command-palette/command-palette.tsx index 14b19727f..148492314 100644 --- a/src/components/command-palette/command-palette.tsx +++ b/src/components/command-palette/command-palette.tsx @@ -30,7 +30,7 @@ import { EMPTY_STATE_ACTIONS, type PaletteEntry, type PaletteGroup, -} from "@/lib/command-palette/build-index"; +} from "@/components/command-palette/build-index"; import { rankEntries } from "@/lib/command-palette/rank"; import { pushRecent, readRecent } from "@/lib/command-palette/recent"; import { scrollToAnchorWhenReady } from "@/lib/dom/scroll-to-anchor"; diff --git a/src/components/command-palette/use-palette-index.ts b/src/components/command-palette/use-palette-index.ts index 3d98e2a0b..32eb9a68d 100644 --- a/src/components/command-palette/use-palette-index.ts +++ b/src/components/command-palette/use-palette-index.ts @@ -14,7 +14,7 @@ import { useRecordCapabilities } from "@/hooks/use-record-capabilities"; import { buildPaletteIndex, type PaletteEntry, -} from "@/lib/command-palette/build-index"; +} from "@/components/command-palette/build-index"; import { useTranslations } from "@/lib/i18n/context"; import { MODULE_REGISTRY, diff --git a/src/components/insights/__tests__/sleep-overview-empty-href.test.ts b/src/components/insights/__tests__/sleep-overview-empty-href.test.ts index 6a336abaa..441a7b5f2 100644 --- a/src/components/insights/__tests__/sleep-overview-empty-href.test.ts +++ b/src/components/insights/__tests__/sleep-overview-empty-href.test.ts @@ -11,7 +11,7 @@ import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { isSettingsSectionSlug } from "@/components/settings/section-slugs"; +import { isSettingsSectionSlug } from "@/lib/settings/section-slugs"; describe("sleep-overview empty-state CTA href", () => { it("links to /settings/integrations (a real settings slug)", () => { diff --git a/src/components/medications/__tests__/medication-form-v15.test.tsx b/src/components/medications/__tests__/medication-form-v15.test.tsx index c598921b7..f68d69598 100644 --- a/src/components/medications/__tests__/medication-form-v15.test.tsx +++ b/src/components/medications/__tests__/medication-form-v15.test.tsx @@ -24,10 +24,8 @@ import { weekdayIndexesToTokens, weekdayIndexToToken, } from "@/components/medications/scheduling/legacy-bridge"; -import { - DEFAULT_SUB_CONTROLS, - type WeekdayToken, -} from "@/components/medications/scheduling/types"; +import { DEFAULT_SUB_CONTROLS } from "@/components/medications/scheduling/types"; +import { type WeekdayToken } from "@/lib/medications/scheduling/weekday-tokens"; describe("weekdayIndexToToken", () => { it("maps Sunday-anchored 0..6 to SU/MO/TU/WE/TH/FR/SA", () => { diff --git a/src/components/medications/detail/medication-detail-tabs.tsx b/src/components/medications/detail/medication-detail-tabs.tsx index 140e60ab6..ff57868ce 100644 --- a/src/components/medications/detail/medication-detail-tabs.tsx +++ b/src/components/medications/detail/medication-detail-tabs.tsx @@ -88,7 +88,7 @@ import { PhaseConfigSheet } from "@/components/medications/sections/phase-config import { SideEffectsSection } from "@/components/medications/side-effects-section"; import { ChartSkeleton } from "@/components/charts/chart-skeleton"; import { TitrationTimeline } from "@/components/medications/titration-timeline"; -import { estimateRunwayDays } from "@/components/medications/detail/supply-runway"; +import { estimateRunwayDays } from "@/lib/medications/supply-runway"; import type { SupplySummary } from "@/lib/medications/inventory/summary"; import { MedicationWizardDialog } from "@/components/medications/wizard/medication-wizard-dialog"; import { parseScheduleRecurrence } from "@/lib/medication-schedule"; diff --git a/src/components/medications/glp1-medication-card.tsx b/src/components/medications/glp1-medication-card.tsx index 72a73b447..5024217cd 100644 --- a/src/components/medications/glp1-medication-card.tsx +++ b/src/components/medications/glp1-medication-card.tsx @@ -38,7 +38,7 @@ import { estimateDailyDoseCount, estimateRunwayDays, lowStockTriggerDays, -} from "@/components/medications/detail/supply-runway"; +} from "@/lib/medications/supply-runway"; import { useAuth } from "@/hooks/use-auth"; import { useMedicationComplianceSummary } from "@/lib/queries/use-medication-compliance-summary"; import { DEFAULT_TIMEZONE } from "@/lib/tz/format"; diff --git a/src/components/medications/medication-card.tsx b/src/components/medications/medication-card.tsx index 7c7a1f5ed..d3033e08b 100644 --- a/src/components/medications/medication-card.tsx +++ b/src/components/medications/medication-card.tsx @@ -24,7 +24,7 @@ import { estimateDailyDoseCount, estimateRunwayDays, lowStockTriggerDays, -} from "@/components/medications/detail/supply-runway"; +} from "@/lib/medications/supply-runway"; import { useTranslations, useFormatters } from "@/lib/i18n/context"; import { invalidateKeys, diff --git a/src/components/medications/medication-table.tsx b/src/components/medications/medication-table.tsx index 56f11405c..5795b6d3e 100644 --- a/src/components/medications/medication-table.tsx +++ b/src/components/medications/medication-table.tsx @@ -28,7 +28,7 @@ import { Skeleton } from "@/components/ui/skeleton"; import { MedicationStatusPill } from "@/components/medications/card-parts/medication-status-pill"; import { useWeekdayLabel } from "@/components/medications/card-parts/medication-next-last-slot"; import { resolveDisplayedSlotInstant } from "@/components/medications/card-parts/displayed-slot-instant"; -import { estimateRunwayDays } from "@/components/medications/detail/supply-runway"; +import { estimateRunwayDays } from "@/lib/medications/supply-runway"; import { useMedicationIntake } from "@/components/medications/use-medication-intake"; import { reduceCurrentWindowStatus } from "@/lib/medications/window-status"; import { resolveNextDueDayLabel } from "@/lib/medications/next-due-day-label"; diff --git a/src/components/medications/scheduling/cadence-picker.tsx b/src/components/medications/scheduling/cadence-picker.tsx index 4a4271e0b..1ecff45bc 100644 --- a/src/components/medications/scheduling/cadence-picker.tsx +++ b/src/components/medications/scheduling/cadence-picker.tsx @@ -59,9 +59,11 @@ import { type CadenceSubControls, type CadenceValue, DEFAULT_SUB_CONTROLS, +} from "./types"; +import { type WeekdayToken, WEEKDAY_TOKENS, -} from "./types"; +} from "@/lib/medications/scheduling/weekday-tokens"; export interface CadencePickerProps { value: CadenceValue; diff --git a/src/components/medications/scheduling/legacy-bridge.ts b/src/components/medications/scheduling/legacy-bridge.ts index 5af04db0f..7ce92cff9 100644 --- a/src/components/medications/scheduling/legacy-bridge.ts +++ b/src/components/medications/scheduling/legacy-bridge.ts @@ -17,9 +17,11 @@ import { type CadenceSubControls, type CadenceValue, DEFAULT_SUB_CONTROLS, +} from "./types"; +import { type WeekdayToken, WEEKDAY_TOKENS, -} from "./types"; +} from "@/lib/medications/scheduling/weekday-tokens"; /** * Legacy schedule shape the page passes to the form. Sunday-anchored diff --git a/src/components/medications/scheduling/types.ts b/src/components/medications/scheduling/types.ts index 823e42a3f..5df6d4cea 100644 --- a/src/components/medications/scheduling/types.ts +++ b/src/components/medications/scheduling/types.ts @@ -17,6 +17,8 @@ * structured values; the wizard / form maps them to the wire format. */ +import type { WeekdayToken } from "@/lib/medications/scheduling/weekday-tokens"; + /** * The eight cadence kinds the wizard exposes. Maps to the engine's * dispatch tiers in `src/lib/medications/scheduling/recurrence.ts`: @@ -71,18 +73,6 @@ export interface CadenceSubControls { rollingDays: number; } -/** RFC 5545 BYDAY tokens — the order the chip row renders. */ -export const WEEKDAY_TOKENS = [ - "MO", - "TU", - "WE", - "TH", - "FR", - "SA", - "SU", -] as const; -export type WeekdayToken = (typeof WEEKDAY_TOKENS)[number]; - /** Sensible defaults so a brand-new picker has valid sub-controls. */ export const DEFAULT_SUB_CONTROLS: CadenceSubControls = { weekdays: ["MO"], diff --git a/src/components/medications/wizard/nl-bridge.ts b/src/components/medications/wizard/nl-bridge.ts index 9279a2211..685473f9b 100644 --- a/src/components/medications/wizard/nl-bridge.ts +++ b/src/components/medications/wizard/nl-bridge.ts @@ -16,8 +16,8 @@ import { isoStringToDate } from "@/components/medications/scheduling/course-wind import { DEFAULT_SUB_CONTROLS, type CadenceSubControls, - type WeekdayToken, } from "@/components/medications/scheduling/types"; +import { type WeekdayToken } from "@/lib/medications/scheduling/weekday-tokens"; import type { WizardPayload as ExtractorPayload } from "@/components/medications/scheduling/natural-language-extractor"; import type { WizardPayload } from "./wizard-payload"; diff --git a/src/components/medications/wizard/steps/step6-sub-cadence.tsx b/src/components/medications/wizard/steps/step6-sub-cadence.tsx index 259b0e499..e5816b6b9 100644 --- a/src/components/medications/wizard/steps/step6-sub-cadence.tsx +++ b/src/components/medications/wizard/steps/step6-sub-cadence.tsx @@ -5,11 +5,11 @@ import { useMemo } from "react"; import { encodeCadence } from "@/components/medications/scheduling/cadence-picker"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; +import { type CadenceSubControls } from "@/components/medications/scheduling/types"; import { - type CadenceSubControls, type WeekdayToken, WEEKDAY_TOKENS, -} from "@/components/medications/scheduling/types"; +} from "@/lib/medications/scheduling/weekday-tokens"; import { useTranslations } from "@/lib/i18n/context"; import type { StepProps } from "./step1-name"; diff --git a/src/components/settings/__tests__/sections-i18n-parity.test.ts b/src/components/settings/__tests__/sections-i18n-parity.test.ts index 8b6be7cc0..60e0ae128 100644 --- a/src/components/settings/__tests__/sections-i18n-parity.test.ts +++ b/src/components/settings/__tests__/sections-i18n-parity.test.ts @@ -12,7 +12,7 @@ import { describe, expect, it } from "vitest"; import en from "../../../../messages/en.json" with { type: "json" }; import de from "../../../../messages/de.json" with { type: "json" }; -import { SETTINGS_SECTION_SLUGS } from "../section-slugs"; +import { SETTINGS_SECTION_SLUGS } from "@/lib/settings/section-slugs"; type Bag = Record; diff --git a/src/components/settings/__tests__/sections.test.tsx b/src/components/settings/__tests__/sections.test.tsx index e6420d6a3..7433e2e73 100644 --- a/src/components/settings/__tests__/sections.test.tsx +++ b/src/components/settings/__tests__/sections.test.tsx @@ -112,7 +112,7 @@ vi.mock("@/components/settings/thresholds-editor-section", () => ({ })); import { I18nProvider } from "@/lib/i18n/context"; import { SettingsSectionFrame } from "./section-frame-harness"; -import type { SettingsSectionSlug } from "../section-slugs"; +import type { SettingsSectionSlug } from "@/lib/settings/section-slugs"; import { AboutSection } from "../about-section"; import { AccountSection } from "../account-section"; import { AdvancedSection } from "../advanced-section"; diff --git a/src/components/settings/record-settings-section-gate.tsx b/src/components/settings/record-settings-section-gate.tsx index 35d155064..a66a0a387 100644 --- a/src/components/settings/record-settings-section-gate.tsx +++ b/src/components/settings/record-settings-section-gate.tsx @@ -11,7 +11,7 @@ import { classifySettingsDestination } from "@/lib/record-settings/classificatio import { SettingsCardHeader } from "@/components/settings/_card-header"; import { ManagedIntegrationStatus } from "@/components/settings/managed-integration-status"; import { SettingsCard } from "@/components/settings/settings-card"; -import type { SettingsSectionSlug } from "@/components/settings/section-slugs"; +import type { SettingsSectionSlug } from "@/lib/settings/section-slugs"; // The two record-scoped surfaces load on demand. Both are forms that validate // with Zod (the managed settings patch, the allergy and family-history diff --git a/src/components/settings/settings-section-loader.tsx b/src/components/settings/settings-section-loader.tsx index 41aecb597..2c0faae98 100644 --- a/src/components/settings/settings-section-loader.tsx +++ b/src/components/settings/settings-section-loader.tsx @@ -5,7 +5,7 @@ import { useEffect, type ComponentType } from "react"; import { Skeleton } from "@/components/ui/skeleton"; import { SettingsCard } from "@/components/settings/settings-card"; -import type { SettingsSectionSlug } from "@/components/settings/section-slugs"; +import type { SettingsSectionSlug } from "@/lib/settings/section-slugs"; import type { IntegrationCallbackUrls } from "@/lib/integrations/callback-urls"; /** diff --git a/src/components/settings/settings-shell.tsx b/src/components/settings/settings-shell.tsx index 29c8b1af2..1ddee6b15 100644 --- a/src/components/settings/settings-shell.tsx +++ b/src/components/settings/settings-shell.tsx @@ -55,7 +55,7 @@ import { SETTINGS_SECTION_SLUGS, isSettingsSectionSlug, type SettingsSectionSlug, -} from "./section-slugs"; +} from "@/lib/settings/section-slugs"; import { SettingsHubBackLink } from "./settings-hub-back-link"; import { SettingsMobileNav } from "./settings-mobile-nav"; import { SettingsAnchorForward } from "./settings-anchor-forward"; diff --git a/src/lib/ai/coach/medication-extract-prompt.ts b/src/lib/ai/coach/medication-extract-prompt.ts index bbabd616c..09d735dbe 100644 --- a/src/lib/ai/coach/medication-extract-prompt.ts +++ b/src/lib/ai/coach/medication-extract-prompt.ts @@ -29,7 +29,7 @@ import { z } from "zod/v4"; -import { WEEKDAY_TOKENS } from "@/components/medications/scheduling/types"; +import { WEEKDAY_TOKENS } from "@/lib/medications/scheduling/weekday-tokens"; /** Closed list — matches the design-synthesis dose-unit dropdown. */ export const DOSE_UNITS = [ diff --git a/src/lib/jobs/medication-low-stock.ts b/src/lib/jobs/medication-low-stock.ts index ea44a842d..5c2e8af5c 100644 --- a/src/lib/jobs/medication-low-stock.ts +++ b/src/lib/jobs/medication-low-stock.ts @@ -69,7 +69,7 @@ import { lowStockTriggerDays, supplyRunwayDates, type RunwaySchedule, -} from "@/components/medications/detail/supply-runway"; +} from "@/lib/medications/supply-runway"; import { detectSupplyUnderflow, summariseSupply, diff --git a/src/components/medications/__tests__/supply-runway.test.ts b/src/lib/medications/__tests__/supply-runway.test.ts similarity index 99% rename from src/components/medications/__tests__/supply-runway.test.ts rename to src/lib/medications/__tests__/supply-runway.test.ts index 25513acc4..40424820c 100644 --- a/src/components/medications/__tests__/supply-runway.test.ts +++ b/src/lib/medications/__tests__/supply-runway.test.ts @@ -16,7 +16,7 @@ import { lowStockTriggerDays, supplyRunwayDates, type RunwaySchedule, -} from "@/components/medications/detail/supply-runway"; +} from "@/lib/medications/supply-runway"; function schedule(partial: Partial): RunwaySchedule { return { diff --git a/src/lib/medications/list-read.ts b/src/lib/medications/list-read.ts index 72507873e..a931edb2a 100644 --- a/src/lib/medications/list-read.ts +++ b/src/lib/medications/list-read.ts @@ -24,7 +24,7 @@ import { import { effectiveUnitsPerDose, estimateUnitsRunwayDays, -} from "@/components/medications/detail/supply-runway"; +} from "@/lib/medications/supply-runway"; import { serializeScheduleUnitsPerDose } from "@/lib/medications/schedule-units-dto"; import { computeDisplayDue, diff --git a/src/lib/medications/scheduling/weekday-tokens.ts b/src/lib/medications/scheduling/weekday-tokens.ts new file mode 100644 index 000000000..8a5166107 --- /dev/null +++ b/src/lib/medications/scheduling/weekday-tokens.ts @@ -0,0 +1,17 @@ +/** + * RFC 5545 BYDAY tokens in the order the weekday chip row renders them. + * + * Shared by the scheduling wizard and the server-side medication extraction + * prompt, so it lives beside the recurrence engine rather than in a + * component folder. + */ +export const WEEKDAY_TOKENS = [ + "MO", + "TU", + "WE", + "TH", + "FR", + "SA", + "SU", +] as const; +export type WeekdayToken = (typeof WEEKDAY_TOKENS)[number]; diff --git a/src/components/medications/detail/supply-runway.ts b/src/lib/medications/supply-runway.ts similarity index 100% rename from src/components/medications/detail/supply-runway.ts rename to src/lib/medications/supply-runway.ts diff --git a/src/lib/record-settings/classification.ts b/src/lib/record-settings/classification.ts index fbeda64b2..3512c8204 100644 --- a/src/lib/record-settings/classification.ts +++ b/src/lib/record-settings/classification.ts @@ -1,7 +1,7 @@ import { SETTINGS_SECTION_SLUGS, type SettingsSectionSlug, -} from "@/components/settings/section-slugs"; +} from "@/lib/settings/section-slugs"; import type { AccountAccessLevel, AccountRecordKind, diff --git a/src/components/settings/section-slugs.ts b/src/lib/settings/section-slugs.ts similarity index 100% rename from src/components/settings/section-slugs.ts rename to src/lib/settings/section-slugs.ts From f3d7ab31e2b9413896facc604a0e2cba5da5b6d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 04:29:35 +0200 Subject: [PATCH 2/9] test(guards): make path-pinned source reads fail when their subject moves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A test that reads a source file by path and asserts an absence (`expect(source).not.toMatch(…)`) stays green after a split: the code it watched moved next door and the old path no longer holds it. A missing file throws; a file that kept its name but lost its contents does not. - `readSource(path, anchors)` in helpers/source-files.ts reads one file and requires every anchor (a string or pattern the watched code carries) to match at least once; it throws on a missing file, a lost anchor, or a read without an anchor. - `readSourceFolder(dir, { floor })` is the folder-scan form for a subject that was split into several files. - The twelve reads that were asserted only negatively now go through `readSource` with an anchor: the batch route, the integration-sync registrar, the dashboard client, the mood insights client, the sleep overview and sleep page, the sidebar, the baseline form, the account section (two guards), api-handler's requireAuth and the Google Health sync. - The two schema parsers the wipe and enum guards share now refuse a parse of the real schema that finds fewer than 50 models (and 10 enums), so a moved file or a drifted block regex cannot leave those guards comparing an allowlist against nothing. Synthetic schemas a test writes stay exempt. Proof: renaming `setSaving` in the baseline form made the converted confirm-failure guard throw "the file no longer contains setSaving"; the helper's own test plants a missing file, a lost string anchor, a lost regex anchor, an empty anchor list and an under-floor folder, each of which throws. --- .../apple-health-unit-audit-guard.test.ts | 9 ++- src/__tests__/helpers/prisma-schema-names.ts | 11 +++ src/__tests__/helpers/source-files.ts | 81 ++++++++++++++++++- src/__tests__/helpers/wipe-schema-shape.ts | 6 ++ src/__tests__/moodlog-removal-guard.test.ts | 7 +- src/__tests__/source-read-helpers.test.ts | 55 +++++++++++++ .../dashboard-hrv-rmssd-fallback.test.tsx | 9 ++- src/app/api/tokens/__tests__/route.test.ts | 9 +-- .../__tests__/header-add-pattern.test.ts | 6 +- .../sleep/__tests__/empty-state-href.test.ts | 7 +- .../sleep-overview-empty-href.test.ts | 7 +- .../layout/__tests__/sidebar-nav.test.tsx | 7 +- .../confirm-failure-recovery.test.ts | 11 ++- .../anamnesis-about-me-relocation.test.tsx | 9 +-- .../password-card-placement.test.tsx | 7 +- .../integration-cohort-outcomes.test.ts | 7 +- 16 files changed, 204 insertions(+), 44 deletions(-) create mode 100644 src/__tests__/source-read-helpers.test.ts diff --git a/src/__tests__/apple-health-unit-audit-guard.test.ts b/src/__tests__/apple-health-unit-audit-guard.test.ts index b8799aeaf..070472f32 100644 --- a/src/__tests__/apple-health-unit-audit-guard.test.ts +++ b/src/__tests__/apple-health-unit-audit-guard.test.ts @@ -48,6 +48,7 @@ import { convertHkValue, isConvertibleHkUnit, } from "@/lib/measurements/hk-units"; +import { readSource } from "@/__tests__/helpers/source-files"; const ENTRIES = Object.values(APPLE_HEALTH_TYPE_MAP); @@ -193,10 +194,10 @@ describe("Apple Health mapping — the record's own unit (issue #944)", () => { "utf8", ); expect(archive).toContain(optIn); - const batch = readFileSync( - join(process.cwd(), "src/app/api/measurements/batch/route.ts"), - "utf8", - ); + const batch = readSource("src/app/api/measurements/batch/route.ts", [ + "export const POST", + "hkIdentifier", + ]); expect( batch, "the batch route opted into record-unit conversion", diff --git a/src/__tests__/helpers/prisma-schema-names.ts b/src/__tests__/helpers/prisma-schema-names.ts index b9e187e12..f45171d02 100644 --- a/src/__tests__/helpers/prisma-schema-names.ts +++ b/src/__tests__/helpers/prisma-schema-names.ts @@ -171,5 +171,16 @@ export function readDeclaredSchema( }; }); + // Every consumer compares this against an allowlist or a "none of these" + // rule, which an empty parse satisfies. For the real schema, this little is + // a broken read (a moved file, a block regex that stopped matching), not a + // schema. A synthetic schema a test writes itself is exempt. + if (schemaPath === SCHEMA_PATH && (models.length < 50 || enums.length < 10)) { + throw new Error( + `readDeclaredSchema(${schemaPath}): parsed ${models.length} model(s) and ` + + `${enums.length} enum(s), below the floor of 50 and 10. Fix the parser ` + + `before trusting a guard built on it.`, + ); + } return { enums, models }; } diff --git a/src/__tests__/helpers/source-files.ts b/src/__tests__/helpers/source-files.ts index 15a5da508..70e92385a 100644 --- a/src/__tests__/helpers/source-files.ts +++ b/src/__tests__/helpers/source-files.ts @@ -50,7 +50,7 @@ * worktrees, each a full second copy of the tree that would double every * match. Every caller roots at `src/`, `e2e/` or a subtree of one. */ -import { readdirSync, readFileSync } from "node:fs"; +import { existsSync, readdirSync, readFileSync } from "node:fs"; import { join, sep } from "node:path"; export function walkSourceFiles( @@ -229,3 +229,82 @@ export function scanSourceMatches( options.matchFloor, ) as SourceMatch[]; } + +/** Something a file must still contain for a read of it to mean anything. */ +export type SourceAnchor = string | RegExp; + +function anchorCount(text: string, anchor: SourceAnchor): number { + if (typeof anchor === "string") return text.split(anchor).length - 1; + const flags = anchor.flags.includes("g") ? anchor.flags : `${anchor.flags}g`; + return [...text.matchAll(new RegExp(anchor.source, flags))].length; +} + +/** + * Read one source file by path, and prove it is still the file the guard is + * about. + * + * A guard that reads a fixed path and asserts that something is ABSENT + * (`expect(source).not.toMatch(…)`) is green on a file that has been split: + * the code it watched moved next door, the old path holds whatever is left, + * and the absence is true for the wrong reason. A missing file at least + * throws; a file that kept its name but lost its contents does not. + * + * So the caller names at least one anchor: a string or pattern the watched + * code itself carries (the function the guard is about, the call it pins). + * Every anchor must match at least once, or this throws and names the file, + * which is the moment to point the guard at the new home (or at a folder + * with `readSourceFolder`). `rel` is relative to the repository root. + */ +export function readSource( + rel: string, + anchors: SourceAnchor | readonly SourceAnchor[], +): string { + const list = (Array.isArray(anchors) ? anchors : [anchors]) as SourceAnchor[]; + if (list.length === 0) { + throw new Error( + `readSource(${rel}): name at least one anchor the watched code carries.`, + ); + } + const path = join(process.cwd(), rel); + if (!existsSync(path)) { + throw new Error( + `readSource(${rel}): the file does not exist. If it was split or moved, ` + + `point the guard at the new location or scan the folder with ` + + `readSourceFolder and a floor.`, + ); + } + const text = readFileSync(path, "utf8"); + const lost = list.filter((anchor) => anchorCount(text, anchor) === 0); + if (lost.length > 0) { + throw new Error( + `readSource(${rel}): the file no longer contains ${lost + .map(String) + .join(", ")}. The code this guard watches has moved; a negative ` + + `assertion on this file would now pass for the wrong reason.`, + ); + } + return text; +} + +/** + * Read every source file under a folder (relative to the repository root), + * with a floor on how many it found: the folder-scan form of `readSource` + * for a guard whose subject was split into several files. Returns + * `[path relative to the folder, text]` pairs, sorted. + */ +export function readSourceFolder( + relDir: string, + options: { + floor: number; + include?: (rel: string) => boolean; + extensions?: readonly string[]; + }, +): Array<[string, string]> { + const root = join(process.cwd(), relDir); + const files = walkSourceFiles(root, { + floor: options.floor, + extensions: options.extensions, + }).filter(options.include ?? (() => true)); + requireFloor(`readSourceFolder(${relDir})`, files, options.floor); + return files.map((rel) => [rel, readFileSync(join(root, rel), "utf8")]); +} diff --git a/src/__tests__/helpers/wipe-schema-shape.ts b/src/__tests__/helpers/wipe-schema-shape.ts index 1e6f915f2..51a80a736 100644 --- a/src/__tests__/helpers/wipe-schema-shape.ts +++ b/src/__tests__/helpers/wipe-schema-shape.ts @@ -38,6 +38,12 @@ export function parseWipeSchema( const blocks = [...schema.matchAll(/^model\s+(\w+)\s*\{([\s\S]*?)^\}/gm)].map( (m) => ({ name: m[1], body: m[2] }), ); + if (schemaPath === WIPE_SCHEMA_PATH && blocks.length < 50) { + throw new Error( + `parseWipeSchema(${schemaPath}): parsed ${blocks.length} model(s), below ` + + `the floor of 50. A wipe guard over an empty parse passes on nothing.`, + ); + } const modelNames = new Set(blocks.map((b) => b.name)); const out = new Map(); diff --git a/src/__tests__/moodlog-removal-guard.test.ts b/src/__tests__/moodlog-removal-guard.test.ts index 6bd82e6a3..6eccca904 100644 --- a/src/__tests__/moodlog-removal-guard.test.ts +++ b/src/__tests__/moodlog-removal-guard.test.ts @@ -22,6 +22,7 @@ import { describe, expect, it } from "vitest"; import { INTEGRATION_CADENCE } from "@/lib/integrations/sync-verdict"; import { INTEGRATION_DISPLAY_NAMES } from "@/components/settings/integrations/integration-fallback-row"; +import { readSource } from "@/__tests__/helpers/source-files"; const ROOT = path.resolve(__dirname, "../.."); @@ -65,9 +66,9 @@ describe("moodLog integration stays removed", () => { }); it("registers no moodlog queue, cron or handler binding", () => { - const registrar = readFileSync( - path.join(ROOT, "src/lib/jobs/reminder/register-integration-sync.ts"), - "utf8", + const registrar = readSource( + "src/lib/jobs/reminder/register-integration-sync.ts", + "export async function registerIntegrationSyncQueues", ); expect(registrar).not.toContain("moodlog"); expect(registrar).not.toContain("MoodLog"); diff --git a/src/__tests__/source-read-helpers.test.ts b/src/__tests__/source-read-helpers.test.ts new file mode 100644 index 000000000..05fb28e7a --- /dev/null +++ b/src/__tests__/source-read-helpers.test.ts @@ -0,0 +1,55 @@ +/** + * `readSource` and `readSourceFolder`: the reads a path-pinned guard goes + * through, so a split file fails the guard instead of leaving it green. + */ +import { describe, expect, it } from "vitest"; + +import { readSource, readSourceFolder } from "./helpers/source-files"; + +const HERE = "src/__tests__/helpers/source-files.ts"; + +describe("readSource", () => { + it("returns the text when every anchor is present", () => { + const text = readSource(HERE, [ + "export function readSource(", + /export function walkSourceFiles\(/, + ]); + expect(text.length).toBeGreaterThan(1000); + }); + + it("throws on a file that does not exist", () => { + expect(() => readSource("src/lib/does-not-exist.ts", "anything")).toThrow( + /does not exist/, + ); + }); + + it("throws when an anchor no longer matches", () => { + expect(() => + readSource(HERE, ["export function readSource(", "noSuchAnchorHere"]), + ).toThrow(/no longer contains noSuchAnchorHere/); + expect(() => readSource(HERE, /export function neverDeclared\(/)).toThrow( + /no longer contains/, + ); + }); + + it("refuses a read without an anchor", () => { + expect(() => readSource(HERE, [])).toThrow(/at least one anchor/); + }); +}); + +describe("readSourceFolder", () => { + it("returns the files with their text, above the floor", () => { + const files = readSourceFolder("src/__tests__/helpers", { floor: 5 }); + expect(files.length).toBeGreaterThanOrEqual(5); + expect(files.map(([rel]) => rel)).toContain("source-files.ts"); + }); + + it("throws when the folder holds fewer files than the floor", () => { + expect(() => + readSourceFolder("src/__tests__/helpers", { + floor: 1, + include: (rel) => rel === "nothing-matches.ts", + }), + ).toThrow(/below the stated floor/); + }); +}); diff --git a/src/app/__tests__/dashboard-hrv-rmssd-fallback.test.tsx b/src/app/__tests__/dashboard-hrv-rmssd-fallback.test.tsx index c063d13c1..6ed76d946 100644 --- a/src/app/__tests__/dashboard-hrv-rmssd-fallback.test.tsx +++ b/src/app/__tests__/dashboard-hrv-rmssd-fallback.test.tsx @@ -19,6 +19,7 @@ import { import { moduleForMeasurementType } from "@/lib/modules/measurement-scope"; import { locales } from "@/lib/i18n/config"; import type { DataSummary } from "@/lib/analytics/trends"; +import { readSource } from "@/__tests__/helpers/source-files"; /** * The dashboard HRV tile must accept either HRV type. @@ -250,10 +251,10 @@ describe("the tile the user actually sees", () => { // current formatting of the replacement, so reformatting cannot break // it. Freshness is per-type and captioning a live RMSSD tile from the // SDNN lookup is the failure it guards. - const page = readFileSync( - join(process.cwd(), "src/app/page-client.tsx"), - "utf8", - ); + const page = readSource("src/app/page-client.tsx", [ + "const tileStaleDays", + "HEART_RATE_VARIABILITY", + ]); expect(page).not.toContain('tileStaleDays("HEART_RATE_VARIABILITY")'); expect(page).not.toContain( "const hrvSummary = data?.summaries?.HEART_RATE_VARIABILITY", diff --git a/src/app/api/tokens/__tests__/route.test.ts b/src/app/api/tokens/__tests__/route.test.ts index 2c757c67d..e273d971d 100644 --- a/src/app/api/tokens/__tests__/route.test.ts +++ b/src/app/api/tokens/__tests__/route.test.ts @@ -40,6 +40,7 @@ import { DELETE } from "../[id]/route"; import { requireAuth } from "@/lib/api-handler"; import { prisma } from "@/lib/db"; import { isApiGloballyEnabled } from "@/lib/app-settings"; +import { readSource } from "@/__tests__/helpers/source-files"; const USER = { id: "user-1", username: "u" }; @@ -168,11 +169,9 @@ describe("the switch and the authentication path", () => { // a token would NOT be live during the outage and hiding the list would // stop being wrong. This reads the source because the behaviour is an // absence, and an absence has no call to spy on. - const { readFileSync } = await import("node:fs"); - const { join } = await import("node:path"); - const source = readFileSync( - join(process.cwd(), "src/lib/api-handler.ts"), - "utf8", + const source = readSource( + "src/lib/api-handler.ts", + "export async function requireAuth(", ); expect(source).not.toContain("isApiGloballyEnabled"); expect(source).not.toContain("apiGlobal"); diff --git a/src/app/insights/__tests__/header-add-pattern.test.ts b/src/app/insights/__tests__/header-add-pattern.test.ts index 72f9616a8..5dc8179ee 100644 --- a/src/app/insights/__tests__/header-add-pattern.test.ts +++ b/src/app/insights/__tests__/header-add-pattern.test.ts @@ -14,6 +14,7 @@ import { readdirSync, readFileSync, statSync } from "node:fs"; import { join, relative } from "node:path"; import { describe, expect, it } from "vitest"; +import { readSource } from "@/__tests__/helpers/source-files"; const ROOT = process.cwd(); const INSIGHTS = join(ROOT, "src/app/insights"); @@ -52,7 +53,10 @@ describe("Insights add actions", () => { }); it("the mood page has no text links into logging or the check-in", () => { - const source = readFileSync(join(INSIGHTS, "mood/page-client.tsx"), "utf8"); + const source = readSource( + "src/app/insights/mood/page-client.tsx", + " { it("links to /settings/integrations (a real settings slug)", () => { @@ -29,9 +30,9 @@ describe("sleep empty-state CTA href", () => { }); it("never links to the removed /settings/data-sources slug", () => { - const source = readFileSync( - join(process.cwd(), "src/app/insights/sleep/page.tsx"), - "utf8", + const source = readSource( + "src/app/insights/sleep/page.tsx", + " { it("links to /settings/integrations (a real settings slug)", () => { @@ -30,9 +31,9 @@ describe("sleep-overview empty-state CTA href", () => { }); it("never links to the non-existent /settings/devices slug", () => { - const source = readFileSync( - join(process.cwd(), "src/components/insights/sleep-overview.tsx"), - "utf8", + const source = readSource( + "src/components/insights/sleep-overview.tsx", + " carries no rules for a viewport it never shows on", () => // A touch screen at most 520 px tall gets the phone shell (the // `shell-mobile` variant), so the rail is hidden there and a class keyed // on that query can never apply. - const source = readFileSync( - join(__dirname, "..", "sidebar-nav.tsx"), - "utf8", + const source = readSource( + "src/components/layout/sidebar-nav.tsx", + "export function SidebarNav", ); expect(source).not.toMatch(/pointer:coarse\)_and_\(height<=520px\)/); }); diff --git a/src/components/onboarding/__tests__/confirm-failure-recovery.test.ts b/src/components/onboarding/__tests__/confirm-failure-recovery.test.ts index 2df83c41e..dc1e036b7 100644 --- a/src/components/onboarding/__tests__/confirm-failure-recovery.test.ts +++ b/src/components/onboarding/__tests__/confirm-failure-recovery.test.ts @@ -16,8 +16,6 @@ * component tests render server-side and cannot click. */ import { describe, expect, it, vi } from "vitest"; -import { readFileSync } from "node:fs"; -import { join } from "node:path"; import { defaultOnboardingSteps, @@ -27,6 +25,7 @@ import { import { runBaselineAttempt } from "../baseline-form"; import { runConfirmFinish } from "../confirm-screen"; +import { readSource } from "@/__tests__/helpers/source-files"; type PassableStep = Parameters< Parameters[0]["passStep"] @@ -92,10 +91,10 @@ describe("runBaselineAttempt", () => { it("is the form's only way to move the pending flag", () => { // A second, inline `setSaving(false)` is how the flag stopped being // reset on one of the three exits in the first place. - const source = readFileSync( - join(process.cwd(), "src/components/onboarding/baseline-form.tsx"), - "utf8", - ); + const source = readSource("src/components/onboarding/baseline-form.tsx", [ + "export function BaselineForm", + "setSaving", + ]); expect(source).not.toContain("setSaving(false)"); expect(source).not.toContain("setSaving(true)"); }); diff --git a/src/components/settings/__tests__/anamnesis-about-me-relocation.test.tsx b/src/components/settings/__tests__/anamnesis-about-me-relocation.test.tsx index 0383d285b..4eedaf5cb 100644 --- a/src/components/settings/__tests__/anamnesis-about-me-relocation.test.tsx +++ b/src/components/settings/__tests__/anamnesis-about-me-relocation.test.tsx @@ -1,11 +1,10 @@ import type React from "react"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; -import { readFileSync } from "node:fs"; -import { join } from "node:path"; import { I18nProvider } from "@/lib/i18n/context"; import { AnamnesisSection } from "../anamnesis-section"; +import { readSource } from "@/__tests__/helpers/source-files"; /** * #159 — the "About me" note and the free-text allergies line moved from the @@ -144,9 +143,9 @@ describe("Anamnese — the About-me note card (#159)", () => { describe("Account settings — the About-me card is gone (#159)", () => { it("the account section carries no about-me mount any more", () => { - const source = readFileSync( - join(process.cwd(), "src/components/settings/account-section/index.tsx"), - "utf8", + const source = readSource( + "src/components/settings/account-section/index.tsx", + "export function AccountSection", ); expect(source).not.toContain("AboutMeSection"); expect(source).not.toContain("about-me-section"); diff --git a/src/components/settings/__tests__/password-card-placement.test.tsx b/src/components/settings/__tests__/password-card-placement.test.tsx index 57618e9ff..d902a055a 100644 --- a/src/components/settings/__tests__/password-card-placement.test.tsx +++ b/src/components/settings/__tests__/password-card-placement.test.tsx @@ -41,6 +41,7 @@ vi.mock("@/hooks/use-auth", () => ({ import { PasswordCard } from "../password-card"; import { SecuritySection } from "../security-section"; +import { readSource } from "@/__tests__/helpers/source-files"; const SLOT = 'data-slot="settings-password-card"'; @@ -108,9 +109,9 @@ describe("change-password card placement", () => { }); it("left no password form behind in the Account section", () => { - const account = readFileSync( - join(ROOT, "src/components/settings/account-section/index.tsx"), - "utf8", + const account = readSource( + "src/components/settings/account-section/index.tsx", + "export function AccountSection", ); for (const marker of [ "settings.passwordTitle", diff --git a/src/lib/jobs/reminder/__tests__/integration-cohort-outcomes.test.ts b/src/lib/jobs/reminder/__tests__/integration-cohort-outcomes.test.ts index 644c43133..1916b6511 100644 --- a/src/lib/jobs/reminder/__tests__/integration-cohort-outcomes.test.ts +++ b/src/lib/jobs/reminder/__tests__/integration-cohort-outcomes.test.ts @@ -1,6 +1,7 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it, vi } from "vitest"; +import { readSource } from "@/__tests__/helpers/source-files"; type Provider = "google_health" | "withings"; type UserStatus = "complete" | "partial" | "failed" | "parked" | "skipped"; @@ -45,9 +46,9 @@ const googleHandlerSource = readFileSync( join(process.cwd(), "src/lib/jobs/reminder/google-health-sync.ts"), "utf8", ); -const googleSyncSource = readFileSync( - join(process.cwd(), "src/lib/google-health/sync.ts"), - "utf8", +const googleSyncSource = readSource( + "src/lib/google-health/sync.ts", + "export async function syncUserGoogleHealth(", ); const withingsHandlerSource = readFileSync( join(process.cwd(), "src/lib/jobs/reminder/withings-sync.ts"), From 2604c3e3c6207075e8e77f1b55915e576a6ba0e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 04:29:47 +0200 Subject: [PATCH 3/9] test(guards): ratchet source size, test realism and version-stamp comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a ratchet mechanism (helpers/ratchet.ts): per-key counts against a JSON baseline under src/__tests__/baselines/. A new key or a higher count fails; a lower count fails until it is written back with `RATCHET_WRITE=1 pnpm vitest run `, which only ever lowers numbers. Raising one is a hand edit in a reviewed change. The baselines are machine-written, so prettier leaves them alone. helpers/source-metrics.ts measures the production tree through the TypeScript compiler: the file list (dot directories included, tests and generated code out), line counts, every function node with a stable "outer > inner" name and its length, and the runtime import graph. Guards and their baselines, measured on this tree: - source-size-ratchet: 129 files over 800 lines and 415 functions over 200 lines (catalog and data files exempt, each named with its reason; slack 25 and 10 lines before a shrink must be written back). - test-realism-ratchet: 321 route tests mocking @/lib/db wholesale, 138 stubbing @/lib/api-handler, 6014 `as never` casts in 570 test files, and 400 test files that read a source file by path (reads through readSource are not counted). - version-stamp-ratchet: 10634 comment lines in 2985 files that open with a release version; none may be added. Proof each can fail: a planted 953-line file with a 253-line function failed both size ratchets, and one line appended to app/page-client.tsx failed the file ratchet (2326 → 2327); a planted route test with vi.mock("@/lib/db") and one `as never` failed both of those ratchets; a planted comment "// v1.45.0 — probe stamp" failed the stamp ratchet. Raising one as-never baseline entry by 5 failed with the write-back message, and RATCHET_WRITE=1 restored the file byte for byte. All probes were removed again. --- .prettierignore | 5 + .../baselines/route-test-db-mocks.json | 328 ++ .../baselines/route-test-handler-stubs.json | 145 + .../baselines/source-file-lines.json | 136 + .../baselines/source-function-lines.json | 422 +++ src/__tests__/baselines/test-as-never.json | 577 ++++ .../baselines/test-source-reads.json | 407 +++ .../baselines/version-stamp-comments.json | 2992 +++++++++++++++++ src/__tests__/helpers/ratchet.ts | 151 + src/__tests__/helpers/source-metrics.ts | 376 +++ src/__tests__/source-size-ratchet.test.ts | 168 + src/__tests__/test-realism-ratchet.test.ts | 196 ++ src/__tests__/version-stamp-ratchet.test.ts | 85 + 13 files changed, 5988 insertions(+) create mode 100644 src/__tests__/baselines/route-test-db-mocks.json create mode 100644 src/__tests__/baselines/route-test-handler-stubs.json create mode 100644 src/__tests__/baselines/source-file-lines.json create mode 100644 src/__tests__/baselines/source-function-lines.json create mode 100644 src/__tests__/baselines/test-as-never.json create mode 100644 src/__tests__/baselines/test-source-reads.json create mode 100644 src/__tests__/baselines/version-stamp-comments.json create mode 100644 src/__tests__/helpers/ratchet.ts create mode 100644 src/__tests__/helpers/source-metrics.ts create mode 100644 src/__tests__/source-size-ratchet.test.ts create mode 100644 src/__tests__/test-realism-ratchet.test.ts create mode 100644 src/__tests__/version-stamp-ratchet.test.ts diff --git a/.prettierignore b/.prettierignore index 68f690586..2dd095877 100644 --- a/.prettierignore +++ b/.prettierignore @@ -16,3 +16,8 @@ docs/api/openapi.yaml # Build-generated service worker (the PWA build emits it). public/sw.js + +# Ratchet baselines, written by the guards themselves (RATCHET_WRITE=1). The +# writer's JSON layout is the canonical one, so a write-back never fails the +# format check. +src/__tests__/baselines diff --git a/src/__tests__/baselines/route-test-db-mocks.json b/src/__tests__/baselines/route-test-db-mocks.json new file mode 100644 index 000000000..ff02e09a7 --- /dev/null +++ b/src/__tests__/baselines/route-test-db-mocks.json @@ -0,0 +1,328 @@ +{ + "$comment": "route tests that mock @/lib/db wholesale, per test file under src/. Maintained by src/__tests__/test-realism-ratchet.test.ts.", + "count": 321, + "total": 321, + "entries": { + "app/api/account/grants/__tests__/invite-access-level.test.ts": 1, + "app/api/account/grants/__tests__/invite-recent-proof.test.ts": 1, + "app/api/account/grants/__tests__/invite-scope-and-manage.test.ts": 1, + "app/api/activity/__tests__/routes.test.ts": 1, + "app/api/admin/ai-settings/__tests__/route.test.ts": 1, + "app/api/admin/audit-log/__tests__/route.test.ts": 1, + "app/api/admin/audit-log/actions/__tests__/route.test.ts": 1, + "app/api/admin/backups/__tests__/route.test.ts": 1, + "app/api/admin/backups/[id]/summary/__tests__/route.test.ts": 1, + "app/api/admin/import-apple-health-export/__tests__/route.test.ts": 1, + "app/api/admin/maintenance/measurements/__tests__/status.test.ts": 1, + "app/api/admin/maintenance/pulse-day-mean/__tests__/route.test.ts": 1, + "app/api/admin/notifications/diagnostic/__tests__/route.test.ts": 1, + "app/api/admin/notifications/health/__tests__/route.test.ts": 1, + "app/api/admin/notifications/reminder-check/__tests__/route.test.ts": 1, + "app/api/admin/notifications/test/__tests__/route.test.ts": 1, + "app/api/admin/provider-health/__tests__/route.test.ts": 1, + "app/api/admin/settings/__tests__/route.test.ts": 1, + "app/api/admin/settings/assistant-flags/__tests__/route.test.ts": 1, + "app/api/admin/settings/module-availability/__tests__/route.test.ts": 1, + "app/api/admin/settings/web-push-vapid/generate/__tests__/route.test.ts": 1, + "app/api/admin/status/__tests__/route.test.ts": 1, + "app/api/admin/support-report/__tests__/route.test.ts": 1, + "app/api/admin/users/__tests__/route.test.ts": 1, + "app/api/admin/users/[id]/__tests__/route.test.ts": 1, + "app/api/analytics/__tests__/route.test.ts": 1, + "app/api/anamnesis/facts/__tests__/idempotency-conflict.test.ts": 1, + "app/api/anamnesis/facts/__tests__/route.test.ts": 1, + "app/api/auth/claim/__tests__/route.test.ts": 1, + "app/api/auth/login/__tests__/constant-work.test.ts": 1, + "app/api/auth/login/__tests__/native-token.test.ts": 1, + "app/api/auth/login/__tests__/route.test.ts": 1, + "app/api/auth/login/__tests__/trusted-device.test.ts": 1, + "app/api/auth/me/ai-usage/__tests__/route.test.ts": 1, + "app/api/auth/me/coach-prefs/__tests__/route.test.ts": 1, + "app/api/auth/me/devices/__tests__/route.test.ts": 1, + "app/api/auth/me/devices/[id]/__tests__/route.test.ts": 1, + "app/api/auth/me/diabetes/__tests__/route.test.ts": 1, + "app/api/auth/me/disable-coach/__tests__/route.test.ts": 1, + "app/api/auth/me/disable-coach/__tests__/unknown-key-strict.test.ts": 1, + "app/api/auth/me/documents-auto-ai-read/__tests__/route.test.ts": 1, + "app/api/auth/me/glucose-unit/__tests__/route.test.ts": 1, + "app/api/auth/me/health-score-config/__tests__/route.test.ts": 1, + "app/api/auth/me/mfa/disable/__tests__/route.test.ts": 1, + "app/api/auth/me/mfa/webauthn/register/__tests__/route.test.ts": 1, + "app/api/auth/me/modules/__tests__/route.test.ts": 1, + "app/api/auth/me/notification-prefs/__tests__/route.test.ts": 1, + "app/api/auth/me/security-activity/__tests__/route.test.ts": 1, + "app/api/auth/me/sessions/__tests__/route.test.ts": 1, + "app/api/auth/me/source-priority/__tests__/route.test.ts": 1, + "app/api/auth/me/timezone/__tests__/route.test.ts": 1, + "app/api/auth/me/unit-preference/__tests__/route.test.ts": 1, + "app/api/auth/me/workout-routes/__tests__/route.test.ts": 1, + "app/api/auth/mfa/webauthn/verify/__tests__/route.test.ts": 1, + "app/api/auth/native/complete/__tests__/route.test.ts": 1, + "app/api/auth/native/login/__tests__/route.test.ts": 1, + "app/api/auth/native/token/__tests__/route.test.ts": 1, + "app/api/auth/oidc/callback/__tests__/route.test.ts": 1, + "app/api/auth/oidc/native/token/__tests__/route.test.ts": 1, + "app/api/auth/passkey/login-verify/__tests__/native-token.test.ts": 1, + "app/api/auth/passkey/login-verify/__tests__/oidc-only.test.ts": 1, + "app/api/auth/passkey/login-verify/__tests__/refusals.test.ts": 1, + "app/api/auth/passkeys/__tests__/route.test.ts": 1, + "app/api/auth/passkeys/[id]/__tests__/route.test.ts": 1, + "app/api/auth/password/__tests__/route.test.ts": 1, + "app/api/auth/register/__tests__/route.test.ts": 1, + "app/api/biomarkers/__tests__/error-contract.test.ts": 1, + "app/api/biomarkers/[id]/__tests__/delete-record-auth.test.ts": 1, + "app/api/biomarkers/[id]/__tests__/route.test.ts": 1, + "app/api/c/[token]/unlock/__tests__/route.test.ts": 1, + "app/api/coach/about-me/__tests__/route-concurrency.test.ts": 1, + "app/api/coach/about-me/__tests__/route-module-gate.test.ts": 1, + "app/api/coach/about-me/adopt/__tests__/route.test.ts": 1, + "app/api/coach/plans/__tests__/route.test.ts": 1, + "app/api/coach/reminders/__tests__/route.test.ts": 1, + "app/api/coach/suggested-actions/__tests__/route.test.ts": 1, + "app/api/consent/ai/__tests__/route.test.ts": 1, + "app/api/consent/ai/latest/__tests__/route.test.ts": 1, + "app/api/custom-metrics/__tests__/route.test.ts": 1, + "app/api/custom-metrics/[id]/__tests__/route.test.ts": 1, + "app/api/custom-metrics/[id]/entries/__tests__/note-encryption.test.ts": 1, + "app/api/custom-metrics/[id]/entries/__tests__/route.test.ts": 1, + "app/api/custom-metrics/[id]/entries/__tests__/soft-delete-restore.test.ts": 1, + "app/api/cycle/__tests__/external-id-floor.test.ts": 1, + "app/api/cycle/calendar/__tests__/day-entries.test.ts": 1, + "app/api/cycle/calendar/__tests__/verdict-round-trip.test.ts": 1, + "app/api/cycle/day-logs/bulk/__tests__/route.test.ts": 1, + "app/api/cycle/insights/__tests__/route-locale.test.ts": 1, + "app/api/cycle/symptoms/custom/__tests__/custom-crud.test.ts": 1, + "app/api/daily/digest/__tests__/route.test.ts": 1, + "app/api/daily/digest/dismiss/__tests__/route.test.ts": 1, + "app/api/dashboard/chart-overlay-prefs/__tests__/route.test.ts": 1, + "app/api/dashboard/snapshot/__tests__/route.test.ts": 1, + "app/api/dashboard/summary/__tests__/module-gate.test.ts": 1, + "app/api/dashboard/summary/__tests__/route.test.ts": 1, + "app/api/dashboard/widgets/__tests__/route.test.ts": 1, + "app/api/dawarich/places/__tests__/route.test.ts": 1, + "app/api/devices/__tests__/route.test.ts": 1, + "app/api/devices/[id]/__tests__/route.test.ts": 1, + "app/api/documents/inbound/__tests__/route.test.ts": 1, + "app/api/documents/inbound/[id]/__tests__/route.test.ts": 1, + "app/api/documents/inbound/[id]/chat/__tests__/route.test.ts": 1, + "app/api/documents/inbound/[id]/extract/__tests__/route.test.ts": 1, + "app/api/documents/inbound/[id]/index/__tests__/route.test.ts": 1, + "app/api/documents/inbound/[id]/original/__tests__/route.test.ts": 1, + "app/api/documents/inbound/[id]/suggest/__tests__/route.test.ts": 1, + "app/api/documents/inbound/[id]/summary/__tests__/route.test.ts": 1, + "app/api/documents/inbound/[id]/thumbnail/__tests__/route.test.ts": 1, + "app/api/documents/inbound/layout/__tests__/route.test.ts": 1, + "app/api/documents/inbound/reindex/__tests__/route.test.ts": 1, + "app/api/documents/inbound/usage/__tests__/route.test.ts": 1, + "app/api/encounters/procedures/__tests__/route.test.ts": 1, + "app/api/environment/backfill/__tests__/route.test.ts": 1, + "app/api/export/__tests__/measurement-streaming.test.ts": 1, + "app/api/export/__tests__/per-type-routes.test.ts": 1, + "app/api/export/__tests__/soft-delete-filter.test.ts": 1, + "app/api/export/encrypted/__tests__/route.test.ts": 1, + "app/api/export/health-record/__tests__/route.test.ts": 1, + "app/api/feature-flags/__tests__/route.test.ts": 1, + "app/api/fhir/__tests__/capability-and-everything.test.ts": 1, + "app/api/fhir/__tests__/module-gate.test.ts": 1, + "app/api/fitbit/connect/__tests__/route.test.ts": 1, + "app/api/fitbit/credentials/__tests__/route.test.ts": 1, + "app/api/gamification/achievements/__tests__/hidden-redaction.test.ts": 1, + "app/api/gamification/achievements/__tests__/ios-format.test.ts": 1, + "app/api/gamification/achievements/__tests__/module-gate.test.ts": 1, + "app/api/gamification/achievements/__tests__/writes-nothing.test.ts": 1, + "app/api/google-health/credentials/__tests__/route.test.ts": 1, + "app/api/health/__tests__/route.test.ts": 1, + "app/api/illness/episodes/[id]/__tests__/body-site.test.ts": 1, + "app/api/illness/episodes/[id]/day-logs/__tests__/route.test.ts": 1, + "app/api/illness/episodes/[id]/restore/__tests__/route.test.ts": 1, + "app/api/import/__tests__/route.test.ts": 1, + "app/api/import/apple-health-export/__tests__/route.test.ts": 1, + "app/api/import/apple-health-export/[jobId]/status/__tests__/route.test.ts": 1, + "app/api/import/csv/__tests__/route.test.ts": 1, + "app/api/import/health-connect-export/__tests__/route.test.ts": 1, + "app/api/ingest/medication/__tests__/route.test.ts": 1, + "app/api/insights/biomarker-assessment/__tests__/route.test.ts": 1, + "app/api/insights/blood-pressure-status/__tests__/route.test.ts": 1, + "app/api/insights/cards/__tests__/route.test.ts": 1, + "app/api/insights/chat/__tests__/golden-transcripts.test.ts": 1, + "app/api/insights/chat/__tests__/route-activity.test.ts": 1, + "app/api/insights/chat/__tests__/route-cancelled-turn.test.ts": 1, + "app/api/insights/chat/__tests__/route-capability-gate.test.ts": 1, + "app/api/insights/chat/__tests__/route-fence-guard.test.ts": 1, + "app/api/insights/chat/__tests__/route-followups.test.ts": 1, + "app/api/insights/chat/__tests__/route-guard-activation.test.ts": 1, + "app/api/insights/chat/__tests__/route-guard-ledger.test.ts": 1, + "app/api/insights/chat/__tests__/route-guard.test.ts": 1, + "app/api/insights/chat/__tests__/route-list-query.test.ts": 1, + "app/api/insights/chat/__tests__/route-snapshot-once.test.ts": 1, + "app/api/insights/chat/__tests__/route-tool-mode.test.ts": 1, + "app/api/insights/chat/__tests__/route-workout-scope.test.ts": 1, + "app/api/insights/chat/[id]/attachments/__tests__/route.test.ts": 1, + "app/api/insights/chat/[id]/messages/[messageId]/trail/__tests__/route.test.ts": 1, + "app/api/insights/chat/fenced/__tests__/route.test.ts": 1, + "app/api/insights/coach-read/__tests__/route.test.ts": 1, + "app/api/insights/coach/facts/__tests__/route-memory.test.ts": 1, + "app/api/insights/coach/facts/__tests__/route.test.ts": 1, + "app/api/insights/coach/nudge-status/__tests__/route.test.ts": 1, + "app/api/insights/comprehensive/__tests__/route.test.ts": 1, + "app/api/insights/correlations/__tests__/route.test.ts": 1, + "app/api/insights/derived/__tests__/route.test.ts": 1, + "app/api/insights/derived/batch/__tests__/route.test.ts": 1, + "app/api/insights/ecg/__tests__/route.test.ts": 1, + "app/api/insights/ecg/[id]/__tests__/route.test.ts": 1, + "app/api/insights/generate/__tests__/route.test.ts": 1, + "app/api/insights/glp1-timeline/__tests__/route.test.ts": 1, + "app/api/insights/layout/__tests__/route.test.ts": 1, + "app/api/insights/metric-status/__tests__/route.test.ts": 1, + "app/api/insights/mood-status/__tests__/route.test.ts": 1, + "app/api/insights/patterns/__tests__/route.test.ts": 1, + "app/api/insights/patterns/[id]/__tests__/route.test.ts": 1, + "app/api/insights/pregenerate/__tests__/route.test.ts": 1, + "app/api/insights/provider-chain/__tests__/put.test.ts": 1, + "app/api/insights/provider-chain/__tests__/route.test.ts": 1, + "app/api/insights/rhythm-events/__tests__/route.test.ts": 1, + "app/api/insights/targets/__tests__/route.test.ts": 1, + "app/api/integrations/fitbit/test/__tests__/route.test.ts": 1, + "app/api/integrations/healthkit/__tests__/route.test.ts": 1, + "app/api/integrations/status/__tests__/route.test.ts": 1, + "app/api/integrations/whoop/test/__tests__/route.test.ts": 1, + "app/api/integrations/withings/test/__tests__/route.test.ts": 1, + "app/api/internal/deploy-webhook/__tests__/route.test.ts": 1, + "app/api/labs/__tests__/error-contract.test.ts": 1, + "app/api/labs/ocr/commit/__tests__/route.test.ts": 1, + "app/api/labs/ocr/extract/__tests__/route.test.ts": 1, + "app/api/labs/restore/__tests__/route.test.ts": 1, + "app/api/mcp/oauth/__tests__/authorize.test.ts": 1, + "app/api/mcp/oauth/__tests__/token.test.ts": 1, + "app/api/mcp/tokens/__tests__/route.test.ts": 1, + "app/api/measurement-reminders/[id]/__tests__/route.test.ts": 1, + "app/api/measurement-reminders/[id]/__tests__/skip-snooze-history.test.ts": 1, + "app/api/measurement-reminders/[id]/complete/__tests__/route.test.ts": 1, + "app/api/measurement-reminders/[id]/satisfy/__tests__/route.test.ts": 1, + "app/api/measurements/__tests__/arrival-wiring.test.ts": 1, + "app/api/measurements/__tests__/group-by-day.test.ts": 1, + "app/api/measurements/__tests__/put-duplicate-timestamp.test.ts": 1, + "app/api/measurements/__tests__/range-aggregation-route.test.ts": 1, + "app/api/measurements/__tests__/zod-multi-issue.test.ts": 1, + "app/api/measurements/[id]/__tests__/route.test.ts": 1, + "app/api/measurements/batch/__tests__/aggregated-hr-wire.test.ts": 1, + "app/api/measurements/batch/__tests__/duplicate-prefilter.test.ts": 1, + "app/api/measurements/batch/__tests__/hr-bucket-observability.test.ts": 1, + "app/api/measurements/batch/__tests__/insert-returning-arrivals.test.ts": 1, + "app/api/measurements/batch/__tests__/issue-paths.test.ts": 1, + "app/api/measurements/batch/__tests__/pr-detection-hook.test.ts": 1, + "app/api/measurements/batch/__tests__/refusal-detail.test.ts": 1, + "app/api/measurements/batch/__tests__/sync-trigger.test.ts": 1, + "app/api/measurements/batch/__tests__/timestamp-plausibility.test.ts": 1, + "app/api/measurements/batch/__tests__/unstable-external-id.test.ts": 1, + "app/api/measurements/bulk-delete/__tests__/route.test.ts": 1, + "app/api/measurements/by-external-ids/__tests__/route.test.ts": 1, + "app/api/measurements/restore/__tests__/route.test.ts": 1, + "app/api/measurements/series/__tests__/route.test.ts": 1, + "app/api/measurements/series/__tests__/unknown-query-param-open.test.ts": 1, + "app/api/medications/__tests__/route.test.ts": 1, + "app/api/medications/[id]/__tests__/route.test.ts": 1, + "app/api/medications/[id]/cadence/__tests__/route.test.ts": 1, + "app/api/medications/[id]/compliance/__tests__/route.test.ts": 1, + "app/api/medications/[id]/dose-history/__tests__/route.test.ts": 1, + "app/api/medications/[id]/glp1/__tests__/route.test.ts": 1, + "app/api/medications/[id]/intake/__tests__/route.test.ts": 1, + "app/api/medications/[id]/intake/__tests__/source-provenance.test.ts": 1, + "app/api/medications/[id]/intake/[eventId]/__tests__/route.test.ts": 1, + "app/api/medications/[id]/intake/bulk-delete/__tests__/route.test.ts": 1, + "app/api/medications/[id]/intake/import/__tests__/route.test.ts": 1, + "app/api/medications/[id]/intake/import/[jobId]/status/__tests__/route.test.ts": 1, + "app/api/medications/[id]/intake/purge/__tests__/route.test.ts": 1, + "app/api/medications/[id]/inventory/__tests__/route.test.ts": 1, + "app/api/medications/[id]/inventory/[itemId]/__tests__/route.test.ts": 1, + "app/api/medications/[id]/phase-config/__tests__/route.test.ts": 1, + "app/api/medications/[id]/schedule-revisions/__tests__/route.test.ts": 1, + "app/api/medications/[id]/side-effects/__tests__/route.test.ts": 1, + "app/api/medications/compliance/__tests__/route.test.ts": 1, + "app/api/medications/intake/__tests__/route.test.ts": 1, + "app/api/medications/intake/bulk/__tests__/dose-arrival-scope.test.ts": 1, + "app/api/medications/intake/bulk/__tests__/route.test.ts": 1, + "app/api/medications/intake/dose-history-import/__tests__/route.test.ts": 1, + "app/api/medications/layout/__tests__/route.test.ts": 1, + "app/api/mental-health/assessments/__tests__/route.test.ts": 1, + "app/api/mental-health/assessments/[id]/__tests__/route.test.ts": 1, + "app/api/meta/capabilities/__tests__/route.test.ts": 1, + "app/api/monitoring/glitchtip/__tests__/route.test.ts": 1, + "app/api/mood-entries/__tests__/route.test.ts": 1, + "app/api/mood-entries/[id]/__tests__/route.test.ts": 1, + "app/api/mood-entries/bulk-delete/__tests__/route.test.ts": 1, + "app/api/mood-entries/bulk/__tests__/route.test.ts": 1, + "app/api/mood-entries/restore/__tests__/route.test.ts": 1, + "app/api/mood/analytics/__tests__/route.test.ts": 1, + "app/api/mood/tags/__tests__/custom-crud.test.ts": 1, + "app/api/mood/tags/__tests__/groups-crud.test.ts": 1, + "app/api/mood/tags/__tests__/layout.test.ts": 1, + "app/api/mood/tags/__tests__/route.test.ts": 1, + "app/api/mood/tags/layout/__tests__/route.test.ts": 1, + "app/api/nightscout/connect/__tests__/route.test.ts": 1, + "app/api/notifications/action/[token]/__tests__/route.test.ts": 1, + "app/api/notifications/status/__tests__/route.test.ts": 1, + "app/api/notifications/web-push/__tests__/route.test.ts": 1, + "app/api/notifications/web-push/test/__tests__/route.test.ts": 1, + "app/api/nutrients/__tests__/route.test.ts": 1, + "app/api/nutrients/batch/__tests__/route.test.ts": 1, + "app/api/nutrients/daily/__tests__/route.test.ts": 1, + "app/api/nutrients/water/__tests__/route.test.ts": 1, + "app/api/onboarding/__tests__/error-contract.test.ts": 1, + "app/api/onboarding/complete/__tests__/route.test.ts": 1, + "app/api/oura/callback/__tests__/route.test.ts": 1, + "app/api/oura/credentials/__tests__/route.test.ts": 1, + "app/api/oura/disconnect/__tests__/route.test.ts": 1, + "app/api/oura/status/__tests__/route.test.ts": 1, + "app/api/personal-records/__tests__/route.test.ts": 1, + "app/api/polar/callback/__tests__/route.test.ts": 1, + "app/api/polar/credentials/__tests__/route.test.ts": 1, + "app/api/polar/disconnect/__tests__/route.test.ts": 1, + "app/api/polar/status/__tests__/route.test.ts": 1, + "app/api/search/__tests__/route.test.ts": 1, + "app/api/settings/__tests__/channel-enabled-routes.test.ts": 1, + "app/api/settings/__tests__/channel-global-switch-refusal.test.ts": 1, + "app/api/settings/__tests__/email-test-route.test.ts": 1, + "app/api/settings/__tests__/private-origin-routes.test.ts": 1, + "app/api/settings/__tests__/telegram-test-locale.test.ts": 1, + "app/api/settings/__tests__/webhook-format-route.test.ts": 1, + "app/api/settings/reminder-thresholds/__tests__/route.test.ts": 1, + "app/api/settings/webhook/test/__tests__/route.test.ts": 1, + "app/api/share-links/__tests__/route.test.ts": 1, + "app/api/sleep/night/__tests__/route.test.ts": 1, + "app/api/sleep/rhythm/__tests__/route.test.ts": 1, + "app/api/strava/callback/__tests__/route.test.ts": 1, + "app/api/strava/credentials/__tests__/route.test.ts": 1, + "app/api/strava/disconnect/__tests__/route.test.ts": 1, + "app/api/sync/changes/__tests__/module-gate.test.ts": 1, + "app/api/sync/state/__tests__/route.test.ts": 1, + "app/api/telegram/webhook/__tests__/route.test.ts": 1, + "app/api/tokens/__tests__/route.test.ts": 1, + "app/api/tokens/documents/__tests__/route.test.ts": 1, + "app/api/tokens/measurements/__tests__/route.test.ts": 1, + "app/api/tokens/workouts/__tests__/route.test.ts": 1, + "app/api/user/ai-provider/__tests__/legacy-private-host.test.ts": 1, + "app/api/user/ai-provider/__tests__/route.test.ts": 1, + "app/api/user/avatar/__tests__/route.test.ts": 1, + "app/api/user/avatar/[id]/__tests__/route.test.ts": 1, + "app/api/user/profile/__tests__/route.test.ts": 1, + "app/api/user/thresholds/__tests__/route.test.ts": 1, + "app/api/whoop/callback/__tests__/route.test.ts": 1, + "app/api/whoop/connect/__tests__/route.test.ts": 1, + "app/api/whoop/credentials/__tests__/route.test.ts": 1, + "app/api/whoop/disconnect/__tests__/route.test.ts": 1, + "app/api/whoop/status/__tests__/route.test.ts": 1, + "app/api/whoop/webhook/[token]/__tests__/route.test.ts": 1, + "app/api/withings/callback/__tests__/route.test.ts": 1, + "app/api/withings/connect/__tests__/route.test.ts": 1, + "app/api/withings/credentials/__tests__/route.test.ts": 1, + "app/api/withings/webhook/__tests__/route.test.ts": 1, + "app/api/withings/webhook/[token]/__tests__/route.test.ts": 1, + "app/api/workouts/__tests__/batch-create.test.ts": 1, + "app/api/workouts/__tests__/batch-scope.test.ts": 1, + "app/api/workouts/__tests__/canonical-dedup.test.ts": 1, + "app/api/workouts/__tests__/delete.test.ts": 1, + "app/api/workouts/__tests__/detail.test.ts": 1, + "app/api/workouts/batch/__tests__/unstable-external-id.test.ts": 1 + } +} diff --git a/src/__tests__/baselines/route-test-handler-stubs.json b/src/__tests__/baselines/route-test-handler-stubs.json new file mode 100644 index 000000000..23fe2efea --- /dev/null +++ b/src/__tests__/baselines/route-test-handler-stubs.json @@ -0,0 +1,145 @@ +{ + "$comment": "route tests that stub @/lib/api-handler, per test file under src/. Maintained by src/__tests__/test-realism-ratchet.test.ts.", + "count": 138, + "total": 138, + "entries": { + "app/api/account/grants/__tests__/invite-access-level.test.ts": 1, + "app/api/account/grants/__tests__/invite-scope-and-manage.test.ts": 1, + "app/api/activity/__tests__/routes.test.ts": 1, + "app/api/admin/ai-settings/__tests__/route.test.ts": 1, + "app/api/admin/app-logs/__tests__/route.test.ts": 1, + "app/api/admin/audit-log/__tests__/route.test.ts": 1, + "app/api/admin/audit-log/actions/__tests__/route.test.ts": 1, + "app/api/admin/backups/__tests__/route.test.ts": 1, + "app/api/admin/backups/[id]/summary/__tests__/route.test.ts": 1, + "app/api/admin/notifications/diagnostic/__tests__/route.test.ts": 1, + "app/api/admin/notifications/health/__tests__/route.test.ts": 1, + "app/api/admin/notifications/reminder-check/__tests__/route.test.ts": 1, + "app/api/admin/notifications/test/__tests__/route.test.ts": 1, + "app/api/admin/provider-health/__tests__/route.test.ts": 1, + "app/api/admin/settings/__tests__/route.test.ts": 1, + "app/api/admin/settings/web-push-vapid/generate/__tests__/route.test.ts": 1, + "app/api/admin/status/__tests__/route.test.ts": 1, + "app/api/admin/support-report/__tests__/route.test.ts": 1, + "app/api/admin/users/__tests__/route.test.ts": 1, + "app/api/admin/users/[id]/__tests__/route.test.ts": 1, + "app/api/ai/test/__tests__/route.test.ts": 1, + "app/api/anamnesis/facts/__tests__/route.test.ts": 1, + "app/api/auth/me/mfa/disable/__tests__/route.test.ts": 1, + "app/api/auth/me/mfa/webauthn/register/__tests__/route.test.ts": 1, + "app/api/auth/mfa/webauthn/verify/__tests__/route.test.ts": 1, + "app/api/auth/native/complete/__tests__/route.test.ts": 1, + "app/api/auth/native/login/__tests__/route.test.ts": 1, + "app/api/auth/native/token/__tests__/route.test.ts": 1, + "app/api/auth/oidc/callback/__tests__/route.test.ts": 1, + "app/api/auth/oidc/login/__tests__/route.test.ts": 1, + "app/api/auth/oidc/native/token/__tests__/route.test.ts": 1, + "app/api/auth/passkeys/__tests__/route.test.ts": 1, + "app/api/auth/passkeys/[id]/__tests__/route.test.ts": 1, + "app/api/auth/password/__tests__/route.test.ts": 1, + "app/api/biomarkers/[id]/__tests__/delete-record-auth.test.ts": 1, + "app/api/coach/about-me/__tests__/route-concurrency.test.ts": 1, + "app/api/coach/about-me/__tests__/route-module-gate.test.ts": 1, + "app/api/coach/about-me/adopt/__tests__/route.test.ts": 1, + "app/api/coach/about-me/questions/__tests__/route-module-gate.test.ts": 1, + "app/api/dawarich/places/__tests__/route.test.ts": 1, + "app/api/documents/inbound/usage/__tests__/route.test.ts": 1, + "app/api/encounters/procedures/__tests__/route.test.ts": 1, + "app/api/environment/backfill/__tests__/route.test.ts": 1, + "app/api/fitbit/connect/__tests__/route.test.ts": 1, + "app/api/fitbit/credentials/__tests__/route.test.ts": 1, + "app/api/fitbit/sync/__tests__/route.test.ts": 1, + "app/api/google-health/credentials/__tests__/route.test.ts": 1, + "app/api/google-health/sync/__tests__/route.test.ts": 1, + "app/api/google-health/sync/status/__tests__/route.test.ts": 1, + "app/api/health/__tests__/route.test.ts": 1, + "app/api/import/__tests__/record-csv-routes.test.ts": 1, + "app/api/import/__tests__/route.test.ts": 1, + "app/api/import/csv/__tests__/route.test.ts": 1, + "app/api/insights/chat/__tests__/golden-transcripts.test.ts": 1, + "app/api/insights/chat/__tests__/route-activity.test.ts": 1, + "app/api/insights/chat/__tests__/route-cancelled-turn.test.ts": 1, + "app/api/insights/chat/__tests__/route-capability-gate.test.ts": 1, + "app/api/insights/chat/__tests__/route-fence-guard.test.ts": 1, + "app/api/insights/chat/__tests__/route-followups.test.ts": 1, + "app/api/insights/chat/__tests__/route-guard-activation.test.ts": 1, + "app/api/insights/chat/__tests__/route-guard-ledger.test.ts": 1, + "app/api/insights/chat/__tests__/route-guard.test.ts": 1, + "app/api/insights/chat/__tests__/route-list-query.test.ts": 1, + "app/api/insights/chat/__tests__/route-snapshot-once.test.ts": 1, + "app/api/insights/chat/__tests__/route-tool-mode.test.ts": 1, + "app/api/insights/chat/__tests__/route-workout-scope.test.ts": 1, + "app/api/insights/chat/[id]/__tests__/route-patch.test.ts": 1, + "app/api/insights/chat/[id]/messages/[messageId]/results/__tests__/route.test.ts": 1, + "app/api/insights/chat/[id]/messages/[messageId]/trail/__tests__/route.test.ts": 1, + "app/api/insights/coach/nudge-status/__tests__/route.test.ts": 1, + "app/api/insights/generate/__tests__/route.test.ts": 1, + "app/api/insights/glp1-timeline/__tests__/route.test.ts": 1, + "app/api/insights/provider-chain/__tests__/put.test.ts": 1, + "app/api/insights/provider-chain/__tests__/route.test.ts": 1, + "app/api/integrations/fitbit/test/__tests__/route.test.ts": 1, + "app/api/integrations/status/__tests__/route.test.ts": 1, + "app/api/integrations/whoop/test/__tests__/route.test.ts": 1, + "app/api/integrations/withings/resume/__tests__/route.test.ts": 1, + "app/api/integrations/withings/test/__tests__/route.test.ts": 1, + "app/api/internal/deploy-webhook/__tests__/route.test.ts": 1, + "app/api/labs/ocr/extract/__tests__/route.test.ts": 1, + "app/api/measurement-reminders/[id]/__tests__/route.test.ts": 1, + "app/api/measurement-reminders/[id]/__tests__/skip-snooze-history.test.ts": 1, + "app/api/measurement-reminders/[id]/complete/__tests__/route.test.ts": 1, + "app/api/measurement-reminders/[id]/satisfy/__tests__/route.test.ts": 1, + "app/api/measurements/__tests__/arrival-wiring.test.ts": 1, + "app/api/medications/extract/__tests__/route.test.ts": 1, + "app/api/medications/intake/bulk/__tests__/dose-arrival-scope.test.ts": 1, + "app/api/monitoring/glitchtip/__tests__/route.test.ts": 1, + "app/api/monitoring/umami-script/__tests__/route.test.ts": 1, + "app/api/monitoring/umami/[...path]/__tests__/route.test.ts": 1, + "app/api/nightscout/connect/__tests__/route.test.ts": 1, + "app/api/nightscout/sync/__tests__/route.test.ts": 1, + "app/api/notifications/web-push/__tests__/route.test.ts": 1, + "app/api/notifications/web-push/test/__tests__/route.test.ts": 1, + "app/api/oura/callback/__tests__/route.test.ts": 1, + "app/api/oura/connect/__tests__/route.test.ts": 1, + "app/api/oura/credentials/__tests__/route.test.ts": 1, + "app/api/oura/disconnect/__tests__/route.test.ts": 1, + "app/api/oura/status/__tests__/route.test.ts": 1, + "app/api/oura/sync/__tests__/route.test.ts": 1, + "app/api/polar/callback/__tests__/route.test.ts": 1, + "app/api/polar/connect/__tests__/route.test.ts": 1, + "app/api/polar/credentials/__tests__/route.test.ts": 1, + "app/api/polar/disconnect/__tests__/route.test.ts": 1, + "app/api/polar/status/__tests__/route.test.ts": 1, + "app/api/polar/sync/__tests__/route.test.ts": 1, + "app/api/record-settings/integrations/[provider]/__tests__/route.test.ts": 1, + "app/api/search/__tests__/route.test.ts": 1, + "app/api/settings/__tests__/telegram-test-locale.test.ts": 1, + "app/api/strava/callback/__tests__/route.test.ts": 1, + "app/api/strava/connect/__tests__/route.test.ts": 1, + "app/api/strava/credentials/__tests__/route.test.ts": 1, + "app/api/strava/disconnect/__tests__/route.test.ts": 1, + "app/api/strava/sync/__tests__/route.test.ts": 1, + "app/api/sync/state/__tests__/route.test.ts": 1, + "app/api/telegram/webhook/__tests__/route.test.ts": 1, + "app/api/tokens/__tests__/route.test.ts": 1, + "app/api/tokens/documents/__tests__/route.test.ts": 1, + "app/api/tokens/measurements/__tests__/route.test.ts": 1, + "app/api/tokens/workouts/__tests__/route.test.ts": 1, + "app/api/user/ai-provider/__tests__/legacy-private-host.test.ts": 1, + "app/api/user/ai-provider/__tests__/route.test.ts": 1, + "app/api/version/__tests__/route.test.ts": 1, + "app/api/version/check-updates/__tests__/route.test.ts": 1, + "app/api/whoop/callback/__tests__/route.test.ts": 1, + "app/api/whoop/connect/__tests__/route.test.ts": 1, + "app/api/whoop/connect/ticket/__tests__/route.test.ts": 1, + "app/api/whoop/credentials/__tests__/route.test.ts": 1, + "app/api/whoop/disconnect/__tests__/route.test.ts": 1, + "app/api/whoop/status/__tests__/route.test.ts": 1, + "app/api/whoop/sync/__tests__/route.test.ts": 1, + "app/api/whoop/webhook/[token]/__tests__/route.test.ts": 1, + "app/api/withings/callback/__tests__/route.test.ts": 1, + "app/api/withings/connect/__tests__/route.test.ts": 1, + "app/api/withings/sync/__tests__/route.test.ts": 1, + "app/api/withings/webhook/__tests__/route.test.ts": 1, + "app/api/withings/webhook/[token]/__tests__/route.test.ts": 1 + } +} diff --git a/src/__tests__/baselines/source-file-lines.json b/src/__tests__/baselines/source-file-lines.json new file mode 100644 index 000000000..43b2d9c5c --- /dev/null +++ b/src/__tests__/baselines/source-file-lines.json @@ -0,0 +1,136 @@ +{ + "$comment": "Production files over 800 lines and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.", + "count": 129, + "total": 156466, + "entries": { + "app/api/dashboard/summary/route.ts": 1032, + "app/api/documents/inbound/route.ts": 811, + "app/api/insights/generate/route.ts": 1209, + "app/api/measurements/batch/route.ts": 1576, + "app/api/measurements/route.ts": 938, + "app/api/measurements/series/route.ts": 879, + "app/api/medications/[id]/route.ts": 1281, + "app/api/medications/intake/bulk/route.ts": 1044, + "app/api/workouts/batch/route.ts": 971, + "app/medications/page-client.tsx": 839, + "app/page-client.tsx": 2326, + "components/admin/backups-section.tsx": 1462, + "components/charts/health-chart.tsx": 2517, + "components/charts/mood-chart.tsx": 1166, + "components/cycle/log-day-sheet.tsx": 1639, + "components/documents/document-detail-sheet.tsx": 1305, + "components/documents/documents-view.tsx": 1212, + "components/insights/coach-panel/chat-bubble.tsx": 888, + "components/insights/coach-panel/coach-conversation.tsx": 1286, + "components/insights/coach-panel/use-coach.ts": 1486, + "components/insights/derived/vitals-dashboard.tsx": 1033, + "components/insights/health-score-card.tsx": 1023, + "components/insights/insights-tab-strip.tsx": 1086, + "components/layout/sidebar-nav.tsx": 858, + "components/measurement-reminders/vorsorge-section.tsx": 1536, + "components/measurements/measurement-form.tsx": 815, + "components/measurements/measurement-list.tsx": 2019, + "components/medications/detail/medication-detail-tabs.tsx": 1176, + "components/medications/dose-history-ledger.tsx": 1052, + "components/medications/medication-table.tsx": 853, + "components/medications/wizard/wizard-payload.ts": 1414, + "components/mood/mood-list.tsx": 1408, + "components/records/life-circumstances-manager.tsx": 1080, + "components/settings/dashboard-layout-section.tsx": 1033, + "components/settings/managed-record-settings-section.tsx": 1164, + "components/timeline/timeline-chart.tsx": 1174, + "components/timeline/timeline-geometry.ts": 1395, + "lib/ai/anthropic-client.ts": 804, + "lib/ai/coach/about-me.ts": 1012, + "lib/ai/coach/coach-prose-grounding.ts": 1148, + "lib/ai/coach/persistence.ts": 1219, + "lib/ai/coach/results/metric-table-tool.ts": 1102, + "lib/ai/coach/snapshot.ts": 1362, + "lib/ai/coach/stream-events.ts": 817, + "lib/ai/coach/tools/executor.ts": 1523, + "lib/ai/coach/turn/model.ts": 820, + "lib/ai/coach/types.ts": 1011, + "lib/ai/codex-client.ts": 1094, + "lib/ai/prompts/insight-generator.ts": 1121, + "lib/ai/provider.ts": 1644, + "lib/ai/safety/outbound-screen.ts": 1463, + "lib/analytics/score/reader.ts": 998, + "lib/analytics/sleep-night.ts": 1345, + "lib/analytics/summaries-slice.ts": 1314, + "lib/api-handler.ts": 1978, + "lib/auth/session.ts": 811, + "lib/cache/server-cache.ts": 851, + "lib/cycle/prediction.ts": 883, + "lib/daily/digest.ts": 1317, + "lib/daily/load-digest.ts": 1000, + "lib/daily/today-overview.ts": 838, + "lib/dashboard-layout.ts": 971, + "lib/dashboard/snapshot.ts": 1549, + "lib/day/records.ts": 1175, + "lib/doctor-report/collect.ts": 803, + "lib/export/backup-plan.ts": 1089, + "lib/export/coach-backup.ts": 1070, + "lib/export/full-backup-payload.ts": 1520, + "lib/export/profile-backup.ts": 1019, + "lib/export/records-backup.ts": 834, + "lib/export/restore-backup.ts": 3208, + "lib/fitbit/client.ts": 1529, + "lib/fitbit/sync-core.ts": 968, + "lib/gamification/achievements-result.ts": 1094, + "lib/gamification/achievements.ts": 913, + "lib/google-health/client.ts": 1875, + "lib/google-health/mappers.ts": 1295, + "lib/google-health/sync-core.ts": 1634, + "lib/idempotency.ts": 879, + "lib/illness/correlation.ts": 1059, + "lib/import/health-connect/import.ts": 1689, + "lib/import/unzip-export-xml.ts": 825, + "lib/insights/comprehensive-aggregator.ts": 943, + "lib/insights/comprehensive-generate.ts": 1669, + "lib/insights/correlation-channel-series.ts": 1339, + "lib/insights/correlation-discovery.ts": 1240, + "lib/insights/correlations.ts": 1026, + "lib/insights/features.ts": 2009, + "lib/insights/mood-aggregates.ts": 1055, + "lib/insights/mood-status.ts": 814, + "lib/insights/status-cache.ts": 812, + "lib/insights/strain-score.ts": 832, + "lib/integrations/status.ts": 1445, + "lib/jobs/apple-health-import-worker.ts": 863, + "lib/jobs/backup-restore.ts": 888, + "lib/jobs/coach-nudge.ts": 1089, + "lib/jobs/insight-pregenerate.ts": 1266, + "lib/jobs/medication-intake-import.ts": 822, + "lib/jobs/offhost-backup.ts": 1168, + "lib/jobs/reaction-line.ts": 824, + "lib/jobs/reminder/medication-reminder-check.ts": 818, + "lib/jobs/reminder/register-integration-sync.ts": 1051, + "lib/jobs/reminder/register-maintenance.ts": 2197, + "lib/jobs/reminder/register-rollup.ts": 1156, + "lib/jobs/reminder/register-status.ts": 878, + "lib/managed-profiles/handover.ts": 986, + "lib/mcp/record-search.ts": 1467, + "lib/mcp/rich-reads.ts": 1468, + "lib/mcp/tools.ts": 2654, + "lib/measurements/dense-intraday-retention.ts": 1057, + "lib/measurements/import-apple-health-export.ts": 1729, + "lib/medications/scheduling/cadence.ts": 978, + "lib/medications/scheduling/recurrence.ts": 1108, + "lib/medications/scheduling/slot-upsert.ts": 926, + "lib/notifications/senders/apns.ts": 1100, + "lib/oura/client.ts": 1360, + "lib/oura/sync.ts": 917, + "lib/polar/client.ts": 1010, + "lib/rollups/measurement-rollups.ts": 1496, + "lib/search/search-records.ts": 996, + "lib/sharing/grants.ts": 1046, + "lib/support/report.ts": 905, + "lib/telegram-webhook-handlers.ts": 1384, + "lib/validations/backup.ts": 2418, + "lib/validations/inbound-documents.ts": 1130, + "lib/whoop/client.ts": 917, + "lib/withings/sync-sleep.ts": 903, + "lib/withings/sync.ts": 978, + "proxy.ts": 870 + } +} diff --git a/src/__tests__/baselines/source-function-lines.json b/src/__tests__/baselines/source-function-lines.json new file mode 100644 index 000000000..02291aeae --- /dev/null +++ b/src/__tests__/baselines/source-function-lines.json @@ -0,0 +1,422 @@ +{ + "$comment": "Production functions over 200 lines, keyed \"file :: name\", and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.", + "count": 415, + "total": 154643, + "entries": { + "app/achievements/page.tsx :: AchievementsPage": 206, + "app/api/admin/backups/upload/route.ts :: apiHandler(arg)": 314, + "app/api/admin/notifications/reminder-check/route.ts :: apiHandler(arg)": 208, + "app/api/admin/settings/route.ts :: apiHandler(arg) #2": 217, + "app/api/analytics/route.ts :: buildAnalyticsResponse": 446, + "app/api/auth/login/route.ts :: apiHandler(arg)": 262, + "app/api/auth/me/route.ts :: apiHandler(arg)": 386, + "app/api/auth/oidc/callback/route.ts :: apiHandler(arg)": 499, + "app/api/auth/register/route.ts :: apiHandler(arg)": 275, + "app/api/coach/about-me/route.ts :: apiHandler(arg) #2": 309, + "app/api/cycle/calendar/route.ts :: apiHandler(arg)": 230, + "app/api/cycle/day-logs/bulk/route.ts :: postBulk": 213, + "app/api/cycle/insights/route.ts :: apiHandler(arg)": 226, + "app/api/dashboard/summary/route.ts :: buildDashboardSummary": 606, + "app/api/devices/route.ts :: apiHandler(arg)": 237, + "app/api/documents/inbound/bulk/route.ts :: apiHandler(arg)": 212, + "app/api/documents/inbound/route.ts :: processUpload": 309, + "app/api/encounters/[id]/route.ts :: apiHandler(arg) #2": 275, + "app/api/environment/route.ts :: apiHandler(arg)": 201, + "app/api/export/health-record/route.ts :: apiHandler(arg)": 246, + "app/api/import/csv/route.ts :: apiHandler(arg)": 407, + "app/api/import/route.ts :: apiHandler(arg)": 364, + "app/api/ingest/medication/route.ts :: apiHandler(arg)": 269, + "app/api/insights/cards/route.ts :: apiHandler(arg)": 211, + "app/api/insights/comprehensive/route.ts :: buildComprehensiveResponse": 505, + "app/api/insights/generate/route.ts :: apiHandler(arg) #2": 862, + "app/api/insights/generate/route.ts :: apiHandler(arg) > withFeatureCacheScope(arg)": 858, + "app/api/integrations/status/route.ts :: apiHandler(arg)": 451, + "app/api/labs/[id]/route.ts :: apiHandler(arg) #2": 244, + "app/api/labs/ocr/commit/route.ts :: commitOcrRows": 290, + "app/api/labs/route.ts :: postLabResult": 248, + "app/api/mcp/oauth/token/route.ts :: POST": 250, + "app/api/mcp/oauth/token/route.ts :: POST > withBackgroundEvent(arg)": 248, + "app/api/measurement-reminders/[id]/route.ts :: apiHandler(arg) #2": 240, + "app/api/measurements/[id]/route.ts :: apiHandler(arg) #2": 203, + "app/api/measurements/batch/route.ts :: postBatch": 1277, + "app/api/measurements/route.ts :: apiHandler(arg)": 286, + "app/api/measurements/route.ts :: postMeasurement": 377, + "app/api/measurements/series/route.ts :: apiHandler(arg)": 670, + "app/api/medications/[id]/dose-history/route.ts :: apiHandler(arg)": 256, + "app/api/medications/[id]/intake/[eventId]/route.ts :: apiHandler(arg)": 390, + "app/api/medications/[id]/intake/route.ts :: postIntake": 607, + "app/api/medications/[id]/inventory/[itemId]/route.ts :: apiHandler(arg)": 210, + "app/api/medications/[id]/route.ts :: apiHandler(arg)": 214, + "app/api/medications/[id]/route.ts :: apiHandler(arg) #2": 892, + "app/api/medications/[id]/route.ts :: apiHandler(arg) > prisma.$transaction(arg)": 293, + "app/api/medications/[id]/schedule-revisions/[revisionId]/route.ts :: apiHandler(arg)": 254, + "app/api/medications/extract/route.ts :: handleExtract": 203, + "app/api/medications/intake/bulk/route.ts :: postBulk": 846, + "app/api/medications/intake/route.ts :: apiHandler(arg) #2": 432, + "app/api/medications/route.ts :: postMedication": 403, + "app/api/mental-health/assessments/route.ts :: postAssessment": 267, + "app/api/mood-entries/[id]/route.ts :: apiHandler(arg) #2": 302, + "app/api/mood-entries/bulk/route.ts :: postBulk": 479, + "app/api/mood-entries/route.ts :: postMoodEntry": 329, + "app/api/nutrients/batch/route.ts :: postBatch": 304, + "app/api/onboarding/complete/route.ts :: completeNeedsFlow": 218, + "app/api/record-settings/[family]/route.ts :: apiHandler(arg) #2": 262, + "app/api/sync/changes/route.ts :: apiHandler(arg)": 427, + "app/api/user/ai-provider/route.ts :: apiHandler(arg) #2": 252, + "app/api/withings/callback/route.ts :: apiHandler(arg)": 247, + "app/api/workouts/[id]/route.ts :: apiHandler(arg)": 313, + "app/api/workouts/batch/route.ts :: postBatch": 722, + "app/auth/login/page.tsx :: LoginPage": 576, + "app/auth/register/page.tsx :: RegisterPage": 285, + "app/coach/conversations/page.tsx :: CoachConversationsBody": 249, + "app/coach/plans/page.tsx :: CoachPlansBody": 216, + "app/insights/catalog/page.tsx :: MetricCatalogPage": 204, + "app/insights/medications/page.tsx :: InsightsMedikamentePage": 265, + "app/insights/page-client.tsx :: InsightsPageClient": 376, + "app/labs/page.tsx :: LabsPage": 228, + "app/medications/page-client.tsx :: MedicationsPageClient": 591, + "app/notifications/page.tsx :: NotificationsPage": 327, + "app/page-client.tsx :: DashboardPageClient": 2133, + "app/page-client.tsx :: DashboardPageClient > (anonymous)": 1279, + "components/activity/activity-calendar-card.tsx :: ActivityCalendarCard": 470, + "components/admin/admin-shell.tsx :: AdminShell": 254, + "components/admin/api-token-overview-section.tsx :: ApiTokenOverviewSection": 212, + "components/admin/app-log-preview-section.tsx :: AppLogPreviewSection": 341, + "components/admin/backups-section.tsx :: BackupsSection": 618, + "components/admin/backups-section.tsx :: RestoreRowDialog": 226, + "components/admin/central-codex-section.tsx :: CentralCodexSection": 241, + "components/admin/encryption-section.tsx :: EncryptionSection": 303, + "components/admin/general-settings-section.tsx :: GeneralSettingsSection": 220, + "components/admin/glitchtip-section.tsx :: GlitchtipSection": 233, + "components/admin/host-metrics-chart.tsx :: HostMetricsChart": 232, + "components/admin/invite-tokens-section.tsx :: InviteTokensSection": 525, + "components/admin/key-backup-card.tsx :: KeyBackupCard": 238, + "components/admin/login-overview-section.tsx :: LoginOverviewSection": 520, + "components/admin/pulse-day-mean-card.tsx :: PulseDayMeanCard": 305, + "components/admin/reminders-section.tsx :: RemindersSection": 309, + "components/admin/user-management-section.tsx :: UserManagementSection": 634, + "components/admin/web-push-vapid-section.tsx :: WebPushVapidSection": 217, + "components/auth/claim-profile.tsx :: ClaimProfile": 429, + "components/auth/mfa-login-step.tsx :: MfaLoginStep": 217, + "components/charts/compliance-heatmap.tsx :: ComplianceHeatmap": 325, + "components/charts/health-chart.tsx :: HealthChart": 1929, + "components/charts/medication-compliance-chart.tsx :: MedicationComplianceChart": 543, + "components/charts/mood-chart.tsx :: MoodChart": 896, + "components/charts/mood-heatmap.tsx :: MoodHeatmap": 296, + "components/charts/trend-card.tsx :: TrendCard": 365, + "components/clinician/clinician-view.tsx :: ClinicianView": 223, + "components/command-palette/command-palette.tsx :: PaletteBody": 333, + "components/custom-metrics/custom-metric-chart.tsx :: CustomMetricChart": 231, + "components/custom-metrics/custom-metric-detail.tsx :: CustomMetricDetail": 277, + "components/custom-metrics/custom-metric-form.tsx :: CustomMetricForm": 214, + "components/custom-metrics/custom-metric-history-list.tsx :: CustomMetricHistoryList": 302, + "components/cycle/bbt-chart.tsx :: BbtChart": 244, + "components/cycle/cycle-calendar.tsx :: CycleCalendar": 303, + "components/cycle/cycle-settings.tsx :: CycleSettings": 228, + "components/cycle/cycle-view.tsx :: CycleView": 408, + "components/cycle/log-day-sheet.tsx :: LogDaySheet": 753, + "components/daily/today-hero.tsx :: TodayHero": 490, + "components/dashboard/dashboard-header.tsx :: DashboardHeader": 215, + "components/dashboard/medication-intake-quick-add.tsx :: MedicationIntakeQuickAdd": 384, + "components/day/day-layer.tsx :: DayLayer": 375, + "components/day/day-view.tsx :: DayView": 300, + "components/documents/document-ai-section.tsx :: DocumentAiSection": 262, + "components/documents/document-bulk-bar.tsx :: DocumentBulkBar": 204, + "components/documents/document-card.tsx :: DocumentCard": 354, + "components/documents/document-detail-sheet.tsx :: DocumentDetailSheet": 936, + "components/documents/document-facts-review.tsx :: DocumentFactsSection": 252, + "components/documents/document-facts-review.tsx :: FactRow": 262, + "components/documents/document-filter-bar.tsx :: DocumentFilterBar": 348, + "components/documents/document-record-links.tsx :: DocumentRecordLinks": 202, + "components/documents/document-timeline.tsx :: DocumentTimeline": 314, + "components/documents/documents-view.tsx :: DocumentsView": 964, + "components/documents/sources/document-source-picker.tsx :: DocumentSourcePicker": 565, + "components/illness/log-day-sheet.tsx :: LogDaySheet": 265, + "components/illness/new-episode-sheet.tsx :: NewEpisodeSheet": 264, + "components/insights/coach-panel/attachment-picker.tsx :: AttachmentPicker": 208, + "components/insights/coach-panel/chat-bubble.tsx :: ChatBubbleImpl": 419, + "components/insights/coach-panel/coach-conversation.tsx :: CoachConversation": 1012, + "components/insights/coach-panel/coach-input.tsx :: CoachInput": 259, + "components/insights/coach-panel/coach-model-picker.tsx :: CoachModelPicker": 212, + "components/insights/coach-panel/conversations-panel.tsx :: ConversationsPanel": 317, + "components/insights/coach-panel/message-thread.tsx :: MessageThread": 390, + "components/insights/coach-panel/sources-rail.tsx :: SourcesRail": 203, + "components/insights/coach-panel/use-coach.ts :: useSendCoachMessage": 436, + "components/insights/coach-panel/use-coach.ts :: useSendCoachMessage > useCallback(arg) #2": 340, + "components/insights/daily-briefing.tsx :: DailyBriefing": 224, + "components/insights/derived/coach-read-strip.tsx :: CoachReadStrip": 202, + "components/insights/derived/composite-score-anatomy.tsx :: CompositeScoreAnatomy": 251, + "components/insights/derived/score-trend-chart.tsx :: ScoreTrendChart": 255, + "components/insights/derived/vitals-dashboard.tsx :: VitalsDashboard": 239, + "components/insights/derived/wellness-scores.tsx :: WellnessScores": 273, + "components/insights/glucose/glucose-clinical-panel.tsx :: GlucoseClinicalPanel": 213, + "components/insights/health-score-card.tsx :: HealthScoreCard": 561, + "components/insights/healthkit-metric-page.tsx :: HealthKitMetricPage": 322, + "components/insights/insight-status-card.tsx :: InsightStatusCard": 205, + "components/insights/insights-edit-mode.tsx :: InsightsEditMode": 270, + "components/insights/insights-tab-strip.tsx :: InsightsTabStripImpl": 410, + "components/insights/intraday-pulse-chart.tsx :: IntradayPulseChart": 283, + "components/insights/layout-coach-fab.tsx :: LayoutCoachFab": 230, + "components/insights/metric-target-summary.tsx :: TargetReferencePanel": 223, + "components/insights/mood/mood-dimension-trends.tsx :: MoodDimensionTrends": 227, + "components/insights/sleep-stage-stacked-bar.tsx :: SleepStageStackedBar": 354, + "components/insights/sub-page-shell.tsx :: SubPageShell": 266, + "components/insights/trends-row.tsx :: TrendsRow": 215, + "components/labs/biomarker-form.tsx :: BiomarkerForm": 244, + "components/labs/biomarker-manager.tsx :: BiomarkerManager": 242, + "components/labs/biomarker-merge-sheet.tsx :: BiomarkerMergeSheet": 250, + "components/labs/lab-biomarker-chart.tsx :: LabBiomarkerChart": 370, + "components/labs/lab-biomarker-detail.tsx :: LabBiomarkerDetail": 481, + "components/labs/lab-form.tsx :: LabForm": 473, + "components/labs/lab-history-list.tsx :: LabHistoryList": 534, + "components/labs/lab-list.tsx :: LabList": 308, + "components/labs/ocr-review-dialog.tsx :: OcrReviewDialog": 359, + "components/labs/ocr-row-editor.tsx :: OcrRowEditor": 393, + "components/layout/auth-shell.tsx :: AuthShell": 520, + "components/layout/bottom-nav.tsx :: BottomNav": 260, + "components/layout/capture-picker.tsx :: CapturePicker": 235, + "components/layout/sidebar-nav.tsx :: SidebarNav": 372, + "components/layout/top-bar.tsx :: TopBar": 212, + "components/links/entity-link-picker.tsx :: EntityLinkPicker": 392, + "components/measurement-reminders/vorsorge-dashboard-card.tsx :: VorsorgeDashboardCard": 215, + "components/measurement-reminders/vorsorge-section.tsx :: VorsorgeCard": 635, + "components/measurement-reminders/vorsorge-section.tsx :: VorsorgeSection": 618, + "components/measurements/measurement-form.tsx :: MeasurementForm": 500, + "components/measurements/measurement-list.tsx :: MeasurementList": 1586, + "components/measurements/measurement-list.tsx :: MeasurementList > data.measurements.map(arg)": 223, + "components/measurements/measurement-list.tsx :: MeasurementList > data.measurements.map(arg) #2": 229, + "components/medications/detail/efficacy/efficacy-chart.tsx :: EfficacyChart": 361, + "components/medications/detail/medication-detail-tabs.tsx :: MedicationDetailTabs": 787, + "components/medications/dose-history-add-dialog.tsx :: LedgerAddDialog": 223, + "components/medications/dose-history-ledger.tsx :: DoseHistoryLedger": 404, + "components/medications/dose-history-ledger.tsx :: LedgerRowItem": 314, + "components/medications/dose-strength-curve.tsx :: DoseStrengthCurve": 266, + "components/medications/glp1-medication-card.tsx :: Glp1MedicationCard": 405, + "components/medications/injection-site-picker.tsx :: InjectionSitePicker": 287, + "components/medications/intake-history-editable.tsx :: IntakeHistoryEditable": 224, + "components/medications/intake-history-list-v2.tsx :: IntakeHistoryListV2": 435, + "components/medications/intake-import-dialog.tsx :: IntakeImportDialog": 208, + "components/medications/log-intake-dialog.tsx :: LogIntakeDialog": 267, + "components/medications/medication-card.tsx :: MedicationCard": 413, + "components/medications/medication-table.tsx :: MedicationTableRowItem": 377, + "components/medications/scheduling/natural-language-extractor.tsx :: NaturalLanguageExtractor": 214, + "components/medications/scheduling/schedule-history-timeline.tsx :: EraDialog": 219, + "components/medications/scheduling/schedule-history-timeline.tsx :: ScheduleHistoryTimeline": 256, + "components/medications/sections/api-tokens-row.tsx :: ApiTokensRow": 296, + "components/medications/sections/composition-section.tsx :: CompositionEditor": 362, + "components/medications/sections/inventory-dialogs.tsx :: AddInventoryDialog": 238, + "components/medications/sections/inventory-section.tsx :: InventorySection": 368, + "components/medications/side-effects-section.tsx :: SideEffectsSection": 516, + "components/medications/wizard/medication-wizard-dialog.tsx :: WizardDialogShell": 516, + "components/medications/wizard/steps/step3-dose.tsx :: Step3Dose": 222, + "components/medications/wizard/steps/step8-summary.tsx :: Step8Summary": 230, + "components/mental-health/check-in-wizard.tsx :: CheckInWizard": 214, + "components/mental-health/mental-wellbeing.tsx :: MentalWellbeing": 210, + "components/mood/manage/tag-groups-card.tsx :: TagGroupsCard": 256, + "components/mood/manage/tag-manager-card.tsx :: TagManagerCard": 270, + "components/mood/mood-form.tsx :: MoodForm": 513, + "components/mood/mood-list.tsx :: MoodList": 1109, + "components/mood/mood-tag-picker.tsx :: MoodTagPicker": 257, + "components/onboarding/confirm-screen.tsx :: ConfirmScreen": 255, + "components/onboarding/done-screen.tsx :: DoneScreen": 408, + "components/onboarding/getting-started-checklist.tsx :: GettingStartedChecklist": 425, + "components/onboarding/tour.tsx :: OnboardingTour": 544, + "components/practitioners/practitioner-list.tsx :: PractitionerList": 230, + "components/provenance/source-provenance-view.tsx :: SourceProvenanceView": 363, + "components/records/allergy-form.tsx :: AllergyForm": 225, + "components/records/audit-c-panel.tsx :: AuditCPanel": 285, + "components/records/life-circumstances-manager.tsx :: SectionBody": 482, + "components/settings/about-section.tsx :: AboutSection": 236, + "components/settings/access/grant-invite-card.tsx :: GrantInviteCard": 311, + "components/settings/access/managed-profile-create-form.tsx :: ManagedProfileCreateForm": 235, + "components/settings/access/managed-profile-edit-form.tsx :: ManagedProfileEditFields": 208, + "components/settings/access/managed-profile-guardians.tsx :: ManagedProfileGuardians": 234, + "components/settings/account-section/index.tsx :: AccountSection": 659, + "components/settings/ai/codex-provider-form.tsx :: CodexProviderForm": 281, + "components/settings/ai/fallback-chain-card.tsx :: FallbackChainCard": 228, + "components/settings/ai/runtime-actions-row.tsx :: RuntimeActionsRow": 282, + "components/settings/api-section.tsx :: ApiTokensCard": 366, + "components/settings/coach-nudge-card.tsx :: CoachNudgeCard": 251, + "components/settings/coach-prefs-section.tsx :: CoachPrefsSection": 350, + "components/settings/dashboard-layout-section.tsx :: DashboardLayoutSection": 620, + "components/settings/environment-section.tsx :: EnvironmentSection": 666, + "components/settings/import-panel/apple-health-import-card.tsx :: AppleHealthImportCard": 298, + "components/settings/import-panel/csv-import-card.tsx :: CsvImportCard": 212, + "components/settings/import-panel/dose-history-import-card.tsx :: DoseHistoryImportCard": 225, + "components/settings/import-panel/health-connect-import-card.tsx :: HealthConnectImportCard": 293, + "components/settings/import-panel/json-import-card.tsx :: JsonImportCard": 223, + "components/settings/import-panel/record-csv-import-card.tsx :: RecordCsvImportSheet": 460, + "components/settings/insights-pill-order-section.tsx :: InsightsPillOrderSection": 255, + "components/settings/integrations/connections-panel.tsx :: ConnectionsPanel": 236, + "components/settings/integrations/document-sources-card.tsx :: SourceForm": 306, + "components/settings/integrations/google-health-card.tsx :: GoogleHealthCard": 300, + "components/settings/integrations/integration-card.tsx :: IntegrationCard": 209, + "components/settings/integrations/oauth-integration-card.tsx :: OAuthIntegrationCardView": 321, + "components/settings/low-stock-card.tsx :: LowStockCard": 296, + "components/settings/mcp-section.tsx :: McpTokensCard": 257, + "components/settings/mood-reminder-card.tsx :: MoodReminderCard": 234, + "components/settings/report-selection/selection-panel.tsx :: HealthRecordExportPanel": 370, + "components/settings/score-section.tsx :: ScoreSection": 383, + "components/settings/security-section/passkey-list-section.tsx :: PasskeyListSection": 410, + "components/settings/security-section/security-keys-card.tsx :: SecurityKeysCard": 288, + "components/settings/security-section/totp-card.tsx :: TotpCard": 533, + "components/settings/security-sessions-card.tsx :: SecuritySessionsCard": 245, + "components/settings/settings-shell.tsx :: SettingsShell": 254, + "components/settings/share-link-create-form.tsx :: ShareLinkCreateForm": 451, + "components/settings/sharing-section.tsx :: ShareLinksCard": 260, + "components/settings/sources-section.tsx :: SourcesSection": 601, + "components/settings/thresholds-editor-section.tsx :: MetricRow": 241, + "components/settings/web-push-card.tsx :: WebPushCard": 215, + "components/symptoms/symptom-entry-form.tsx :: SymptomEntryForm": 317, + "components/symptoms/symptoms-section.tsx :: SymptomsSection": 205, + "components/targets/range-bar.tsx :: RangeBar": 220, + "components/targets/target-edit-sheet.tsx :: TargetEditSheetBody": 474, + "components/timeline/life-event-form.tsx :: LifeEventForm": 281, + "components/timeline/series-point-tips.tsx :: SeriesPointTips": 212, + "components/timeline/timeline-chart.tsx :: ChartBody": 243, + "components/timeline/timeline-chronicle.tsx :: TimelineChronicle": 274, + "components/timeline/timeline-view.tsx :: TimelineView": 502, + "components/ui/responsive-sheet.tsx :: ResponsiveSheet": 237, + "components/vaccinations/catalog-picker.tsx :: CatalogPicker": 221, + "components/vaccinations/custom-vaccine-sheet.tsx :: CustomVaccineSheet": 246, + "components/vaccinations/vaccination-form.tsx :: VaccinationForm": 218, + "components/workouts/manual-workout-form.tsx :: ManualWorkoutForm": 323, + "lib/activity/calendar-math.ts :: buildActivityCalendar": 204, + "lib/ai/anthropic-client.ts :: generateCompletion": 294, + "lib/ai/coach/cycle-snapshot.ts :: buildCycleSnapshotBlock": 218, + "lib/ai/coach/glp1-snapshot.ts :: buildGlp1SnapshotBlock": 218, + "lib/ai/coach/snapshot.ts :: buildCoachSnapshotImpl": 898, + "lib/ai/coach/tools/loop.ts :: runCoachToolLoop": 438, + "lib/ai/coach/turn/model.ts :: runTurnModel": 560, + "lib/ai/coach/turn/pipeline.ts :: runTurn": 274, + "lib/ai/coach/turn/reply-guards.ts :: guardReply": 331, + "lib/ai/codex-client.ts :: parseResponse": 302, + "lib/ai/local-client.ts :: generateCompletionStream": 288, + "lib/ai/openai-client.ts :: generateCompletion": 346, + "lib/ai/safety/outbound-screen.ts :: buildDoseChangeClass": 275, + "lib/analytics/bp-in-target-fast-path.ts :: computeFromRollups": 203, + "lib/analytics/compliance/display.ts :: calculateCompliance": 212, + "lib/analytics/correlations-fast-path.ts :: computeCorrelationHypothesesFastPath": 256, + "lib/analytics/intraday-pulse-io.ts :: loadIntradayPulse": 206, + "lib/analytics/score/reader.ts :: computeUserHealthScore": 324, + "lib/analytics/summaries-slice.ts :: computeFromRollups": 441, + "lib/api-handler.ts :: apiHandler": 207, + "lib/api-handler.ts :: apiHandler > wrapped": 201, + "lib/auth/profile-update.ts :: applyProfileUpdate": 353, + "lib/cache/server-cache.ts :: createServerCaches": 230, + "lib/cycle/backup.ts :: restoreCycleData": 243, + "lib/cycle/day-log-write.ts :: writeDayLog": 311, + "lib/daily/digest.ts :: buildDailyDigest": 227, + "lib/daily/load-digest.ts :: loadDailyDigest": 452, + "lib/dashboard/snapshot.ts :: buildDashboardSnapshot": 312, + "lib/dashboard/snapshot.ts :: buildExtras": 240, + "lib/day/records.ts :: medicationsPart": 306, + "lib/doctor-report-pdf/clinical-records-notes-section.ts :: buildClinicalRecordsNotesSection": 648, + "lib/doctor-report-pdf/measurements-charts-section.ts :: buildMeasurementsChartsSection": 398, + "lib/doctor-report-pdf/medication-mood-wellness-section.ts :: buildMedicationMoodWellnessSection": 509, + "lib/doctor-report/collect.ts :: collectDoctorReportData": 678, + "lib/doctor-report/dense-buckets.ts :: loadDenseMeasurementBuckets": 210, + "lib/documents/fenced-chat.ts :: streamFencedReply": 309, + "lib/documents/ingest.ts :: ingestDocument": 272, + "lib/export/backup-json-scan.ts :: scanBackupJson": 225, + "lib/export/full-backup-payload.ts :: buildFullBackupPayload": 804, + "lib/export/full-backup-payload.ts :: buildFullBackupPayload > medications.map(arg) #3": 268, + "lib/export/profile-backup.ts :: buildProfileBackupSection": 270, + "lib/export/profile-backup.ts :: restoreProfileData": 291, + "lib/export/records-backup.ts :: buildRecordsBackupSection": 308, + "lib/export/restore-backup.ts :: restoreBackup": 2833, + "lib/export/restore-backup.ts :: restoreBackup > prisma.$transaction(arg)": 2163, + "lib/export/vaccinations-backup.ts :: restoreVaccinationsData": 216, + "lib/export/visits-backup.ts :: restoreVisitsData": 261, + "lib/fhir/build-bundle.ts :: buildFhirDocumentBundle": 363, + "lib/fitbit/sync-core.ts :: upsertFitbitMeasurements": 370, + "lib/gamification/achievements-result.ts :: buildAchievementsResult": 586, + "lib/google-health/client.ts :: forEachDataPointPage": 288, + "lib/google-health/client.ts :: forEachDataPointPage > walk": 223, + "lib/google-health/sync-core.ts :: upsertGoogleHealthMeasurements": 463, + "lib/google-health/sync.ts :: runGoogleHealthSyncCycle": 203, + "lib/illness/correlation.ts :: computeIllnessCorrelation": 233, + "lib/import/labs-csv.ts :: importLabsCsv": 309, + "lib/import/labs-csv.ts :: importLabsCsv > flush": 231, + "lib/insights/biomarker-status.ts :: generateBiomarkerStatus": 290, + "lib/insights/blood-pressure-status.ts :: prepareBloodPressureStatusForUser": 635, + "lib/insights/bmi-status.ts :: prepareBmiStatusForUser": 387, + "lib/insights/comprehensive-aggregator.ts :: buildFromRollups": 220, + "lib/insights/comprehensive-generate.ts :: generateComprehensiveInsight": 872, + "lib/insights/features.ts :: extractFeatures": 796, + "lib/insights/general-status.ts :: prepareGeneralStatusForUser": 449, + "lib/insights/medication-compliance-status.ts :: prepareMedicationComplianceStatusForUser": 485, + "lib/insights/metric-status.ts :: generateMetricStatus": 477, + "lib/insights/mood-aggregates.ts :: fetchMoodAggregates": 246, + "lib/insights/mood-status.ts :: prepareMoodStatusForUser": 646, + "lib/insights/pulse-status.ts :: preparePulseStatusForUser": 414, + "lib/insights/status-provider.ts :: runStatusCompletion": 241, + "lib/insights/weight-status.ts :: prepareWeightStatusForUser": 598, + "lib/integrations/connection-health-load.ts :: loadConnectionFacts": 227, + "lib/integrations/status.ts :: recordSyncFailure": 292, + "lib/jobs/apple-health-import-worker.ts :: handleAppleHealthImport": 357, + "lib/jobs/coach-nudge.ts :: findTriggerForUser": 279, + "lib/jobs/coach-nudge.ts :: runCoachNudgeTick": 292, + "lib/jobs/coach-reminder-sweep.ts :: runCoachReminderSweep": 222, + "lib/jobs/cycle-reminder.ts :: runCycleReminderTick": 243, + "lib/jobs/document-summary.ts :: runDocumentSummaryJob": 239, + "lib/jobs/google-health-backfill.ts :: runGoogleHealthBackfillForUser": 276, + "lib/jobs/insight-pregenerate.ts :: forceWarmUser": 282, + "lib/jobs/insight-pregenerate.ts :: forceWarmUser > withFeatureCacheScope(arg)": 252, + "lib/jobs/insight-pregenerate.ts :: runInsightPregenerate": 383, + "lib/jobs/insight-pregenerate.ts :: runInsightPregenerate > pregenerateCandidate": 233, + "lib/jobs/measurement-reminder.ts :: runMeasurementReminderTick": 286, + "lib/jobs/medication-intake-import.ts :: processNextChunk": 220, + "lib/jobs/medication-intake-import.ts :: processNextChunk > client.$transaction(arg)": 212, + "lib/jobs/medication-low-stock.ts :: runMedicationLowStockTick": 286, + "lib/jobs/oura-backfill.ts :: runOuraBackfillForUser": 236, + "lib/jobs/reaction-line.ts :: runReactionLine": 337, + "lib/jobs/reminder/medication-reminder-check.ts :: handleReminderCheck": 619, + "lib/jobs/reminder/medication-reminder-check.ts :: handleReminderCheck > withBackgroundEvent(arg)": 614, + "lib/jobs/reminder/register-integration-sync.ts :: enqueueIntegrationSyncBootDiscovery": 224, + "lib/jobs/reminder/register-integration-sync.ts :: registerIntegrationSyncQueues": 320, + "lib/jobs/reminder/register-maintenance.ts :: enqueueMaintenanceBootDiscovery": 232, + "lib/jobs/reminder/register-maintenance.ts :: registerMaintenanceQueues": 997, + "lib/jobs/reminder/register-rollup.ts :: enqueueRollupBootDiscovery": 238, + "lib/jobs/reminder/register-rollup.ts :: registerRollupQueues": 616, + "lib/jobs/reminder/register-status.ts :: registerStatusQueues": 542, + "lib/jobs/workout-insight-generate.ts :: runWorkoutInsightGenerate": 324, + "lib/labs/biomarker-merge.ts :: runMerge": 254, + "lib/labs/ocr-extract.ts :: runOcrExtraction": 207, + "lib/labs/ocr-run.ts :: executeOcrExtraction": 206, + "lib/mcp/tools.ts :: searchAndFetchTools": 421, + "lib/mcp/tools.ts :: searchAndFetchTools > run": 205, + "lib/measurements/consolidate-daily-mean.ts :: consolidateDailyMean": 278, + "lib/measurements/consolidate-legacy-steps.ts :: consolidateLegacySteps": 263, + "lib/measurements/dense-intraday-retention.ts :: runDenseIntradayRetention": 537, + "lib/measurements/dense-intraday-retention.ts :: runDenseIntradayRetention > walkSource": 447, + "lib/measurements/dense-intraday-retention.ts :: runDenseIntradayRetention > walkSource > writeDay": 373, + "lib/measurements/dense-intraday-retention.ts :: runDenseIntradayRetention > walkSource > writeDay > foldOnce": 212, + "lib/measurements/drain-per-sample-cumulative.ts :: drainPerSampleCumulative": 344, + "lib/measurements/drain-per-sample-cumulative.ts :: drainPerSampleCumulative > writeDay": 205, + "lib/measurements/import-apple-health-export.ts :: streamParseExportXml": 1256, + "lib/measurements/import-apple-health-export.ts :: streamParseExportXml > (anonymous) #2": 323, + "lib/medications/efficacy/build-efficacy.ts :: buildMedicationEfficacy": 268, + "lib/medications/intake-slot-dedup.ts :: dedupeUserIntakeSlots": 321, + "lib/medications/list-read.ts :: buildMedicationsList": 310, + "lib/medications/scheduling/dose-history.ts :: reconstructDoseHistory": 218, + "lib/multipart/stream-to-disk.ts :: streamMultipartToDisk": 272, + "lib/notifications/dispatcher.ts :: dispatchNotification": 378, + "lib/notifications/senders/apns.ts :: sendViaApns": 299, + "lib/notifications/senders/web-push.ts :: sendViaWebPush": 268, + "lib/personal-records/pr-detection-worker.ts :: detectPersonalRecordsForUser": 239, + "lib/personal-records/pr-direction.ts :: getPRDirection": 202, + "lib/record-activity/activity-verb.ts :: recordActivityVerbLine": 395, + "lib/strava/sync.ts :: runStravaSync": 219, + "lib/support/report.ts :: collectSupportReportFacts": 332, + "lib/targets/build-response.ts :: buildTargetsResponse": 330, + "lib/targets/vitals-builder.ts :: buildVitalTargets": 324, + "lib/telegram-webhook-handlers.ts :: handleCallback": 599, + "lib/telegram-webhook-handlers.ts :: handleTextMessage": 337, + "lib/withings/sync-activity.ts :: syncUserActivity": 391, + "lib/withings/sync-ecg.ts :: syncUserEcg": 213, + "lib/withings/sync-sleep.ts :: syncUserSleep": 429, + "lib/withings/sync.ts :: syncUserMeasurements": 373, + "proxy.ts :: proxy": 499 + } +} diff --git a/src/__tests__/baselines/test-as-never.json b/src/__tests__/baselines/test-as-never.json new file mode 100644 index 000000000..7db17ddc4 --- /dev/null +++ b/src/__tests__/baselines/test-as-never.json @@ -0,0 +1,577 @@ +{ + "$comment": "as never casts in tests, per test file under src/. Maintained by src/__tests__/test-realism-ratchet.test.ts.", + "count": 570, + "total": 6014, + "entries": { + "__tests__/api/auth/login/mfa-branch.test.ts": 10, + "__tests__/api/auth/mfa/verify.test.ts": 9, + "__tests__/api/auth/registration-status/route.test.ts": 4, + "__tests__/derived-wellness-value-contract.test.ts": 2, + "__tests__/notification-delivery-identity.test.ts": 13, + "__tests__/profile-email-rate-limit-contract.test.ts": 3, + "__tests__/record-write-rate-limit-contract.test.ts": 3, + "__tests__/shared-record-navigation.test.tsx": 2, + "__tests__/sharing-legacy-client-contract.test.ts": 2, + "__tests__/tz-day-paths-harness.test.ts": 12, + "app/api/account/grants/__tests__/invite-access-level.test.ts": 2, + "app/api/account/grants/__tests__/invite-recent-proof.test.ts": 4, + "app/api/account/grants/__tests__/invite-scope-and-manage.test.ts": 2, + "app/api/admin/ai-settings/__tests__/route.test.ts": 9, + "app/api/admin/app-logs/__tests__/route.test.ts": 1, + "app/api/admin/audit-log/__tests__/route.test.ts": 1, + "app/api/admin/audit-log/actions/__tests__/route.test.ts": 2, + "app/api/admin/import-apple-health-export/__tests__/route.test.ts": 5, + "app/api/admin/maintenance/measurements/__tests__/status.test.ts": 11, + "app/api/admin/maintenance/pulse-day-mean/__tests__/route.test.ts": 7, + "app/api/admin/notifications/diagnostic/__tests__/route.test.ts": 15, + "app/api/admin/notifications/health/__tests__/route.test.ts": 5, + "app/api/admin/notifications/reminder-check/__tests__/route.test.ts": 4, + "app/api/admin/notifications/test/__tests__/route.test.ts": 15, + "app/api/admin/provider-health/__tests__/route.test.ts": 29, + "app/api/admin/settings/__tests__/route.test.ts": 12, + "app/api/admin/settings/assistant-flags/__tests__/route.test.ts": 21, + "app/api/admin/settings/module-availability/__tests__/route.test.ts": 13, + "app/api/admin/settings/web-push-vapid/generate/__tests__/route.test.ts": 4, + "app/api/admin/status/__tests__/route.test.ts": 3, + "app/api/admin/users/__tests__/route.test.ts": 3, + "app/api/admin/users/[id]/__tests__/route.test.ts": 8, + "app/api/ai/test/__tests__/route.test.ts": 26, + "app/api/analytics/__tests__/route.test.ts": 25, + "app/api/auth/claim/__tests__/route.test.ts": 13, + "app/api/auth/login/__tests__/constant-work.test.ts": 4, + "app/api/auth/login/__tests__/native-token.test.ts": 8, + "app/api/auth/login/__tests__/route.test.ts": 5, + "app/api/auth/login/__tests__/trusted-device.test.ts": 2, + "app/api/auth/me/ai-usage/__tests__/route.test.ts": 3, + "app/api/auth/me/coach-prefs/__tests__/route.test.ts": 9, + "app/api/auth/me/devices/__tests__/route.test.ts": 8, + "app/api/auth/me/devices/[id]/__tests__/route.test.ts": 13, + "app/api/auth/me/diabetes/__tests__/route.test.ts": 13, + "app/api/auth/me/disable-coach/__tests__/route.test.ts": 19, + "app/api/auth/me/disable-coach/__tests__/unknown-key-strict.test.ts": 2, + "app/api/auth/me/documents-auto-ai-read/__tests__/route.test.ts": 5, + "app/api/auth/me/glucose-unit/__tests__/route.test.ts": 19, + "app/api/auth/me/health-score-config/__tests__/route.test.ts": 25, + "app/api/auth/me/mfa/disable/__tests__/route.test.ts": 7, + "app/api/auth/me/mfa/webauthn/register/__tests__/route.test.ts": 8, + "app/api/auth/me/modules/__tests__/route.test.ts": 29, + "app/api/auth/me/notification-prefs/__tests__/route.test.ts": 44, + "app/api/auth/me/security-activity/__tests__/route.test.ts": 2, + "app/api/auth/me/sessions/__tests__/route.test.ts": 13, + "app/api/auth/me/source-priority/__tests__/route.test.ts": 11, + "app/api/auth/me/timezone/__tests__/route.test.ts": 15, + "app/api/auth/me/unit-preference/__tests__/route.test.ts": 19, + "app/api/auth/me/workout-routes/__tests__/route.test.ts": 3, + "app/api/auth/mfa/webauthn/verify/__tests__/route.test.ts": 11, + "app/api/auth/native/complete/__tests__/route.test.ts": 2, + "app/api/auth/native/login/__tests__/route.test.ts": 2, + "app/api/auth/native/token/__tests__/route.test.ts": 12, + "app/api/auth/oidc/callback/__tests__/route.test.ts": 18, + "app/api/auth/oidc/login/__tests__/route.test.ts": 4, + "app/api/auth/oidc/native/token/__tests__/route.test.ts": 11, + "app/api/auth/passkey/login-options/__tests__/oidc-only.test.ts": 2, + "app/api/auth/passkey/login-verify/__tests__/native-token.test.ts": 7, + "app/api/auth/passkey/login-verify/__tests__/oidc-only.test.ts": 4, + "app/api/auth/passkey/login-verify/__tests__/refusals.test.ts": 6, + "app/api/auth/passkeys/__tests__/route.test.ts": 3, + "app/api/auth/passkeys/[id]/__tests__/route.test.ts": 17, + "app/api/auth/password/__tests__/route.test.ts": 5, + "app/api/auth/register/__tests__/route.test.ts": 25, + "app/api/biomarkers/__tests__/error-contract.test.ts": 3, + "app/api/biomarkers/[id]/__tests__/delete-record-auth.test.ts": 3, + "app/api/biomarkers/[id]/__tests__/route.test.ts": 23, + "app/api/coach/plans/__tests__/route.test.ts": 23, + "app/api/coach/reminders/__tests__/route.test.ts": 15, + "app/api/coach/suggested-actions/__tests__/route.test.ts": 7, + "app/api/consent/ai/__tests__/route.test.ts": 10, + "app/api/consent/ai/latest/__tests__/route.test.ts": 42, + "app/api/consent/ai/web/__tests__/route.test.ts": 6, + "app/api/custom-metrics/__tests__/route.test.ts": 14, + "app/api/custom-metrics/[id]/__tests__/route.test.ts": 23, + "app/api/custom-metrics/[id]/entries/__tests__/note-encryption.test.ts": 13, + "app/api/custom-metrics/[id]/entries/__tests__/route.test.ts": 12, + "app/api/custom-metrics/[id]/entries/__tests__/soft-delete-restore.test.ts": 6, + "app/api/cycle/__tests__/external-id-floor.test.ts": 9, + "app/api/cycle/calendar/__tests__/day-entries.test.ts": 9, + "app/api/cycle/calendar/__tests__/verdict-round-trip.test.ts": 26, + "app/api/cycle/day-logs/bulk/__tests__/route.test.ts": 6, + "app/api/cycle/symptoms/custom/__tests__/custom-crud.test.ts": 4, + "app/api/daily/digest/__tests__/route.test.ts": 2, + "app/api/daily/digest/dismiss/__tests__/route.test.ts": 2, + "app/api/dashboard/chart-overlay-prefs/__tests__/route.test.ts": 2, + "app/api/dashboard/snapshot/__tests__/route.test.ts": 5, + "app/api/dashboard/summary/__tests__/module-gate.test.ts": 6, + "app/api/dashboard/summary/__tests__/route.test.ts": 86, + "app/api/dashboard/widgets/__tests__/route.test.ts": 49, + "app/api/devices/__tests__/route.test.ts": 32, + "app/api/devices/[id]/__tests__/route.test.ts": 10, + "app/api/documents/inbound/__tests__/route.test.ts": 62, + "app/api/documents/inbound/[id]/__tests__/route.test.ts": 56, + "app/api/documents/inbound/[id]/chat/__tests__/route.test.ts": 42, + "app/api/documents/inbound/[id]/extract/__tests__/route.test.ts": 38, + "app/api/documents/inbound/[id]/index/__tests__/route.test.ts": 40, + "app/api/documents/inbound/[id]/original/__tests__/route.test.ts": 7, + "app/api/documents/inbound/[id]/suggest/__tests__/route.test.ts": 12, + "app/api/documents/inbound/[id]/summary/__tests__/route.test.ts": 31, + "app/api/documents/inbound/[id]/thumbnail/__tests__/route.test.ts": 5, + "app/api/documents/inbound/layout/__tests__/route.test.ts": 11, + "app/api/documents/inbound/reindex/__tests__/route.test.ts": 9, + "app/api/export/__tests__/measurement-streaming.test.ts": 9, + "app/api/export/__tests__/per-type-routes.test.ts": 100, + "app/api/export/__tests__/soft-delete-filter.test.ts": 39, + "app/api/export/encrypted/__tests__/route.test.ts": 20, + "app/api/export/health-record/__tests__/route.test.ts": 32, + "app/api/feature-flags/__tests__/route.test.ts": 6, + "app/api/fhir/__tests__/capability-and-everything.test.ts": 4, + "app/api/fhir/__tests__/module-gate.test.ts": 6, + "app/api/fhir/__tests__/observation-series.test.ts": 8, + "app/api/fhir/__tests__/route.test.ts": 9, + "app/api/fitbit/connect/__tests__/route.test.ts": 4, + "app/api/gamification/achievements/__tests__/hidden-redaction.test.ts": 12, + "app/api/gamification/achievements/__tests__/ios-format.test.ts": 13, + "app/api/gamification/achievements/__tests__/module-gate.test.ts": 17, + "app/api/gamification/achievements/__tests__/writes-nothing.test.ts": 1, + "app/api/google-health/credentials/__tests__/route.test.ts": 1, + "app/api/google-health/sync/__tests__/route.test.ts": 3, + "app/api/google-health/sync/status/__tests__/route.test.ts": 1, + "app/api/illness/episodes/[id]/__tests__/body-site.test.ts": 9, + "app/api/illness/episodes/[id]/day-logs/__tests__/route.test.ts": 8, + "app/api/illness/episodes/[id]/restore/__tests__/route.test.ts": 9, + "app/api/import/__tests__/route.test.ts": 47, + "app/api/import/apple-health-export/__tests__/route.test.ts": 8, + "app/api/import/apple-health-export/[jobId]/status/__tests__/route.test.ts": 4, + "app/api/import/csv/__tests__/route.test.ts": 4, + "app/api/import/health-connect-export/__tests__/route.test.ts": 2, + "app/api/ingest/medication/__tests__/route.test.ts": 28, + "app/api/insights/biomarker-assessment/__tests__/route.test.ts": 6, + "app/api/insights/blood-pressure-status/__tests__/route.test.ts": 3, + "app/api/insights/cards/__tests__/route.test.ts": 18, + "app/api/insights/chat/[id]/attachments/__tests__/route.test.ts": 23, + "app/api/insights/chat/fenced/__tests__/route.test.ts": 18, + "app/api/insights/coach/facts/__tests__/route-memory.test.ts": 9, + "app/api/insights/coach/facts/__tests__/route.test.ts": 12, + "app/api/insights/comprehensive/__tests__/route.test.ts": 20, + "app/api/insights/correlations/__tests__/route.test.ts": 12, + "app/api/insights/derived/__tests__/route.test.ts": 12, + "app/api/insights/derived/batch/__tests__/route.test.ts": 21, + "app/api/insights/ecg/__tests__/route.test.ts": 5, + "app/api/insights/ecg/[id]/__tests__/route.test.ts": 5, + "app/api/insights/generate/__tests__/route.test.ts": 54, + "app/api/insights/layout/__tests__/route.test.ts": 23, + "app/api/insights/metric-status/__tests__/route.test.ts": 7, + "app/api/insights/mood-status/__tests__/route.test.ts": 10, + "app/api/insights/patterns/__tests__/route.test.ts": 1, + "app/api/insights/patterns/[id]/__tests__/route.test.ts": 4, + "app/api/insights/pregenerate/__tests__/route.test.ts": 4, + "app/api/insights/provider-chain/__tests__/put.test.ts": 4, + "app/api/insights/provider-chain/__tests__/route.test.ts": 13, + "app/api/insights/rhythm-events/__tests__/route.test.ts": 5, + "app/api/insights/targets/__tests__/route.test.ts": 1, + "app/api/integrations/fitbit/test/__tests__/route.test.ts": 18, + "app/api/integrations/healthkit/__tests__/route.test.ts": 48, + "app/api/integrations/status/__tests__/route.test.ts": 1, + "app/api/integrations/whoop/test/__tests__/route.test.ts": 15, + "app/api/integrations/withings/resume/__tests__/route.test.ts": 2, + "app/api/integrations/withings/test/__tests__/route.test.ts": 9, + "app/api/internal/deploy-webhook/__tests__/route.test.ts": 9, + "app/api/labs/__tests__/error-contract.test.ts": 4, + "app/api/labs/ocr/commit/__tests__/route.test.ts": 9, + "app/api/labs/ocr/extract/__tests__/route.test.ts": 17, + "app/api/labs/restore/__tests__/route.test.ts": 5, + "app/api/mcp/oauth/__tests__/authorize.test.ts": 29, + "app/api/mcp/oauth/__tests__/register.test.ts": 8, + "app/api/mcp/oauth/__tests__/token.test.ts": 14, + "app/api/mcp/tokens/__tests__/route.test.ts": 3, + "app/api/measurement-categories/__tests__/route.test.ts": 3, + "app/api/measurements/__tests__/arrival-wiring.test.ts": 7, + "app/api/measurements/__tests__/group-by-day.test.ts": 10, + "app/api/measurements/__tests__/put-duplicate-timestamp.test.ts": 9, + "app/api/measurements/__tests__/range-aggregation-route.test.ts": 10, + "app/api/measurements/__tests__/zod-multi-issue.test.ts": 2, + "app/api/measurements/[id]/__tests__/route.test.ts": 8, + "app/api/measurements/batch/__tests__/aggregated-hr-wire.test.ts": 9, + "app/api/measurements/batch/__tests__/duplicate-prefilter.test.ts": 1, + "app/api/measurements/batch/__tests__/hr-bucket-observability.test.ts": 3, + "app/api/measurements/batch/__tests__/insert-returning-arrivals.test.ts": 2, + "app/api/measurements/batch/__tests__/issue-paths.test.ts": 1, + "app/api/measurements/batch/__tests__/pr-detection-hook.test.ts": 3, + "app/api/measurements/batch/__tests__/refusal-detail.test.ts": 1, + "app/api/measurements/batch/__tests__/sync-trigger.test.ts": 6, + "app/api/measurements/batch/__tests__/timestamp-plausibility.test.ts": 2, + "app/api/measurements/batch/__tests__/unstable-external-id.test.ts": 2, + "app/api/measurements/bulk-delete/__tests__/route.test.ts": 10, + "app/api/measurements/by-external-ids/__tests__/route.test.ts": 6, + "app/api/measurements/restore/__tests__/route.test.ts": 10, + "app/api/measurements/series-batch/__tests__/route.test.ts": 10, + "app/api/measurements/series/__tests__/route.test.ts": 96, + "app/api/measurements/series/__tests__/unknown-query-param-open.test.ts": 6, + "app/api/medications/__tests__/route.test.ts": 43, + "app/api/medications/[id]/__tests__/route.test.ts": 52, + "app/api/medications/[id]/cadence/__tests__/route.test.ts": 12, + "app/api/medications/[id]/compliance/__tests__/route.test.ts": 18, + "app/api/medications/[id]/dose-history/__tests__/route.test.ts": 15, + "app/api/medications/[id]/glp1/__tests__/route.test.ts": 47, + "app/api/medications/[id]/intake/__tests__/route.test.ts": 45, + "app/api/medications/[id]/intake/__tests__/source-provenance.test.ts": 12, + "app/api/medications/[id]/intake/[eventId]/__tests__/route.test.ts": 63, + "app/api/medications/[id]/intake/bulk-delete/__tests__/route.test.ts": 13, + "app/api/medications/[id]/intake/import/__tests__/route.test.ts": 9, + "app/api/medications/[id]/intake/import/[jobId]/status/__tests__/route.test.ts": 2, + "app/api/medications/[id]/intake/purge/__tests__/route.test.ts": 4, + "app/api/medications/[id]/inventory/__tests__/route.test.ts": 45, + "app/api/medications/[id]/inventory/[itemId]/__tests__/route.test.ts": 12, + "app/api/medications/[id]/phase-config/__tests__/route.test.ts": 2, + "app/api/medications/[id]/schedule-revisions/__tests__/route.test.ts": 24, + "app/api/medications/[id]/side-effects/__tests__/route.test.ts": 32, + "app/api/medications/compliance/__tests__/route.test.ts": 10, + "app/api/medications/extract/__tests__/route.test.ts": 38, + "app/api/medications/intake/__tests__/route.test.ts": 59, + "app/api/medications/intake/bulk/__tests__/dose-arrival-scope.test.ts": 9, + "app/api/medications/intake/bulk/__tests__/route.test.ts": 68, + "app/api/medications/intake/dose-history-import/__tests__/route.test.ts": 7, + "app/api/medications/layout/__tests__/route.test.ts": 17, + "app/api/mental-health/assessments/__tests__/route.test.ts": 12, + "app/api/mental-health/assessments/[id]/__tests__/route.test.ts": 7, + "app/api/meta/capabilities/__tests__/route.test.ts": 1, + "app/api/monitoring/glitchtip/__tests__/route.test.ts": 2, + "app/api/monitoring/umami-script/__tests__/route.test.ts": 2, + "app/api/monitoring/umami/[...path]/__tests__/route.test.ts": 4, + "app/api/mood-entries/__tests__/route.test.ts": 6, + "app/api/mood-entries/[id]/__tests__/route.test.ts": 8, + "app/api/mood-entries/bulk-delete/__tests__/route.test.ts": 10, + "app/api/mood-entries/bulk/__tests__/route.test.ts": 17, + "app/api/mood-entries/restore/__tests__/route.test.ts": 11, + "app/api/mood/analytics/__tests__/route.test.ts": 15, + "app/api/mood/insights/__tests__/route.test.ts": 3, + "app/api/mood/tags/__tests__/custom-crud.test.ts": 2, + "app/api/mood/tags/__tests__/groups-crud.test.ts": 1, + "app/api/mood/tags/__tests__/layout.test.ts": 1, + "app/api/mood/tags/__tests__/route.test.ts": 1, + "app/api/mood/tags/layout/__tests__/route.test.ts": 11, + "app/api/nightscout/sync/__tests__/route.test.ts": 3, + "app/api/notifications/action/[token]/__tests__/route.test.ts": 5, + "app/api/notifications/status/__tests__/route.test.ts": 4, + "app/api/notifications/web-push/__tests__/route.test.ts": 5, + "app/api/notifications/web-push/test/__tests__/route.test.ts": 9, + "app/api/nutrients/__tests__/route.test.ts": 5, + "app/api/nutrients/batch/__tests__/route.test.ts": 11, + "app/api/nutrients/daily/__tests__/route.test.ts": 7, + "app/api/nutrients/water/__tests__/route.test.ts": 8, + "app/api/onboarding/__tests__/error-contract.test.ts": 1, + "app/api/onboarding/complete/__tests__/route.test.ts": 4, + "app/api/oura/callback/__tests__/route.test.ts": 1, + "app/api/oura/sync/__tests__/route.test.ts": 3, + "app/api/personal-records/__tests__/route.test.ts": 15, + "app/api/polar/callback/__tests__/route.test.ts": 1, + "app/api/polar/sync/__tests__/route.test.ts": 3, + "app/api/settings/__tests__/channel-enabled-routes.test.ts": 14, + "app/api/settings/__tests__/channel-global-switch-refusal.test.ts": 15, + "app/api/settings/__tests__/email-test-route.test.ts": 2, + "app/api/settings/__tests__/private-origin-routes.test.ts": 4, + "app/api/settings/__tests__/telegram-test-locale.test.ts": 9, + "app/api/settings/__tests__/webhook-format-route.test.ts": 4, + "app/api/settings/reminder-thresholds/__tests__/route.test.ts": 7, + "app/api/settings/webhook/test/__tests__/route.test.ts": 2, + "app/api/share-links/__tests__/route.test.ts": 19, + "app/api/sleep/night/__tests__/route.test.ts": 24, + "app/api/sleep/rhythm/__tests__/route.test.ts": 6, + "app/api/strava/sync/__tests__/route.test.ts": 3, + "app/api/sync/changes/__tests__/module-gate.test.ts": 7, + "app/api/sync/state/__tests__/route.test.ts": 8, + "app/api/telegram/webhook/__tests__/route.test.ts": 43, + "app/api/tokens/__tests__/route.test.ts": 5, + "app/api/tokens/documents/__tests__/route.test.ts": 25, + "app/api/tokens/measurements/__tests__/route.test.ts": 25, + "app/api/tokens/workouts/__tests__/route.test.ts": 25, + "app/api/user/ai-provider/__tests__/legacy-private-host.test.ts": 2, + "app/api/user/ai-provider/__tests__/route.test.ts": 12, + "app/api/user/avatar/__tests__/route.test.ts": 13, + "app/api/user/avatar/[id]/__tests__/route.test.ts": 5, + "app/api/user/profile/__tests__/route.test.ts": 29, + "app/api/user/thresholds/__tests__/route.test.ts": 7, + "app/api/whoop/callback/__tests__/route.test.ts": 2, + "app/api/whoop/webhook/[token]/__tests__/route.test.ts": 5, + "app/api/withings/callback/__tests__/route.test.ts": 9, + "app/api/withings/connect/__tests__/route.test.ts": 4, + "app/api/withings/credentials/__tests__/route.test.ts": 4, + "app/api/withings/webhook/__tests__/route.test.ts": 26, + "app/api/withings/webhook/[token]/__tests__/route.test.ts": 8, + "app/api/workouts/__tests__/batch-create.test.ts": 14, + "app/api/workouts/__tests__/batch-scope.test.ts": 10, + "app/api/workouts/__tests__/canonical-dedup.test.ts": 20, + "app/api/workouts/__tests__/delete.test.ts": 7, + "app/api/workouts/__tests__/detail.test.ts": 36, + "app/api/workouts/batch/__tests__/unstable-external-id.test.ts": 4, + "app/c/[token]/__tests__/report-download.test.ts": 11, + "app/c/[token]/d/[id]/__tests__/route.test.ts": 8, + "app/mcp/__tests__/route.test.ts": 1, + "components/day/__tests__/day-view.test.tsx": 1, + "components/documents/__tests__/document-record-links.test.tsx": 1, + "components/insights/__tests__/coincident-deviation-card.test.tsx": 1, + "components/insights/coach-panel/__tests__/chat-bubble-memo.test.tsx": 1, + "components/insights/coach-panel/__tests__/message-thread.test.tsx": 1, + "components/insights/coach-panel/__tests__/open-at-end.test.ts": 1, + "components/insights/coach-panel/__tests__/result-chart-compare.test.tsx": 2, + "components/insights/coach-panel/__tests__/turn-activity.test.tsx": 1, + "components/links/__tests__/vault-document-options.test.ts": 1, + "components/measurements/__tests__/measurement-list-url-sync.test.tsx": 1, + "components/medications/__tests__/side-effects-section.test.tsx": 3, + "components/medications/__tests__/use-medication-intake.test.ts": 1, + "components/medications/detail/efficacy/__tests__/change-at-start-summary.test.tsx": 1, + "components/mood/__tests__/mood-list-url-sync.test.tsx": 1, + "components/settings/__tests__/settings-shell.test.tsx": 1, + "components/settings/integrations/__tests__/dawarich-card.test.tsx": 1, + "components/timeline/__tests__/medication-rows.test.ts": 1, + "lib/__tests__/acting-account-resolver.test.ts": 5, + "lib/__tests__/auth-error-codes.test.ts": 7, + "lib/__tests__/db.test.ts": 2, + "lib/__tests__/doctor-report-data.test.ts": 2, + "lib/__tests__/doctor-report-module-backstop.test.ts": 3, + "lib/__tests__/doctor-report-recovery-canonical.test.ts": 1, + "lib/__tests__/idempotency.test.ts": 29, + "lib/__tests__/rate-limit-auth-surface.test.ts": 3, + "lib/__tests__/require-auth-bearer.test.ts": 20, + "lib/__tests__/require-fresh-mfa-if-enrolled.test.ts": 11, + "lib/__tests__/require-fresh-mfa-or-elevation-if-enrolled.test.ts": 15, + "lib/__tests__/require-fresh-mfa.test.ts": 32, + "lib/__tests__/require-recent-proof.test.ts": 16, + "lib/__tests__/require-record-auth-scope.test.ts": 8, + "lib/ai/__tests__/briefing-grounding.test.ts": 9, + "lib/ai/__tests__/codex-oauth.test.ts": 5, + "lib/ai/__tests__/openai-compatible-provider.test.ts": 2, + "lib/ai/__tests__/provider-admin-host.test.ts": 2, + "lib/ai/__tests__/provider-health-ledger.test.ts": 5, + "lib/ai/__tests__/provider-legacy-private-host.test.ts": 3, + "lib/ai/__tests__/provider-presence.test.ts": 2, + "lib/ai/__tests__/provider-reasoning-effort.test.ts": 5, + "lib/ai/__tests__/provider-response-timeout.test.ts": 2, + "lib/ai/__tests__/provider.test.ts": 35, + "lib/ai/__tests__/resolve-chain.test.ts": 12, + "lib/ai/__tests__/server-provider-health.test.ts": 1, + "lib/ai/capabilities/__tests__/egress.test.ts": 1, + "lib/ai/capabilities/__tests__/gate.test.ts": 1, + "lib/ai/coach/__tests__/budget.test.ts": 1, + "lib/ai/coach/__tests__/coach-memory-refresh-worker.test.ts": 1, + "lib/ai/coach/__tests__/context-reminders.test.ts": 5, + "lib/ai/coach/__tests__/conversation-summary.test.ts": 2, + "lib/ai/coach/__tests__/facts-deterministic.test.ts": 3, + "lib/ai/coach/__tests__/persistence-trail.test.ts": 1, + "lib/ai/coach/__tests__/plans.test.ts": 6, + "lib/ai/coach/__tests__/reminders.test.ts": 2, + "lib/ai/coach/__tests__/series-long-window.test.ts": 1, + "lib/ai/coach/__tests__/suggest-gate.test.ts": 1, + "lib/ai/coach/__tests__/tools-executor.test.ts": 1, + "lib/ai/coach/__tests__/tools-inventory.test.ts": 1, + "lib/ai/coach/__tests__/tools-loop.test.ts": 1, + "lib/ai/coach/results/__tests__/project.test.ts": 2, + "lib/ai/coach/snapshot-blocks/__tests__/core-metrics-pulse-day.test.ts": 2, + "lib/ai/coach/turn/__tests__/ledger-reasoning.test.ts": 2, + "lib/ai/coach/turn/__tests__/model-stable-prefix.test.ts": 2, + "lib/ai/prompts/__tests__/output-language-adoption.test.ts": 1, + "lib/ai/safety/__tests__/outbound-screen-guard2.test.ts": 1, + "lib/analytics/__tests__/mood-series.test.ts": 5, + "lib/analytics/__tests__/sleep-stage-composition.test.ts": 2, + "lib/analytics/score/__tests__/config-change-is-not-a-health-event.test.ts": 1, + "lib/analytics/score/__tests__/record.test.ts": 2, + "lib/apple-health/__tests__/route-import.test.ts": 4, + "lib/arrivals/__tests__/reaction-line-shared.test.ts": 1, + "lib/auth/__tests__/audit.test.ts": 2, + "lib/auth/__tests__/bearer.test.ts": 16, + "lib/auth/__tests__/enrollment-proof-second-factor.test.ts": 12, + "lib/auth/__tests__/login-alert.test.ts": 9, + "lib/auth/__tests__/login-throttle.test.ts": 4, + "lib/auth/__tests__/mfa-enrollment.test.ts": 8, + "lib/auth/__tests__/native-handoff-flow.test.ts": 10, + "lib/auth/__tests__/oidc-native-handoff.test.ts": 13, + "lib/auth/__tests__/oidc.test.ts": 5, + "lib/auth/__tests__/passkey.test.ts": 7, + "lib/auth/__tests__/profile-update-email-limit.test.ts": 5, + "lib/auth/__tests__/profile-update.test.ts": 4, + "lib/auth/__tests__/session-locale-reconcile.test.ts": 1, + "lib/auth/__tests__/session.test.ts": 15, + "lib/auth/__tests__/trusted-device.test.ts": 10, + "lib/auth/__tests__/webauthn-library-contract.test.ts": 5, + "lib/auth/mfa/__tests__/challenge.test.ts": 14, + "lib/auth/mfa/__tests__/recovery-codes.test.ts": 8, + "lib/auth/mfa/__tests__/verify-factor.test.ts": 1, + "lib/cache/__tests__/server-cache.test.ts": 2, + "lib/consent/__tests__/receipts.test.ts": 11, + "lib/consent/__tests__/web-grant.test.ts": 9, + "lib/consent/__tests__/withdrawal.test.ts": 4, + "lib/cycle/__tests__/backup.test.ts": 3, + "lib/cycle/__tests__/prediction-refresh.test.ts": 9, + "lib/daily/__tests__/load-digest.test.ts": 32, + "lib/dashboard/__tests__/snapshot.test.ts": 1, + "lib/devices/__tests__/revoke.test.ts": 17, + "lib/doctor-report/__tests__/leaf-gating-sweep.test.ts": 2, + "lib/documents/__tests__/assist-kinds.test.ts": 1, + "lib/documents/__tests__/commit.test.ts": 9, + "lib/documents/__tests__/fenced-chat-pipeline.test.ts": 6, + "lib/documents/__tests__/index-document.test.ts": 23, + "lib/documents/__tests__/index-outcome-record.test.ts": 14, + "lib/documents/__tests__/prepare-vision-raster-failure.test.ts": 6, + "lib/documents/__tests__/provider-order.test.ts": 1, + "lib/documents/__tests__/rasterize-pdf-font-data.test.ts": 1, + "lib/documents/__tests__/upload-policy.test.ts": 8, + "lib/ecg/__tests__/persist-recording.test.ts": 7, + "lib/export/__tests__/account-settings-backup.test.ts": 1, + "lib/export/__tests__/coach-backup-title.test.ts": 3, + "lib/export/__tests__/full-backup-payload.test.ts": 25, + "lib/export/__tests__/intraday-profile-backup.test.ts": 3, + "lib/export/__tests__/paged-measurements.test.ts": 4, + "lib/export/__tests__/profile-backup-decrypt-failures.test.ts": 1, + "lib/export/__tests__/records-backup.test.ts": 8, + "lib/export/__tests__/reminders-backup-appointment-address.test.ts": 1, + "lib/export/__tests__/restore-backup-owner-mismatch.test.ts": 4, + "lib/export/__tests__/restore-backup-round-trip.test.ts": 5, + "lib/fhir/__tests__/conformance.test.ts": 3, + "lib/fhir/__tests__/rest-module-backstop.test.ts": 4, + "lib/fitbit/__tests__/sync-dead-token.test.ts": 10, + "lib/google-health/__tests__/sync-failsoft.test.ts": 6, + "lib/illness/__tests__/red-flag-notify.test.ts": 2, + "lib/import/__tests__/record-import-route.test.ts": 1, + "lib/insights/__tests__/background-reasoning-wire.test.ts": 2, + "lib/insights/__tests__/biomarker-status.test.ts": 23, + "lib/insights/__tests__/blood-pressure-status.test.ts": 32, + "lib/insights/__tests__/bmi-status.test.ts": 21, + "lib/insights/__tests__/briefing-provider-budget.test.ts": 2, + "lib/insights/__tests__/comprehensive-briefing-reroll.test.ts": 1, + "lib/insights/__tests__/comprehensive-generate-force.test.ts": 2, + "lib/insights/__tests__/feature-cache.test.ts": 2, + "lib/insights/__tests__/general-status.test.ts": 30, + "lib/insights/__tests__/glp1-plateau.test.ts": 17, + "lib/insights/__tests__/locale-pipeline.test.ts": 6, + "lib/insights/__tests__/medication-compliance-status.test.ts": 19, + "lib/insights/__tests__/memory.test.ts": 5, + "lib/insights/__tests__/metric-correlation-context.test.ts": 10, + "lib/insights/__tests__/metric-status.test.ts": 30, + "lib/insights/__tests__/mood-status.test.ts": 14, + "lib/insights/__tests__/payload-budget.test.ts": 13, + "lib/insights/__tests__/pulse-status-timeout.test.ts": 8, + "lib/insights/__tests__/pulse-status.test.ts": 33, + "lib/insights/__tests__/status-batch.test.ts": 33, + "lib/insights/__tests__/status-cache-delegated.test.ts": 2, + "lib/insights/__tests__/status-cache.test.ts": 39, + "lib/insights/__tests__/status-provider-budget.test.ts": 4, + "lib/insights/__tests__/unit-change-refresh.test.ts": 4, + "lib/insights/__tests__/weight-status.test.ts": 33, + "lib/insights/derived/__tests__/baseline.test.ts": 3, + "lib/insights/derived/__tests__/coach-read.test.ts": 1, + "lib/insights/derived/__tests__/derived-assessment-ai.test.ts": 3, + "lib/insights/derived/__tests__/recovery-resolve.test.ts": 1, + "lib/insights/derived/__tests__/sleep-score-oura-rhythm.test.ts": 2, + "lib/insights/derived/__tests__/sleep-score.test.ts": 3, + "lib/insights/derived/__tests__/trajectory.test.ts": 2, + "lib/insights/derived/__tests__/wellness-scores.test.ts": 2, + "lib/insights/narrative/__tests__/period-narrative-deterministic.test.ts": 1, + "lib/insights/narrative/__tests__/period-narrative-locale.test.ts": 3, + "lib/integrations/__tests__/admin-alert.test.ts": 5, + "lib/integrations/__tests__/apple-health-progress.test.ts": 2, + "lib/integrations/__tests__/sources-in-use.test.ts": 4, + "lib/integrations/__tests__/status.test.ts": 85, + "lib/jobs/__tests__/achievement-unlock-sweep.test.ts": 11, + "lib/jobs/__tests__/apple-health-import-reconcile.test.ts": 4, + "lib/jobs/__tests__/async-state-contract.test.ts": 32, + "lib/jobs/__tests__/coach-nudge.test.ts": 15, + "lib/jobs/__tests__/coach-reminder-sweep.test.ts": 11, + "lib/jobs/__tests__/compaction-tombstone-purge-failure.test.ts": 4, + "lib/jobs/__tests__/cycle-prediction-refresh.test.ts": 3, + "lib/jobs/__tests__/data-arrival-worker.test.ts": 18, + "lib/jobs/__tests__/document-ai-run.test.ts": 1, + "lib/jobs/__tests__/document-content-index-backfill.test.ts": 29, + "lib/jobs/__tests__/document-index.test.ts": 6, + "lib/jobs/__tests__/document-summary-reaper.test.ts": 1, + "lib/jobs/__tests__/document-summary.test.ts": 19, + "lib/jobs/__tests__/geo-backfill.test.ts": 1, + "lib/jobs/__tests__/insight-pregenerate.test.ts": 45, + "lib/jobs/__tests__/insight-status-generate.test.ts": 2, + "lib/jobs/__tests__/measurement-maintenance-schedule.test.ts": 2, + "lib/jobs/__tests__/measurement-reminder-byhour.test.ts": 2, + "lib/jobs/__tests__/measurement-reminder.test.ts": 33, + "lib/jobs/__tests__/medication-intake-import.test.ts": 1, + "lib/jobs/__tests__/medication-reminder-check.test.ts": 27, + "lib/jobs/__tests__/mood-reminder.test.ts": 15, + "lib/jobs/__tests__/morning-digest-refresh.test.ts": 6, + "lib/jobs/__tests__/oauth-callback-claim-cleanup.test.ts": 1, + "lib/jobs/__tests__/offhost-backup.test.ts": 5, + "lib/jobs/__tests__/offhost-purge.test.ts": 14, + "lib/jobs/__tests__/pr-detection.test.ts": 2, + "lib/jobs/__tests__/pulse-day-mean-rebuild-state.test.ts": 1, + "lib/jobs/__tests__/reminder-satisfy.test.ts": 9, + "lib/jobs/__tests__/workout-insight-generate-shared.test.ts": 2, + "lib/jobs/__tests__/workout-insight-generate.test.ts": 37, + "lib/jobs/__tests__/workout-insight-no-backfill-spend.test.ts": 3, + "lib/jobs/reminder/__tests__/queue-retention.test.ts": 2, + "lib/jobs/reminder/__tests__/registrar-shared.test.ts": 5, + "lib/labs/__tests__/ocr-capability.test.ts": 4, + "lib/labs/__tests__/ocr-rate-limit.test.ts": 1, + "lib/labs/__tests__/ocr-run.test.ts": 17, + "lib/labs/__tests__/reading-conversion.test.ts": 3, + "lib/labs/__tests__/resolve-or-mint-biomarker.test.ts": 1, + "lib/life-events/__tests__/service.test.ts": 1, + "lib/mcp/__tests__/admin-boundary.test.ts": 1, + "lib/mcp/__tests__/auth.test.ts": 1, + "lib/mcp/__tests__/nutrients-read.test.ts": 6, + "lib/mcp/__tests__/prompts.test.ts": 16, + "lib/mcp/__tests__/resource-templates.test.ts": 17, + "lib/mcp/__tests__/resources.test.ts": 8, + "lib/mcp/__tests__/rich-reads.test.ts": 1, + "lib/mcp/__tests__/search-fetch.test.ts": 12, + "lib/mcp/__tests__/server-resources.test.ts": 3, + "lib/mcp/__tests__/server-write-registration.test.ts": 3, + "lib/mcp/__tests__/tools.test.ts": 64, + "lib/mcp/__tests__/writes.test.ts": 1, + "lib/mcp/oauth/__tests__/connections.test.ts": 13, + "lib/measurement-reminders/__tests__/resolve.test.ts": 7, + "lib/measurement-reminders/__tests__/satisfy.test.ts": 18, + "lib/measurements/__tests__/categories.test.ts": 1, + "lib/measurements/__tests__/pick-canonical-workout-rows.test.ts": 2, + "lib/measurements/__tests__/series-canonical.test.ts": 1, + "lib/medications/__tests__/compliance-payload-heatmap.test.ts": 1, + "lib/medications/__tests__/compliance-payload-pre-creation.test.ts": 1, + "lib/medications/__tests__/intake-import-job-status.test.ts": 2, + "lib/medications/__tests__/intake-slot-dedup-converge.test.ts": 12, + "lib/medications/__tests__/intake-slot-dedup-inventory.test.ts": 7, + "lib/medications/__tests__/intake-slot-dedup-isolation.test.ts": 9, + "lib/medications/__tests__/intake-slot-dedup-pin.test.ts": 8, + "lib/medications/efficacy/__tests__/segmented-effect.test.ts": 1, + "lib/medications/ingredients/__tests__/label-extract.test.ts": 2, + "lib/medications/inventory/__tests__/consumption.test.ts": 8, + "lib/medications/inventory/__tests__/service.test.ts": 3, + "lib/medications/inventory/__tests__/summary.test.ts": 1, + "lib/medications/scheduling/__tests__/band-minter.test.ts": 1, + "lib/medications/scheduling/__tests__/dose-history.test.ts": 1, + "lib/medications/scheduling/__tests__/pin-conflict.test.ts": 2, + "lib/modules/__tests__/measurement-scope.test.ts": 3, + "lib/notifications/__tests__/admin-locale.test.ts": 7, + "lib/notifications/__tests__/client-managed-apns.test.ts": 9, + "lib/notifications/__tests__/dispatch-localised-cache.test.ts": 5, + "lib/notifications/__tests__/dispatcher-global-channel-switch.test.ts": 8, + "lib/notifications/__tests__/dispatcher.test.ts": 23, + "lib/notifications/__tests__/event-defaults.test.ts": 2, + "lib/notifications/__tests__/reminder-dedup.test.ts": 2, + "lib/notifications/senders/__tests__/apns.test.ts": 24, + "lib/notifications/senders/__tests__/ntfy.test.ts": 1, + "lib/notifications/senders/__tests__/push-attempt-record.test.ts": 14, + "lib/notifications/senders/__tests__/webhook.test.ts": 1, + "lib/onboarding/__tests__/first-result-config.test.ts": 1, + "lib/oura/__tests__/client.test.ts": 3, + "lib/polar/__tests__/client.test.ts": 2, + "lib/profile/__tests__/health-facts.test.ts": 1, + "lib/queries/__tests__/use-medication-list-layout.test.ts": 5, + "lib/sharing/__tests__/account-access.test.ts": 5, + "lib/sharing/__tests__/record-session-fence.test.ts": 1, + "lib/tz/__tests__/resolver.test.ts": 14, + "lib/vaccinations/__tests__/document-suggestion.test.ts": 1, + "lib/whoop/__tests__/sync-dead-token.test.ts": 6, + "lib/withings/__tests__/subscription-state.test.ts": 8, + "lib/withings/__tests__/sync-activity.test.ts": 37, + "lib/withings/__tests__/sync-ecg.test.ts": 12, + "lib/withings/__tests__/sync-measure-watermark.test.ts": 14, + "lib/withings/__tests__/sync-plan.test.ts": 13, + "lib/withings/__tests__/sync-plausibility.test.ts": 10, + "lib/withings/__tests__/sync-sleep.test.ts": 23, + "lib/withings/__tests__/sync-typed-classification.test.ts": 1, + "lib/workouts/__tests__/climb-backfill.test.ts": 1 + } +} diff --git a/src/__tests__/baselines/test-source-reads.json b/src/__tests__/baselines/test-source-reads.json new file mode 100644 index 000000000..b9e0430e0 --- /dev/null +++ b/src/__tests__/baselines/test-source-reads.json @@ -0,0 +1,407 @@ +{ + "$comment": "tests that read a source file by path, per test file under src/. Maintained by src/__tests__/test-realism-ratchet.test.ts.", + "count": 400, + "total": 400, + "entries": { + "__tests__/account-payload-consumer-guard.test.ts": 1, + "__tests__/acting-account-boundary-guard.test.ts": 1, + "__tests__/admin-data-wipe-completeness.test.ts": 1, + "__tests__/aging-fixture-dates-guard.test.ts": 1, + "__tests__/ai-call-timeout-guard.test.ts": 1, + "__tests__/ai-capability-surface-guard.test.ts": 1, + "__tests__/ai-consent-enforcement-guard.test.ts": 1, + "__tests__/ai-egress-capability-guard.test.ts": 1, + "__tests__/ai-reasoning-admin-guard.test.ts": 1, + "__tests__/ai-reasoning-effort-guard.test.ts": 1, + "__tests__/analysis-depth-no-ai-guard.test.ts": 1, + "__tests__/api-errors-leaf-guard.test.ts": 1, + "__tests__/apple-health-unit-audit-guard.test.ts": 1, + "__tests__/backfill-preserves-updated-at-guard.test.ts": 1, + "__tests__/background-reasoning-inventory.test.ts": 1, + "__tests__/background-task-settle-guard.test.ts": 1, + "__tests__/backup-plan-classification.test.ts": 1, + "__tests__/backup-readers-stream-guard.test.ts": 1, + "__tests__/backup-schema-lazy-guard.test.ts": 1, + "__tests__/bearer-scope-enforcement-guard.test.ts": 1, + "__tests__/client-bundle-server-import-guard.test.ts": 1, + "__tests__/client-fetch-limit-guard.test.ts": 1, + "__tests__/client-poll-inventory-guard.test.ts": 1, + "__tests__/client-random-id-guard.test.ts": 1, + "__tests__/client-route-zod-guard.test.ts": 1, + "__tests__/client-threshold-literal-guard.test.ts": 1, + "__tests__/coach-focus-ring-guard.test.ts": 1, + "__tests__/coach-stream-lifecycle-guard.test.ts": 1, + "__tests__/coach-usage-owner-columns-guard.test.ts": 1, + "__tests__/compose-env-whitelist-guard.test.ts": 1, + "__tests__/confirm-destructive-style-guard.test.ts": 1, + "__tests__/consent-grant-explicit-guard.test.ts": 1, + "__tests__/container-scripts-ship-guard.test.ts": 1, + "__tests__/contract-stub-inventory.test.ts": 1, + "__tests__/copy-matches-behaviour-guard.test.ts": 1, + "__tests__/correlation-single-implementation-guard.test.ts": 1, + "__tests__/customize-shortcut-guard.test.ts": 1, + "__tests__/cycle-verdict-single-decider-guard.test.ts": 1, + "__tests__/daily-reads-refetch-guard.test.ts": 1, + "__tests__/dashboard-summary-read-plan-guard.test.ts": 1, + "__tests__/dashboard-tile-eligibility-bucket-guard.test.ts": 1, + "__tests__/date-order-explicit-formatter-guard.test.ts": 1, + "__tests__/datetime-helper-guard.test.ts": 1, + "__tests__/dawarich-backup-completeness.test.ts": 1, + "__tests__/dense-measurement-read-guard.test.ts": 1, + "__tests__/derived-response-consumer-guard.test.ts": 1, + "__tests__/destructive-control-guard.test.ts": 1, + "__tests__/discovery-matrix-guard.test.ts": 1, + "__tests__/display-transform-rounding-guard.test.ts": 1, + "__tests__/doctor-report-control-gating-guard.test.ts": 1, + "__tests__/document-sources-owner-only-guard.test.ts": 1, + "__tests__/domain-write-support-guard.test.ts": 1, + "__tests__/dose-ledger-single-source-guard.test.ts": 1, + "__tests__/dynamic-key-exhaustiveness.test.ts": 1, + "__tests__/e2e-conditional-skip-guard.test.ts": 1, + "__tests__/e2e-measurement-gating-guard.test.ts": 1, + "__tests__/e2e-route-cleanup-guard.test.ts": 1, + "__tests__/e2e-shared-session-guard.test.ts": 1, + "__tests__/empty-state-ratchet.test.ts": 1, + "__tests__/encounter-reminder-exclusion.test.ts": 1, + "__tests__/env-empty-is-unset-guard.test.ts": 1, + "__tests__/environment-backup-completeness.test.ts": 1, + "__tests__/error-code-catalogue.test.ts": 1, + "__tests__/error-display-helper-guard.test.ts": 1, + "__tests__/error-level-discipline-guard.test.ts": 1, + "__tests__/external-id-stability-guard.test.ts": 1, + "__tests__/factor-verification-surface-guard.test.ts": 1, + "__tests__/feature-flags-mock-guard.test.ts": 1, + "__tests__/greeting-hour-single-source.test.ts": 1, + "__tests__/guard-floor-guard.test.ts": 1, + "__tests__/healthcheck-boot-storm.test.ts": 1, + "__tests__/i18n-call-site-coverage.test.ts": 1, + "__tests__/i18n-catalog-cache-key.test.ts": 1, + "__tests__/i18n-english-leak-guard.test.ts": 1, + "__tests__/i18n-reverse-coverage.test.ts": 1, + "__tests__/idempotency-contract-publication.test.ts": 1, + "__tests__/inline-action-guard.test.ts": 1, + "__tests__/insights-cached-text-access-guard.test.ts": 1, + "__tests__/insights-unit-literal-guard.test.ts": 1, + "__tests__/intake-toast-single-decision.test.ts": 1, + "__tests__/integration-callback-url-guard.test.ts": 1, + "__tests__/integration-failure-response-consumer-guard.test.ts": 1, + "__tests__/inventory-write-isolation.test.ts": 1, + "__tests__/job-locale-resolution-guard.test.ts": 1, + "__tests__/job-queue-runtime-guard.test.ts": 1, + "__tests__/job-retention-floor-guard.test.ts": 1, + "__tests__/key-mismatch-refusal-guard.test.ts": 1, + "__tests__/latest-per-type-read-guard.test.ts": 1, + "__tests__/linear-fit-single-implementation-guard.test.ts": 1, + "__tests__/list-cap-reader-guard.test.ts": 1, + "__tests__/list-page-guard.test.ts": 1, + "__tests__/malformed-json-status-guard.test.ts": 1, + "__tests__/manage-activity-inventory.test.ts": 1, + "__tests__/managed-profile-contract.test.ts": 1, + "__tests__/measurement-backup-completeness.test.ts": 1, + "__tests__/measurement-write-plausibility-guard.test.ts": 1, + "__tests__/measurement-write-tail-guard.test.ts": 1, + "__tests__/medication-ingredient-backup-completeness.test.ts": 1, + "__tests__/medication-intake-tracking-guard.test.ts": 1, + "__tests__/memory-block-egress-guard.test.ts": 1, + "__tests__/migration-lock-timeout-guard.test.ts": 1, + "__tests__/module-gate-record-scoped.test.ts": 1, + "__tests__/mood-backup-completeness.test.ts": 1, + "__tests__/mood-prediction-write-surface-guard.test.ts": 1, + "__tests__/moodlog-removal-guard.test.ts": 1, + "__tests__/no-control-bytes-guard.test.ts": 1, + "__tests__/no-server-actions-guard.test.ts": 1, + "__tests__/oauth-kit-guard.test.ts": 1, + "__tests__/oidc-native-error-reasons-documented.test.ts": 1, + "__tests__/openapi-account-sharing-components.test.ts": 1, + "__tests__/openapi-route-coverage-guard.test.ts": 1, + "__tests__/openapi-sharing-denial.test.ts": 1, + "__tests__/page-header-h1-guard.test.ts": 1, + "__tests__/page-walk-guard.test.ts": 1, + "__tests__/password-breach-check-switch.test.ts": 1, + "__tests__/prefetch-page-caching-guard.test.ts": 1, + "__tests__/prisma-enum-mapping-guard.test.ts": 1, + "__tests__/profile-rejection-surface-guard.test.ts": 1, + "__tests__/provider-rounding-guard.test.ts": 1, + "__tests__/proxy-body-limit.test.ts": 1, + "__tests__/proxy-demo-mode-allowlist.test.ts": 1, + "__tests__/proxy-page-marker.test.ts": 1, + "__tests__/proxy-relative-redirect-guard.test.ts": 1, + "__tests__/published-env-reaches-the-container.test.ts": 1, + "__tests__/pulse-day-mean-reader-guard.test.ts": 1, + "__tests__/pwa-mobile-shell-guard.test.ts": 1, + "__tests__/query-error-error-prop-guard.test.ts": 1, + "__tests__/read-failure-markup-guard.test.ts": 1, + "__tests__/read-over-post-guard.test.ts": 1, + "__tests__/recent-proof-surface-guard.test.ts": 1, + "__tests__/record-fence-headerless-transport-guard.test.ts": 1, + "__tests__/record-session-fence-acceptance-map.test.ts": 1, + "__tests__/record-session-fence-compat.test.ts": 1, + "__tests__/record-session-fence-guard.test.ts": 1, + "__tests__/record-settings-route-guard.test.ts": 1, + "__tests__/record-settings-ui.test.tsx": 1, + "__tests__/record-write-rate-limit-contract.test.ts": 1, + "__tests__/recurrence-course-anchor-guard.test.ts": 1, + "__tests__/relation-links-backup-completeness.test.ts": 1, + "__tests__/report-selection-egress-guard.test.ts": 1, + "__tests__/resolved-field-consumer-guard.test.ts": 1, + "__tests__/restore-lock-consolidation-guard.test.ts": 1, + "__tests__/rotation-walk-keyset-guard.test.ts": 1, + "__tests__/score-pill-guard.test.ts": 1, + "__tests__/self-gating-child-frame-guard.test.ts": 1, + "__tests__/service-worker-version-anchor.test.ts": 1, + "__tests__/service-worker.test.ts": 1, + "__tests__/session-surface-guard.test.ts": 1, + "__tests__/session-user-projection-guard.test.ts": 1, + "__tests__/settings-legacy-routes.test.ts": 1, + "__tests__/sharing-accessibility-states.test.tsx": 1, + "__tests__/sharing-native-client-contract.test.ts": 1, + "__tests__/sharing-record-fence-contract.test.ts": 1, + "__tests__/sharing-recoverable-errors.test.tsx": 1, + "__tests__/sharing-surface-guard.test.ts": 1, + "__tests__/spread-single-implementation-guard.test.ts": 1, + "__tests__/sql-now-utc-guard.test.ts": 1, + "__tests__/step-up-elevation-guard.test.ts": 1, + "__tests__/step-up-web-caller-guard.test.ts": 1, + "__tests__/success-affordance-guard.test.ts": 1, + "__tests__/sw-security-hardening.test.ts": 1, + "__tests__/text-field-focus-guard.test.ts": 1, + "__tests__/timezone-explicit-formatter-guard.test.ts": 1, + "__tests__/tombstone-read-guard.test.ts": 1, + "__tests__/translate-probe-guard.test.ts": 1, + "__tests__/ui-code-no-dash-separator.test.ts": 1, + "__tests__/unit-preference-display-guard.test.ts": 1, + "__tests__/unit-suite-browser-install-guard.test.ts": 1, + "__tests__/utc-day-key-allowlist-guard.test.ts": 1, + "__tests__/v1341-locale-parity.test.ts": 1, + "__tests__/visit-link-guard.test.ts": 1, + "__tests__/vorsorge-due-bucket-single-home-guard.test.ts": 1, + "__tests__/weight-tile-direction-guard.test.ts": 1, + "__tests__/workout-canonical-identity-guard.test.ts": 1, + "__tests__/workout-sport-provenance-guard.test.ts": 1, + "__tests__/write-rate-limit-guard.test.ts": 1, + "__tests__/written-outcome-response-consumer-guard.test.ts": 1, + "__tests__/zod-refusal-issue-list-guard.test.ts": 1, + "app/__tests__/dashboard-analytics-split.test.ts": 1, + "app/__tests__/dashboard-chart-reveal.test.tsx": 1, + "app/__tests__/dashboard-demo-checklist.test.ts": 1, + "app/__tests__/dashboard-hero-wiring.test.ts": 1, + "app/__tests__/dashboard-hrv-rmssd-fallback.test.tsx": 1, + "app/__tests__/dashboard-suspense-boundaries.test.ts": 1, + "app/__tests__/dashboard-tile-strip-skeleton.test.ts": 1, + "app/__tests__/i18n-boot-script-strategy.test.ts": 1, + "app/__tests__/insights-b3-wiring.test.ts": 1, + "app/__tests__/insights-polish.test.ts": 1, + "app/__tests__/not-found-copy.test.ts": 1, + "app/__tests__/quick-add-labels.test.ts": 1, + "app/__tests__/root-loading-silhouette.test.tsx": 1, + "app/api/__tests__/ai-capability-route-inventory.test.ts": 1, + "app/api/__tests__/inline-schema-strictness-inventory.test.ts": 1, + "app/api/__tests__/module-route-gate-inventory.test.ts": 1, + "app/api/__tests__/read-routes-write-nothing.test.ts": 1, + "app/api/__tests__/webhook-verb-rate-limit-guard.test.ts": 1, + "app/api/auth/native/__tests__/invariants.test.ts": 1, + "app/api/auth/passkeys/__tests__/route.test.ts": 1, + "app/api/insights/__tests__/coach-route-gate-inventory.test.ts": 1, + "app/api/mood-entries/__tests__/bulk-dimension-bounds.test.ts": 1, + "app/api/polar/sync/__tests__/route.test.ts": 1, + "app/api/settings/__tests__/private-origin-routes.test.ts": 1, + "app/api/user/thresholds/__tests__/route.test.ts": 1, + "app/insights/__tests__/assessment-spine-order.test.ts": 1, + "app/insights/__tests__/explainer-metric-keys.test.ts": 1, + "app/insights/__tests__/header-add-pattern.test.ts": 1, + "app/insights/__tests__/page-skeletons.test.ts": 1, + "app/insights/sleep/__tests__/empty-state-href.test.ts": 1, + "app/measurements/__tests__/add-token-resolution.test.ts": 1, + "app/medications/__tests__/detail-page-history-surface.test.ts": 1, + "components/__tests__/design-consistency-guards.test.tsx": 1, + "components/__tests__/failed-read-never-empty.test.tsx": 1, + "components/__tests__/motion-reduce-spin-coverage.test.ts": 1, + "components/admin/__tests__/admin-shell.test.tsx": 1, + "components/admin/__tests__/notification-test-failures.test.ts": 1, + "components/charts/__tests__/chart-error-state.test.tsx": 1, + "components/charts/__tests__/health-chart-overlay-defaults.test.tsx": 1, + "components/charts/__tests__/health-chart-target-link-removed.test.ts": 1, + "components/charts/__tests__/mood-chart-polish.test.tsx": 1, + "components/charts/__tests__/touch-action-guard.test.ts": 1, + "components/cycle/__tests__/use-cycle-ring-has-dial.test.tsx": 1, + "components/dashboard/__tests__/medication-intake-quick-add.test.tsx": 1, + "components/dashboard/__tests__/tile-destinations.test.ts": 1, + "components/day/__tests__/day-door-coverage-guard.test.ts": 1, + "components/day/__tests__/day-not-here-guard.test.ts": 1, + "components/documents/__tests__/document-coach-launch-wiring.test.ts": 1, + "components/documents/__tests__/document-detail-download.test.ts": 1, + "components/documents/__tests__/documents-delete-confirm-guard.test.ts": 1, + "components/forms/__tests__/use-seeded-form-dismissal.test.ts": 1, + "components/i18n/__tests__/maintainership-banner.test.tsx": 1, + "components/insights/__tests__/coach-metric-scope-inventory.test.ts": 1, + "components/insights/__tests__/correlation-card-skeleton.test.ts": 1, + "components/insights/__tests__/health-score-card-geometry.test.tsx": 1, + "components/insights/__tests__/health-score-coverage-axis-guard.test.tsx": 1, + "components/insights/__tests__/insight-section-card.test.tsx": 1, + "components/insights/__tests__/sleep-overview-empty-href.test.ts": 1, + "components/insights/__tests__/tile-header-wrap.test.tsx": 1, + "components/insights/coach-panel/__tests__/coach-settings-overlay.test.tsx": 1, + "components/insights/coach-panel/__tests__/conversations-panel-fold.test.tsx": 1, + "components/insights/coach-panel/__tests__/message-thread.test.tsx": 1, + "components/insights/coach-panel/__tests__/open-at-end.test.ts": 1, + "components/insights/coach-panel/__tests__/panel-touch-targets.test.ts": 1, + "components/insights/coach-panel/__tests__/scroll-to-bottom-button.test.tsx": 1, + "components/insights/mood/__tests__/mood-notes-timeline-removed.test.ts": 1, + "components/layout/__tests__/auth-shell-coach-hoist.test.ts": 1, + "components/layout/__tests__/auth-shell-shared-record-doors.test.tsx": 1, + "components/layout/__tests__/offline-banner.test.tsx": 1, + "components/layout/__tests__/sidebar-nav.test.tsx": 1, + "components/measurements/__tests__/duration-read-as.test.tsx": 1, + "components/measurements/__tests__/measurement-form-glucose-unit.test.tsx": 1, + "components/measurements/__tests__/measurement-list-glucose-unit.test.tsx": 1, + "components/medications/__tests__/add-inventory-dialog.test.tsx": 1, + "components/medications/__tests__/detail-page-cascade.test.tsx": 1, + "components/medications/__tests__/inventory-section-dates.test.tsx": 1, + "components/medications/__tests__/log-injection-site-dialog.test.tsx": 1, + "components/medications/__tests__/medication-order-editor.test.tsx": 1, + "components/medications/__tests__/medication-table.test.tsx": 1, + "components/medications/__tests__/take-all-due.test.tsx": 1, + "components/mood/__tests__/linked-day-timezone.test.ts": 1, + "components/mood/__tests__/mood-note-fulltext.test.tsx": 1, + "components/mood/__tests__/mood-qol-fixes.test.tsx": 1, + "components/onboarding/__tests__/tour-launcher-gate.test.ts": 1, + "components/records/__tests__/emergency-profile-save.test.tsx": 1, + "components/settings/__tests__/action-feedback.test.ts": 1, + "components/settings/__tests__/card-description-length.test.ts": 1, + "components/settings/__tests__/confirm-style-guard.test.ts": 1, + "components/settings/__tests__/dashboard-layout-snapshot-invalidation.test.ts": 1, + "components/settings/__tests__/destructive-controls-confirm.test.tsx": 1, + "components/settings/__tests__/glucose-unit-select.test.tsx": 1, + "components/settings/__tests__/kept-grant-revoke-confirm.test.ts": 1, + "components/settings/__tests__/module-description-length.test.ts": 1, + "components/settings/__tests__/mood-reminder-card.test.tsx": 1, + "components/settings/__tests__/oauth-return-outcome.test.ts": 1, + "components/settings/__tests__/one-piece-anatomy.test.tsx": 1, + "components/settings/__tests__/password-card-placement.test.tsx": 1, + "components/settings/__tests__/save-model-guard.test.ts": 1, + "components/settings/__tests__/score-refusal-consumer-guard.test.ts": 1, + "components/settings/__tests__/settings-card-header-coverage.test.ts": 1, + "components/settings/__tests__/settings-shell.test.tsx": 1, + "components/settings/__tests__/test-connection-button-response.test.tsx": 1, + "components/settings/__tests__/token-revoke-labels.test.ts": 1, + "components/settings/access/__tests__/grant-access-level.test.tsx": 1, + "components/settings/access/__tests__/grant-invite-expiry.test.tsx": 1, + "components/settings/access/__tests__/grant-invite-scope.test.tsx": 1, + "components/settings/access/__tests__/managed-profile-affordances.test.tsx": 1, + "components/settings/account-section/__tests__/account-section-field-error-clearing.test.ts": 1, + "components/settings/account-section/__tests__/display-name-field.test.tsx": 1, + "components/settings/account-section/__tests__/display-preferences-card.test.tsx": 1, + "components/settings/import-panel/__tests__/import-docs-link.test.ts": 1, + "components/settings/integrations/__tests__/cards-render-health.test.ts": 1, + "components/settings/integrations/__tests__/google-health-card.test.tsx": 1, + "components/settings/security-section/__tests__/factor-enrollment-reauth.test.tsx": 1, + "components/settings/security-section/__tests__/passkey-enrollment-reauth.test.tsx": 1, + "components/settings/security-section/__tests__/passkey-removal-step-up.test.tsx": 1, + "components/settings/security-section/__tests__/recent-proof-callers.test.tsx": 1, + "components/settings/security-section/__tests__/totp-card.test.tsx": 1, + "components/timeline/__tests__/label-keys.test.ts": 1, + "components/timeline/__tests__/no-template-key-guard.test.ts": 1, + "components/ui/__tests__/card-density.test.tsx": 1, + "components/ui/__tests__/card-header-rows.test.ts": 1, + "components/ui/__tests__/filter-bar.test.tsx": 1, + "components/ui/__tests__/segmented.test.tsx": 1, + "hooks/__tests__/use-insight-status.test.ts": 1, + "lib/__tests__/geo-offline-detection.test.ts": 1, + "lib/__tests__/query-keys.test.ts": 1, + "lib/__tests__/ssrf-pin-inventory.test.ts": 1, + "lib/ai/__tests__/provider-state-never-persisted.test.ts": 1, + "lib/ai/coach/__tests__/coach-availability-window-honesty.test.ts": 1, + "lib/ai/coach/__tests__/coach-history-reach-guard.test.ts": 1, + "lib/ai/coach/__tests__/coach-locale-no-german-default.test.ts": 1, + "lib/ai/coach/__tests__/data-field-injection.test.ts": 1, + "lib/ai/coach/__tests__/snapshot.test.ts": 1, + "lib/ai/coach/memory/__tests__/contract.test.ts": 1, + "lib/ai/coach/tools/__tests__/turn-budget.test.ts": 1, + "lib/ai/coach/turn/__tests__/steps.test.ts": 1, + "lib/analytics/__tests__/glucose-panel-window.test.ts": 1, + "lib/analytics/compliance/__tests__/as-needed-compliance-exclusion.test.ts": 1, + "lib/analytics/mood-prognosis/__tests__/gates.test.ts": 1, + "lib/analytics/score/__tests__/reference-citation-guard.test.ts": 1, + "lib/auth/__tests__/step-up.test.ts": 1, + "lib/charts/__tests__/axis-unit.test.ts": 1, + "lib/charts/__tests__/data-ready-latch.test.ts": 1, + "lib/charts/__tests__/fetch-window.test.ts": 1, + "lib/command-palette/__tests__/settings-cards.test.ts": 1, + "lib/crypto/__tests__/encrypted-column-writers.test.ts": 1, + "lib/crypto/__tests__/encrypted-columns.test.ts": 1, + "lib/dashboard/__tests__/snapshot.test.ts": 1, + "lib/documents/__tests__/fenced-chat-module-graph.test.ts": 1, + "lib/documents/__tests__/rasterize-pdf-font-data.test.ts": 1, + "lib/export/__tests__/full-backup-stream-heap-gauge.test.ts": 1, + "lib/export/__tests__/intraday-profile-backup.test.ts": 1, + "lib/feature-flags/__tests__/no-dead-assistant-flag.test.ts": 1, + "lib/fitbit/__tests__/sync-module-graph.test.ts": 1, + "lib/format/__tests__/bytes.test.ts": 1, + "lib/google-health/__tests__/sync-module-graph.test.ts": 1, + "lib/http/__tests__/deprecation.test.ts": 1, + "lib/i18n/__tests__/locale-first-paint-single-write.test.tsx": 1, + "lib/i18n/__tests__/plural-locale-completeness.test.ts": 1, + "lib/import/__tests__/record-import-rows.test.ts": 1, + "lib/insights/__tests__/prompt-soft-delete-filter.test.ts": 1, + "lib/insights/eval/__tests__/tone-harness.test.ts": 1, + "lib/jobs/__tests__/ai-job-capability-inventory.test.ts": 1, + "lib/jobs/__tests__/apple-health-import-ecg.test.ts": 1, + "lib/jobs/__tests__/apple-health-import-export-date.test.ts": 1, + "lib/jobs/__tests__/apple-health-import-reconcile.test.ts": 1, + "lib/jobs/__tests__/apple-health-import-send-policy.test.ts": 1, + "lib/jobs/__tests__/coach-memory-refresh-queue.test.ts": 1, + "lib/jobs/__tests__/coach-message-cleanup-queue.test.ts": 1, + "lib/jobs/__tests__/cumulative-pr-rederive-queue.test.ts": 1, + "lib/jobs/__tests__/cycle-reminder-queue.test.ts": 1, + "lib/jobs/__tests__/daily-briefing.test.ts": 1, + "lib/jobs/__tests__/data-arrival-provider-isolation.test.ts": 1, + "lib/jobs/__tests__/data-arrival-queue.test.ts": 1, + "lib/jobs/__tests__/data-backup-send-policy.test.ts": 1, + "lib/jobs/__tests__/drain-cumulative-queue.test.ts": 1, + "lib/jobs/__tests__/fitbit-queues.test.ts": 1, + "lib/jobs/__tests__/geo-backfill.test.ts": 1, + "lib/jobs/__tests__/geolite2-fetch-queue.test.ts": 1, + "lib/jobs/__tests__/google-health-queues.test.ts": 1, + "lib/jobs/__tests__/insight-pregenerate.test.ts": 1, + "lib/jobs/__tests__/insight-status-generate.test.ts": 1, + "lib/jobs/__tests__/intake-slot-dedup-queue.test.ts": 1, + "lib/jobs/__tests__/integration-backfill-admission.test.ts": 1, + "lib/jobs/__tests__/job-failures-retention.test.ts": 1, + "lib/jobs/__tests__/job-outcome-source-inventory.test.ts": 1, + "lib/jobs/__tests__/mcp-token-cleanup-queue.test.ts": 1, + "lib/jobs/__tests__/mean-consolidation-queue.test.ts": 1, + "lib/jobs/__tests__/measurement-reminder-queue.test.ts": 1, + "lib/jobs/__tests__/measurement-tombstone-cleanup.test.ts": 1, + "lib/jobs/__tests__/nightscout-queues.test.ts": 1, + "lib/jobs/__tests__/period-narrative-warm.test.ts": 1, + "lib/jobs/__tests__/queue-policy-tables.test.ts": 1, + "lib/jobs/__tests__/read-side-effect-queues.test.ts": 1, + "lib/jobs/__tests__/recovery-score-queue.test.ts": 1, + "lib/jobs/__tests__/sleep-timeline-queues.test.ts": 1, + "lib/jobs/__tests__/status-cron-candidates.test.ts": 1, + "lib/jobs/__tests__/step-consolidation-repair-queue.test.ts": 1, + "lib/jobs/__tests__/stress-strain-retention-queue.test.ts": 1, + "lib/jobs/__tests__/subsystem-surface.test.ts": 1, + "lib/jobs/__tests__/tls-pin-monitor-queue.test.ts": 1, + "lib/jobs/__tests__/whoop-queues.test.ts": 1, + "lib/jobs/__tests__/withings-ecg-worker.test.ts": 1, + "lib/jobs/__tests__/withings-queues.test.ts": 1, + "lib/jobs/__tests__/workout-insight-queue.test.ts": 1, + "lib/jobs/reminder/__tests__/integration-cohort-outcomes.test.ts": 1, + "lib/labs/__tests__/local-ocr.test.ts": 1, + "lib/links/__tests__/link-surface-guard.test.ts": 1, + "lib/links/__tests__/relation-matrix.test.ts": 1, + "lib/medications/__tests__/auto-mark-taken.test.ts": 1, + "lib/medications/__tests__/glp1-knowledge-drift.test.ts": 1, + "lib/medications/inventory/__tests__/summary.test.ts": 1, + "lib/modules/__tests__/surface-module-registry.test.ts": 1, + "lib/mood/__tests__/context-vocabulary.test.ts": 1, + "lib/notifications/__tests__/event-defaults.test.ts": 1, + "lib/pwa/__tests__/sw-cache-policy.test.ts": 1, + "lib/record-activity/__tests__/verb-map-covers-routes.test.ts": 1, + "lib/sharing/__tests__/record-session-fence.test.ts": 1, + "lib/sources/__tests__/source-priority-whoop-applehealth.test.ts": 1, + "lib/whoop/__tests__/sync-module-graph.test.ts": 1, + "lib/whoop/__tests__/sync-user-whoop.test.ts": 1 + } +} diff --git a/src/__tests__/baselines/version-stamp-comments.json b/src/__tests__/baselines/version-stamp-comments.json new file mode 100644 index 000000000..00e6549a5 --- /dev/null +++ b/src/__tests__/baselines/version-stamp-comments.json @@ -0,0 +1,2992 @@ +{ + "$comment": "Comment lines that open with a release version, per file under src/. Maintained by src/__tests__/version-stamp-ratchet.test.ts.", + "count": 2985, + "total": 10634, + "entries": { + "__tests__/account-payload-consumer-guard.test.ts": 2, + "__tests__/acting-account-boundary-guard.test.ts": 7, + "__tests__/ai-input-invalidation-guard.test.ts": 2, + "__tests__/ai-reasoning-admin-guard.test.ts": 1, + "__tests__/api/analytics/since-cap.test.ts": 4, + "__tests__/api/auth/registration-status/route.test.ts": 1, + "__tests__/background-reasoning-inventory.test.ts": 1, + "__tests__/backup-plan-classification.test.ts": 1, + "__tests__/coach-usage-owner-columns-guard.test.ts": 1, + "__tests__/coach-voice-guard.test.ts": 1, + "__tests__/contract-stub-inventory.test.ts": 1, + "__tests__/customize-shortcut-guard.test.ts": 1, + "__tests__/daily-reads-refetch-guard.test.ts": 1, + "__tests__/data-wipe-completeness.test.ts": 1, + "__tests__/dawarich-backup-completeness.test.ts": 1, + "__tests__/dawarich-no-location-schema.test.ts": 1, + "__tests__/derived-response-consumer-guard.test.ts": 1, + "__tests__/domain-write-support-guard.test.ts": 1, + "__tests__/dynamic-key-exhaustiveness.test.ts": 1, + "__tests__/encounter-reminder-exclusion.test.ts": 2, + "__tests__/environment-backup-completeness.test.ts": 1, + "__tests__/health-score-basis-additive-contract.test.ts": 1, + "__tests__/helpers/i18n-reference.ts": 1, + "__tests__/i18n-call-site-coverage.test.ts": 2, + "__tests__/i18n-drift-guard.test.ts": 2, + "__tests__/i18n-english-leak-guard.test.ts": 2, + "__tests__/i18n-reference-matcher.test.ts": 1, + "__tests__/i18n-reverse-coverage.test.ts": 2, + "__tests__/insights-unit-literal-guard.test.ts": 1, + "__tests__/inventory-write-isolation.test.ts": 1, + "__tests__/manage-activity-inventory.test.ts": 1, + "__tests__/managed-record-navigation.test.tsx": 1, + "__tests__/measurement-backup-completeness.test.ts": 1, + "__tests__/measurement-write-plausibility-guard.test.ts": 2, + "__tests__/medication-ingredient-backup-completeness.test.ts": 1, + "__tests__/medication-intake-tracking-guard.test.ts": 2, + "__tests__/medications-efficacy-verb-guard.test.ts": 1, + "__tests__/memory-block-egress-guard.test.ts": 1, + "__tests__/openapi-sharing-denial.test.ts": 1, + "__tests__/profile-rejection-surface-guard.test.ts": 1, + "__tests__/proxy-claim-public.test.ts": 1, + "__tests__/proxy-demo-mode-allowlist.test.ts": 3, + "__tests__/proxy-invite-public.test.ts": 2, + "__tests__/proxy-onboarding-redirect.test.ts": 2, + "__tests__/proxy-privacy-public.test.ts": 1, + "__tests__/proxy-well-known-public.test.ts": 1, + "__tests__/published-env-reaches-the-container.test.ts": 1, + "__tests__/record-settings-configuration.test.ts": 1, + "__tests__/record-settings-contract.test.ts": 2, + "__tests__/recurrence-course-anchor-guard.test.ts": 1, + "__tests__/relation-links-backup-completeness.test.ts": 1, + "__tests__/resolved-field-consumer-guard.test.ts": 1, + "__tests__/service-worker.test.ts": 2, + "__tests__/session-surface-guard.test.ts": 7, + "__tests__/settings-legacy-routes.test.ts": 1, + "__tests__/shared-record-navigation.test.tsx": 1, + "__tests__/sharing-accessibility-states.test.tsx": 1, + "__tests__/sharing-boundary-copy.test.ts": 1, + "__tests__/sharing-legacy-client-contract.test.ts": 1, + "__tests__/sharing-native-client-contract.test.ts": 1, + "__tests__/sharing-record-fence-contract.test.ts": 1, + "__tests__/sharing-surface-guard.test.ts": 40, + "__tests__/success-affordance-guard.test.ts": 8, + "__tests__/timezone-explicit-formatter-guard.test.ts": 1, + "__tests__/unit-preference-display-guard.test.ts": 2, + "__tests__/v1341-locale-parity.test.ts": 1, + "__tests__/v1391-additive-contract.test.ts": 1, + "__tests__/write-rate-limit-guard.test.ts": 1, + "app/__tests__/dashboard-analytics-split.test.ts": 2, + "app/__tests__/dashboard-chart-reveal.test.tsx": 2, + "app/__tests__/dashboard-suspense-boundaries.test.ts": 2, + "app/__tests__/dashboard-tile-strip-skeleton.test.ts": 4, + "app/__tests__/insights-b3-wiring.test.ts": 5, + "app/__tests__/insights-polish.test.ts": 5, + "app/__tests__/not-found-copy.test.ts": 1, + "app/__tests__/quick-add-labels.test.ts": 3, + "app/__tests__/root-loading-silhouette.test.tsx": 1, + "app/__tests__/sheet-dirty-check.test.ts": 1, + "app/about/page.tsx": 1, + "app/achievements/__tests__/page.test.tsx": 2, + "app/achievements/page.tsx": 4, + "app/admin/[section]/renderer.tsx": 6, + "app/api/__tests__/ai-route-inventory.ts": 1, + "app/api/__tests__/module-route-gate-inventory.test.ts": 23, + "app/api/account/grants/route.ts": 3, + "app/api/account/switch/route.ts": 1, + "app/api/admin/ai-quality/route.ts": 1, + "app/api/admin/ai-settings/__tests__/route.test.ts": 2, + "app/api/admin/ai-settings/route.ts": 1, + "app/api/admin/audit-log/route.ts": 3, + "app/api/admin/backups/[id]/summary/route.ts": 1, + "app/api/admin/backups/route.ts": 1, + "app/api/admin/data/route.ts": 2, + "app/api/admin/drain-per-sample-cumulative/route.ts": 1, + "app/api/admin/encryption/rotate/route.ts": 1, + "app/api/admin/encryption/status/route.ts": 1, + "app/api/admin/host-metrics/route.ts": 1, + "app/api/admin/invites/[id]/route.ts": 1, + "app/api/admin/invites/route.ts": 2, + "app/api/admin/maintenance/measurements/route.ts": 1, + "app/api/admin/notifications/diagnostic/__tests__/route.test.ts": 2, + "app/api/admin/notifications/diagnostic/route.ts": 2, + "app/api/admin/notifications/health/__tests__/route.test.ts": 1, + "app/api/admin/notifications/health/route.ts": 1, + "app/api/admin/notifications/reminder-check/route.ts": 3, + "app/api/admin/notifications/test/__tests__/route.test.ts": 1, + "app/api/admin/notifications/test/route.ts": 2, + "app/api/admin/provider-health/__tests__/route.test.ts": 6, + "app/api/admin/provider-health/route.ts": 6, + "app/api/admin/settings/__tests__/route.test.ts": 1, + "app/api/admin/settings/assistant-flags/route.ts": 3, + "app/api/admin/settings/module-availability/route.ts": 1, + "app/api/admin/settings/route.ts": 3, + "app/api/admin/status/__tests__/route.test.ts": 1, + "app/api/admin/status/route.ts": 1, + "app/api/admin/users/[id]/reset-password/route.ts": 2, + "app/api/admin/users/[id]/route.ts": 2, + "app/api/admin/users/route.ts": 1, + "app/api/ai-runs/[id]/route.ts": 1, + "app/api/ai/test/__tests__/route.test.ts": 2, + "app/api/ai/test/route.ts": 2, + "app/api/allergies/[id]/route.ts": 2, + "app/api/allergies/route.ts": 1, + "app/api/analytics/__tests__/route.test.ts": 27, + "app/api/analytics/range/route.ts": 2, + "app/api/analytics/route.ts": 39, + "app/api/anamnesis/facts/__tests__/route.test.ts": 1, + "app/api/auth/claim/__tests__/route.test.ts": 1, + "app/api/auth/claim/preview/route.ts": 1, + "app/api/auth/claim/route.ts": 1, + "app/api/auth/codex/device-poll/route.ts": 1, + "app/api/auth/codex/device-start/route.ts": 1, + "app/api/auth/login/__tests__/native-token.test.ts": 2, + "app/api/auth/login/__tests__/route.test.ts": 1, + "app/api/auth/login/route.ts": 5, + "app/api/auth/me/coach-prefs/__tests__/route.test.ts": 1, + "app/api/auth/me/coach-prefs/route.ts": 7, + "app/api/auth/me/cycle-prefs/route.ts": 1, + "app/api/auth/me/devices/__tests__/route.test.ts": 1, + "app/api/auth/me/devices/[id]/__tests__/route.test.ts": 1, + "app/api/auth/me/devices/[id]/route.ts": 3, + "app/api/auth/me/devices/route.ts": 2, + "app/api/auth/me/diabetes/route.ts": 1, + "app/api/auth/me/disable-coach/route.ts": 1, + "app/api/auth/me/health-score-config/__tests__/route.test.ts": 3, + "app/api/auth/me/health-score-config/route.ts": 1, + "app/api/auth/me/injection-site-prefs/route.ts": 1, + "app/api/auth/me/labs-local-ocr/route.ts": 1, + "app/api/auth/me/locale/route.ts": 1, + "app/api/auth/me/mfa/disable/route.ts": 1, + "app/api/auth/me/mfa/totp/confirm/route.ts": 1, + "app/api/auth/me/mfa/webauthn/register/verify/route.ts": 1, + "app/api/auth/me/modules/__tests__/route.test.ts": 4, + "app/api/auth/me/modules/route.ts": 4, + "app/api/auth/me/notification-prefs/__tests__/route.test.ts": 3, + "app/api/auth/me/notification-prefs/route.ts": 4, + "app/api/auth/me/route.ts": 28, + "app/api/auth/me/security-activity/route.ts": 1, + "app/api/auth/me/sessions/[id]/route.ts": 1, + "app/api/auth/me/sessions/route.ts": 1, + "app/api/auth/me/source-priority/route.ts": 2, + "app/api/auth/me/timezone/route.ts": 1, + "app/api/auth/me/unit-preference/route.ts": 1, + "app/api/auth/mfa/verify/route.ts": 1, + "app/api/auth/mfa/webauthn/verify/route.ts": 1, + "app/api/auth/oidc/callback/__tests__/route.test.ts": 1, + "app/api/auth/oidc/callback/route.ts": 7, + "app/api/auth/oidc/login/route.ts": 2, + "app/api/auth/passkey/login-options/route.ts": 1, + "app/api/auth/passkey/login-verify/__tests__/native-token.test.ts": 2, + "app/api/auth/passkey/login-verify/route.ts": 5, + "app/api/auth/passkeys/__tests__/route.test.ts": 1, + "app/api/auth/password/__tests__/route.test.ts": 3, + "app/api/auth/password/route.ts": 5, + "app/api/auth/refresh/__tests__/error-code.test.ts": 1, + "app/api/auth/refresh/route.ts": 2, + "app/api/auth/register/__tests__/route.test.ts": 5, + "app/api/auth/register/route.ts": 11, + "app/api/biomarkers/__tests__/error-contract.test.ts": 1, + "app/api/biomarkers/[id]/__tests__/route.test.ts": 2, + "app/api/biomarkers/[id]/route.ts": 6, + "app/api/biomarkers/route.ts": 4, + "app/api/c/[token]/unlock/__tests__/route.test.ts": 1, + "app/api/c/[token]/unlock/route.ts": 1, + "app/api/coach/about-me/__tests__/route-concurrency.test.ts": 1, + "app/api/coach/about-me/adopt/__tests__/route.test.ts": 2, + "app/api/coach/about-me/adopt/route.ts": 3, + "app/api/coach/about-me/questions/__tests__/route-module-gate.test.ts": 1, + "app/api/coach/about-me/questions/route.ts": 3, + "app/api/coach/about-me/route.ts": 4, + "app/api/coach/plans/__tests__/route.test.ts": 1, + "app/api/coach/plans/[id]/route.ts": 2, + "app/api/coach/plans/route.ts": 1, + "app/api/coach/reminder-suggestions/route.ts": 1, + "app/api/coach/reminders/__tests__/route.test.ts": 1, + "app/api/coach/reminders/[id]/route.ts": 1, + "app/api/coach/reminders/route.ts": 1, + "app/api/coach/suggested-actions/__tests__/route.test.ts": 1, + "app/api/coach/suggested-actions/route.ts": 1, + "app/api/consent/ai/__tests__/route.test.ts": 3, + "app/api/consent/ai/latest/__tests__/route.test.ts": 3, + "app/api/consent/ai/latest/route.ts": 3, + "app/api/consent/ai/route.ts": 3, + "app/api/custom-metrics/[id]/entries/__tests__/note-encryption.test.ts": 1, + "app/api/custom-metrics/[id]/entries/__tests__/soft-delete-restore.test.ts": 1, + "app/api/custom-metrics/[id]/entries/[entryId]/route.ts": 2, + "app/api/custom-metrics/[id]/entries/route.ts": 4, + "app/api/custom-metrics/[id]/route.ts": 1, + "app/api/custom-metrics/route.ts": 1, + "app/api/cycle/__tests__/external-id-floor.test.ts": 1, + "app/api/cycle/calendar/route.ts": 1, + "app/api/cycle/cycles/[id]/route.ts": 1, + "app/api/cycle/day-logs/[id]/route.ts": 4, + "app/api/cycle/day-logs/route.ts": 1, + "app/api/cycle/period/route.ts": 4, + "app/api/cycle/symptoms/custom/__tests__/custom-crud.test.ts": 1, + "app/api/cycle/symptoms/custom/[key]/route.ts": 1, + "app/api/cycle/symptoms/custom/route.ts": 3, + "app/api/dashboard/chart-overlay-prefs/__tests__/route.test.ts": 1, + "app/api/dashboard/chart-overlay-prefs/route.ts": 2, + "app/api/dashboard/snapshot/__tests__/route.test.ts": 2, + "app/api/dashboard/snapshot/route.ts": 1, + "app/api/dashboard/summary/__tests__/route.test.ts": 16, + "app/api/dashboard/summary/route.ts": 31, + "app/api/dashboard/widgets/__tests__/route.test.ts": 4, + "app/api/dashboard/widgets/route.ts": 20, + "app/api/devices/__tests__/route.test.ts": 1, + "app/api/devices/[id]/__tests__/route.test.ts": 1, + "app/api/devices/[id]/route.ts": 1, + "app/api/devices/route.ts": 4, + "app/api/documents/inbound/__tests__/route.test.ts": 1, + "app/api/documents/inbound/[id]/__tests__/route.test.ts": 1, + "app/api/documents/inbound/[id]/chat/__tests__/route.test.ts": 2, + "app/api/documents/inbound/[id]/chat/route.ts": 1, + "app/api/documents/inbound/[id]/confirm/route.ts": 2, + "app/api/documents/inbound/[id]/extract/__tests__/route.test.ts": 1, + "app/api/documents/inbound/[id]/extract/route.ts": 2, + "app/api/documents/inbound/[id]/facts/[factId]/route.ts": 1, + "app/api/documents/inbound/[id]/index/route.ts": 2, + "app/api/documents/inbound/[id]/original/__tests__/route.test.ts": 1, + "app/api/documents/inbound/[id]/original/route.ts": 1, + "app/api/documents/inbound/[id]/route.ts": 3, + "app/api/documents/inbound/[id]/suggest/route.ts": 2, + "app/api/documents/inbound/[id]/summary/route.ts": 2, + "app/api/documents/inbound/[id]/thumbnail/route.ts": 1, + "app/api/documents/inbound/reindex/route.ts": 1, + "app/api/documents/inbound/route.ts": 3, + "app/api/documents/inbound/usage/route.ts": 1, + "app/api/environment/backfill/__tests__/route.test.ts": 1, + "app/api/environment/route.ts": 1, + "app/api/environment/travel/route.ts": 1, + "app/api/export/__tests__/per-type-routes.test.ts": 5, + "app/api/export/__tests__/soft-delete-filter.test.ts": 9, + "app/api/export/encrypted/__tests__/route.test.ts": 4, + "app/api/export/encrypted/route.ts": 1, + "app/api/export/full-backup/route.ts": 1, + "app/api/export/health-record/__tests__/route.test.ts": 3, + "app/api/export/health-record/route.ts": 2, + "app/api/export/measurements/route.ts": 2, + "app/api/export/medications/route.ts": 2, + "app/api/export/mood/route.ts": 2, + "app/api/family-history/[id]/route.ts": 2, + "app/api/family-history/route.ts": 1, + "app/api/fhir/__tests__/observation-series.test.ts": 1, + "app/api/fhir/__tests__/route.test.ts": 1, + "app/api/fhir/metadata/route.ts": 1, + "app/api/fhir/Patient/$everything/route.ts": 1, + "app/api/fitbit/callback/route.ts": 1, + "app/api/fitbit/connect/route.ts": 1, + "app/api/fitbit/credentials/route.ts": 1, + "app/api/fitbit/disconnect/route.ts": 1, + "app/api/gamification/achievements/__tests__/hidden-redaction.test.ts": 4, + "app/api/gamification/achievements/__tests__/ios-format.test.ts": 5, + "app/api/gamification/achievements/__tests__/module-gate.test.ts": 3, + "app/api/gamification/achievements/route.ts": 6, + "app/api/google-health/credentials/route.ts": 1, + "app/api/illness/episodes/[id]/__tests__/body-site.test.ts": 1, + "app/api/illness/episodes/[id]/correlation/route.ts": 1, + "app/api/illness/episodes/[id]/day-logs/__tests__/route.test.ts": 1, + "app/api/illness/episodes/[id]/day-logs/route.ts": 1, + "app/api/illness/episodes/[id]/resolve/route.ts": 1, + "app/api/illness/episodes/[id]/restore/__tests__/route.test.ts": 1, + "app/api/illness/episodes/[id]/restore/route.ts": 1, + "app/api/illness/episodes/[id]/route.ts": 4, + "app/api/illness/episodes/route.ts": 1, + "app/api/import/__tests__/route.test.ts": 3, + "app/api/import/csv/__tests__/route.test.ts": 2, + "app/api/import/route.ts": 8, + "app/api/ingest/medication/__tests__/route.test.ts": 1, + "app/api/ingest/medication/route.ts": 7, + "app/api/insights/biomarker-assessment/route.ts": 1, + "app/api/insights/blood-pressure-status/route.ts": 2, + "app/api/insights/bmi-status/route.ts": 2, + "app/api/insights/breathing-screening/route.ts": 2, + "app/api/insights/cards/__tests__/route.test.ts": 2, + "app/api/insights/cards/route.ts": 9, + "app/api/insights/chat/__tests__/dialog-harness.ts": 1, + "app/api/insights/chat/__tests__/golden-transcripts.test.ts": 1, + "app/api/insights/chat/__tests__/route-activity.test.ts": 1, + "app/api/insights/chat/__tests__/route-capability-gate.test.ts": 1, + "app/api/insights/chat/__tests__/route-fence-guard.test.ts": 1, + "app/api/insights/chat/__tests__/route-guard-ledger.test.ts": 3, + "app/api/insights/chat/__tests__/route-guard.test.ts": 1, + "app/api/insights/chat/__tests__/route-list-query.test.ts": 1, + "app/api/insights/chat/__tests__/route-snapshot-once.test.ts": 2, + "app/api/insights/chat/__tests__/route-tool-mode.test.ts": 4, + "app/api/insights/chat/__tests__/route-workout-scope.test.ts": 1, + "app/api/insights/chat/[id]/attachments/__tests__/route.test.ts": 1, + "app/api/insights/chat/[id]/attachments/[documentId]/route.ts": 1, + "app/api/insights/chat/[id]/attachments/route.ts": 1, + "app/api/insights/chat/[id]/messages/[messageId]/trail/__tests__/route.test.ts": 1, + "app/api/insights/chat/[id]/route.ts": 1, + "app/api/insights/chat/fenced/__tests__/route.test.ts": 1, + "app/api/insights/chat/fenced/route.ts": 1, + "app/api/insights/chat/messages/[id]/feedback/route.ts": 3, + "app/api/insights/chat/route.ts": 9, + "app/api/insights/coach-read/route.ts": 2, + "app/api/insights/coach/facts/__tests__/route-memory.test.ts": 1, + "app/api/insights/coach/facts/__tests__/route.test.ts": 1, + "app/api/insights/coach/facts/[id]/route.ts": 2, + "app/api/insights/coach/facts/route.ts": 1, + "app/api/insights/coach/nudge-status/route.ts": 1, + "app/api/insights/coach/seen/route.ts": 1, + "app/api/insights/comprehensive/__tests__/route.test.ts": 9, + "app/api/insights/comprehensive/route.ts": 24, + "app/api/insights/correlations/__tests__/route.test.ts": 10, + "app/api/insights/correlations/route.ts": 9, + "app/api/insights/derived/__tests__/route.test.ts": 4, + "app/api/insights/derived/batch/__tests__/route.test.ts": 3, + "app/api/insights/derived/batch/route.ts": 6, + "app/api/insights/derived/route.ts": 5, + "app/api/insights/ecg/__tests__/route.test.ts": 1, + "app/api/insights/ecg/[id]/__tests__/route.test.ts": 1, + "app/api/insights/ecg/[id]/route.ts": 2, + "app/api/insights/ecg/route.ts": 1, + "app/api/insights/feedback/route.ts": 2, + "app/api/insights/generate/__tests__/route.test.ts": 12, + "app/api/insights/generate/route.ts": 57, + "app/api/insights/glp1-plateau/route.ts": 2, + "app/api/insights/glp1-timeline/route.ts": 4, + "app/api/insights/health-status/route.ts": 3, + "app/api/insights/labs-changes/route.ts": 2, + "app/api/insights/layout/__tests__/route.test.ts": 4, + "app/api/insights/layout/route.ts": 7, + "app/api/insights/medication-compliance-status/route.ts": 2, + "app/api/insights/metric-status/__tests__/route.test.ts": 1, + "app/api/insights/metric-status/route.ts": 3, + "app/api/insights/mood-status/route.ts": 2, + "app/api/insights/narrative/route.ts": 3, + "app/api/insights/patterns/[id]/route.ts": 1, + "app/api/insights/patterns/route.ts": 1, + "app/api/insights/pregenerate/route.ts": 1, + "app/api/insights/provider-chain/__tests__/put.test.ts": 1, + "app/api/insights/provider-chain/__tests__/route.test.ts": 2, + "app/api/insights/provider-chain/route.ts": 3, + "app/api/insights/pulse-status/route.ts": 2, + "app/api/insights/pulse/intraday/route.ts": 1, + "app/api/insights/rhythm-events/__tests__/route.test.ts": 2, + "app/api/insights/rhythm-events/route.ts": 2, + "app/api/insights/settings/route.ts": 1, + "app/api/insights/targets/__tests__/route.test.ts": 1, + "app/api/insights/targets/route.ts": 2, + "app/api/insights/weight-status/route.ts": 2, + "app/api/integrations/healthkit/__tests__/route.test.ts": 4, + "app/api/integrations/healthkit/route.ts": 3, + "app/api/integrations/status/__tests__/route.test.ts": 2, + "app/api/integrations/status/route.ts": 7, + "app/api/integrations/withings/resume/__tests__/route.test.ts": 1, + "app/api/integrations/withings/resume/route.ts": 1, + "app/api/internal/deploy-webhook/__tests__/route.test.ts": 2, + "app/api/internal/deploy-webhook/route.ts": 1, + "app/api/internal/web-vitals/__tests__/route.test.ts": 1, + "app/api/internal/web-vitals/route.ts": 1, + "app/api/labs/__tests__/error-contract.test.ts": 1, + "app/api/labs/[id]/route.ts": 7, + "app/api/labs/ocr/capability/route.ts": 1, + "app/api/labs/ocr/commit/__tests__/route.test.ts": 2, + "app/api/labs/ocr/commit/route.ts": 4, + "app/api/labs/ocr/extract/__tests__/route.test.ts": 2, + "app/api/labs/ocr/extract/route.ts": 4, + "app/api/labs/restore/route.ts": 2, + "app/api/labs/route.ts": 10, + "app/api/mcp/oauth/__tests__/authorize.test.ts": 1, + "app/api/measurement-reminders/[id]/__tests__/route.test.ts": 3, + "app/api/measurement-reminders/[id]/__tests__/skip-snooze-history.test.ts": 1, + "app/api/measurement-reminders/[id]/complete/__tests__/route.test.ts": 2, + "app/api/measurement-reminders/[id]/complete/route.ts": 2, + "app/api/measurement-reminders/[id]/history/route.ts": 1, + "app/api/measurement-reminders/[id]/route.ts": 8, + "app/api/measurement-reminders/[id]/satisfy/__tests__/route.test.ts": 1, + "app/api/measurement-reminders/[id]/satisfy/route.ts": 2, + "app/api/measurement-reminders/[id]/skip/route.ts": 1, + "app/api/measurement-reminders/[id]/snooze/route.ts": 1, + "app/api/measurement-reminders/route.ts": 3, + "app/api/measurements/__tests__/arrival-wiring.test.ts": 1, + "app/api/measurements/__tests__/group-by-day.test.ts": 8, + "app/api/measurements/__tests__/put-duplicate-timestamp.test.ts": 1, + "app/api/measurements/__tests__/range-aggregation-route.test.ts": 3, + "app/api/measurements/__tests__/zod-multi-issue.test.ts": 4, + "app/api/measurements/[id]/__tests__/route.test.ts": 2, + "app/api/measurements/[id]/route.ts": 16, + "app/api/measurements/batch/__tests__/aggregated-hr-wire.test.ts": 1, + "app/api/measurements/batch/__tests__/duplicate-prefilter.test.ts": 2, + "app/api/measurements/batch/__tests__/hr-bucket-observability.test.ts": 1, + "app/api/measurements/batch/__tests__/pr-detection-hook.test.ts": 1, + "app/api/measurements/batch/__tests__/sync-trigger.test.ts": 1, + "app/api/measurements/batch/route.ts": 49, + "app/api/measurements/bulk-delete/__tests__/route.test.ts": 2, + "app/api/measurements/bulk-delete/route.ts": 4, + "app/api/measurements/by-external-ids/route.ts": 4, + "app/api/measurements/restore/__tests__/route.test.ts": 2, + "app/api/measurements/restore/route.ts": 3, + "app/api/measurements/route.ts": 38, + "app/api/measurements/series-batch/__tests__/route.test.ts": 1, + "app/api/measurements/series-batch/route.ts": 1, + "app/api/measurements/series/__tests__/route.test.ts": 5, + "app/api/measurements/series/route.ts": 21, + "app/api/medications/__tests__/route.test.ts": 10, + "app/api/medications/[id]/__tests__/route.test.ts": 8, + "app/api/medications/[id]/api-endpoint/route.ts": 2, + "app/api/medications/[id]/cadence/__tests__/route.test.ts": 1, + "app/api/medications/[id]/cadence/route.ts": 10, + "app/api/medications/[id]/compliance/__tests__/route.test.ts": 7, + "app/api/medications/[id]/compliance/route.ts": 3, + "app/api/medications/[id]/courses/[courseId]/route.ts": 1, + "app/api/medications/[id]/courses/route.ts": 1, + "app/api/medications/[id]/dose-history/__tests__/route.test.ts": 1, + "app/api/medications/[id]/dose-history/route.ts": 14, + "app/api/medications/[id]/glp1/__tests__/route.test.ts": 3, + "app/api/medications/[id]/glp1/route.ts": 9, + "app/api/medications/[id]/intake/__tests__/route.test.ts": 11, + "app/api/medications/[id]/intake/__tests__/source-provenance.test.ts": 1, + "app/api/medications/[id]/intake/[eventId]/__tests__/route.test.ts": 9, + "app/api/medications/[id]/intake/[eventId]/route.ts": 24, + "app/api/medications/[id]/intake/bulk-delete/__tests__/route.test.ts": 3, + "app/api/medications/[id]/intake/bulk-delete/route.ts": 7, + "app/api/medications/[id]/intake/import/__tests__/route.test.ts": 2, + "app/api/medications/[id]/intake/import/route.ts": 6, + "app/api/medications/[id]/intake/purge/__tests__/route.test.ts": 3, + "app/api/medications/[id]/intake/purge/route.ts": 4, + "app/api/medications/[id]/intake/route.ts": 30, + "app/api/medications/[id]/inventory/__tests__/route.test.ts": 2, + "app/api/medications/[id]/inventory/[itemId]/__tests__/route.test.ts": 5, + "app/api/medications/[id]/inventory/[itemId]/route.ts": 9, + "app/api/medications/[id]/inventory/route.ts": 5, + "app/api/medications/[id]/phase-config/__tests__/route.test.ts": 1, + "app/api/medications/[id]/phase-config/route.ts": 5, + "app/api/medications/[id]/route.ts": 59, + "app/api/medications/[id]/schedule-revisions/__tests__/route.test.ts": 1, + "app/api/medications/[id]/schedule-revisions/[revisionId]/route.ts": 9, + "app/api/medications/[id]/schedule-revisions/route.ts": 3, + "app/api/medications/[id]/side-effects/__tests__/route.test.ts": 2, + "app/api/medications/[id]/side-effects/[logId]/route.ts": 2, + "app/api/medications/[id]/side-effects/route.ts": 6, + "app/api/medications/categories/[key]/route.ts": 1, + "app/api/medications/categories/route.ts": 1, + "app/api/medications/compliance/__tests__/route.test.ts": 2, + "app/api/medications/compliance/route.ts": 5, + "app/api/medications/extract/__tests__/route.test.ts": 1, + "app/api/medications/extract/route.ts": 1, + "app/api/medications/intake/__tests__/route.test.ts": 21, + "app/api/medications/intake/bulk/__tests__/dose-arrival-scope.test.ts": 1, + "app/api/medications/intake/bulk/__tests__/route.test.ts": 10, + "app/api/medications/intake/bulk/route.ts": 42, + "app/api/medications/intake/dose-history-import/route.ts": 2, + "app/api/medications/intake/route.ts": 34, + "app/api/medications/layout/__tests__/route.test.ts": 3, + "app/api/medications/layout/route.ts": 3, + "app/api/medications/route.ts": 32, + "app/api/mental-health/assessments/__tests__/route.test.ts": 2, + "app/api/mental-health/assessments/route.ts": 6, + "app/api/mood-entries/__tests__/route.test.ts": 7, + "app/api/mood-entries/[id]/__tests__/route.test.ts": 4, + "app/api/mood-entries/[id]/route.ts": 22, + "app/api/mood-entries/bulk-delete/__tests__/route.test.ts": 1, + "app/api/mood-entries/bulk-delete/route.ts": 4, + "app/api/mood-entries/bulk/__tests__/route.test.ts": 5, + "app/api/mood-entries/bulk/route.ts": 16, + "app/api/mood-entries/restore/__tests__/route.test.ts": 1, + "app/api/mood-entries/restore/route.ts": 3, + "app/api/mood-entries/route.ts": 22, + "app/api/mood/analytics/__tests__/route.test.ts": 2, + "app/api/mood/analytics/route.ts": 1, + "app/api/mood/insights/route.ts": 3, + "app/api/mood/tags/__tests__/custom-crud.test.ts": 1, + "app/api/mood/tags/__tests__/groups-crud.test.ts": 1, + "app/api/mood/tags/__tests__/layout.test.ts": 2, + "app/api/mood/tags/__tests__/route.test.ts": 1, + "app/api/mood/tags/[key]/hidden/route.ts": 1, + "app/api/mood/tags/custom/[key]/route.ts": 1, + "app/api/mood/tags/custom/route.ts": 1, + "app/api/mood/tags/groups/[key]/route.ts": 1, + "app/api/mood/tags/groups/route.ts": 1, + "app/api/mood/tags/layout/__tests__/route.test.ts": 1, + "app/api/mood/tags/layout/route.ts": 3, + "app/api/mood/tags/route.ts": 1, + "app/api/nightscout/connect/__tests__/route.test.ts": 1, + "app/api/nightscout/sync/__tests__/route.test.ts": 1, + "app/api/notifications/apns/test/route.ts": 1, + "app/api/notifications/status/route.ts": 1, + "app/api/nutrients/__tests__/route.test.ts": 2, + "app/api/nutrients/batch/__tests__/route.test.ts": 3, + "app/api/nutrients/batch/route.ts": 4, + "app/api/nutrients/daily/__tests__/route.test.ts": 1, + "app/api/nutrients/route.ts": 1, + "app/api/nutrients/water/__tests__/route.test.ts": 2, + "app/api/nutrients/water/route.ts": 3, + "app/api/onboarding/answers/route.ts": 1, + "app/api/onboarding/complete/__tests__/route.test.ts": 1, + "app/api/onboarding/complete/route.ts": 3, + "app/api/onboarding/disclaimer/route.ts": 1, + "app/api/onboarding/restart/route.ts": 1, + "app/api/onboarding/tour/route.ts": 2, + "app/api/oura/callback/__tests__/route.test.ts": 1, + "app/api/oura/callback/route.ts": 1, + "app/api/oura/connect/route.ts": 1, + "app/api/oura/credentials/route.ts": 1, + "app/api/oura/disconnect/route.ts": 1, + "app/api/oura/status/route.ts": 1, + "app/api/oura/sync/__tests__/route.test.ts": 1, + "app/api/personal-records/__tests__/route.test.ts": 1, + "app/api/personal-records/route.ts": 3, + "app/api/polar/callback/__tests__/route.test.ts": 1, + "app/api/polar/callback/route.ts": 1, + "app/api/polar/connect/route.ts": 1, + "app/api/polar/credentials/route.ts": 1, + "app/api/polar/disconnect/route.ts": 1, + "app/api/polar/status/route.ts": 1, + "app/api/polar/sync/__tests__/route.test.ts": 1, + "app/api/practitioners/[id]/route.ts": 1, + "app/api/practitioners/route.ts": 1, + "app/api/record-settings/integrations/route.ts": 1, + "app/api/settings/__tests__/channel-enabled-routes.test.ts": 1, + "app/api/settings/__tests__/telegram-test-locale.test.ts": 1, + "app/api/settings/account/route.ts": 1, + "app/api/settings/data/route.ts": 2, + "app/api/settings/ntfy/route.ts": 1, + "app/api/settings/reminder-thresholds/__tests__/route.test.ts": 2, + "app/api/settings/reminder-thresholds/route.ts": 2, + "app/api/settings/telegram/test/route.ts": 1, + "app/api/share-links/__tests__/route.test.ts": 2, + "app/api/share-links/route.ts": 7, + "app/api/sleep/night/__tests__/route.test.ts": 1, + "app/api/sleep/night/route.ts": 1, + "app/api/sleep/rhythm/__tests__/route.test.ts": 2, + "app/api/sleep/rhythm/route.ts": 1, + "app/api/strava/callback/route.ts": 1, + "app/api/strava/connect/route.ts": 1, + "app/api/strava/credentials/route.ts": 1, + "app/api/strava/disconnect/route.ts": 1, + "app/api/strava/status/route.ts": 1, + "app/api/strava/sync/__tests__/route.test.ts": 1, + "app/api/sync/changes/route.ts": 5, + "app/api/sync/state/route.ts": 2, + "app/api/telegram/webhook/__tests__/route.test.ts": 3, + "app/api/user/ai-provider/__tests__/legacy-private-host.test.ts": 1, + "app/api/user/ai-provider/__tests__/route.test.ts": 3, + "app/api/user/ai-provider/route.ts": 7, + "app/api/user/avatar/[id]/route.ts": 1, + "app/api/user/avatar/route.ts": 1, + "app/api/user/profile/__tests__/route.test.ts": 3, + "app/api/user/profile/route.ts": 3, + "app/api/user/thresholds/__tests__/route.test.ts": 1, + "app/api/user/thresholds/route.ts": 1, + "app/api/vaccinations/route.ts": 1, + "app/api/version/route.ts": 3, + "app/api/whoop/callback/route.ts": 2, + "app/api/whoop/connect/route.ts": 1, + "app/api/whoop/webhook/[token]/__tests__/route.test.ts": 1, + "app/api/withings/callback/__tests__/route.test.ts": 4, + "app/api/withings/callback/route.ts": 5, + "app/api/withings/connect/__tests__/route.test.ts": 1, + "app/api/withings/connect/route.ts": 3, + "app/api/withings/status/route.ts": 1, + "app/api/withings/webhook/__tests__/route.test.ts": 1, + "app/api/withings/webhook/[token]/__tests__/route.test.ts": 1, + "app/api/withings/webhook/route.ts": 2, + "app/api/workouts/__tests__/batch-create.test.ts": 3, + "app/api/workouts/__tests__/canonical-dedup.test.ts": 3, + "app/api/workouts/__tests__/detail.test.ts": 1, + "app/api/workouts/[id]/route.ts": 5, + "app/api/workouts/batch/route.ts": 9, + "app/api/workouts/route.ts": 2, + "app/auth/claim/page.tsx": 1, + "app/auth/login/page.tsx": 5, + "app/auth/register/page.tsx": 4, + "app/c/[token]/__tests__/page.test.tsx": 2, + "app/c/[token]/d/[id]/__tests__/route.test.ts": 1, + "app/c/[token]/d/[id]/route.ts": 1, + "app/c/[token]/page.tsx": 2, + "app/checkups/page-client.tsx": 4, + "app/claim/[token]/page.tsx": 1, + "app/coach/__tests__/page-launch-params.test.tsx": 1, + "app/coach/__tests__/page-prefetch.test.tsx": 1, + "app/coach/conversations/page.tsx": 4, + "app/coach/page-client.tsx": 8, + "app/coach/plans/page.tsx": 1, + "app/custom-metrics/[id]/page.tsx": 1, + "app/custom-metrics/[id]/values/page.tsx": 1, + "app/cycle/page.tsx": 2, + "app/enroll-mfa/page.tsx": 1, + "app/error.tsx": 1, + "app/global-error.tsx": 2, + "app/illness/[id]/page.tsx": 1, + "app/illness/page.tsx": 1, + "app/insights/__tests__/assessment-spine-order.test.ts": 4, + "app/insights/__tests__/ecg-cross-link-mount.test.tsx": 1, + "app/insights/__tests__/explainer-metric-keys.test.ts": 4, + "app/insights/__tests__/header-add-pattern.test.ts": 1, + "app/insights/__tests__/page-prefetch.test.tsx": 1, + "app/insights/__tests__/page-skeletons.test.ts": 2, + "app/insights/active-energy/page.tsx": 1, + "app/insights/audio-events/page.tsx": 1, + "app/insights/blood-glucose/page.tsx": 2, + "app/insights/blood-pressure/page.tsx": 7, + "app/insights/bmi/page.tsx": 3, + "app/insights/body-temperature/page.tsx": 1, + "app/insights/body-water/page.tsx": 1, + "app/insights/bone-mass/page.tsx": 1, + "app/insights/breathing-disturbances/page.tsx": 1, + "app/insights/cardio-fitness/page.tsx": 1, + "app/insights/cardio-recovery/page.tsx": 1, + "app/insights/daylight/page.tsx": 1, + "app/insights/double-support-time/page.tsx": 1, + "app/insights/ecg/__tests__/page.test.tsx": 1, + "app/insights/ecg/page.tsx": 1, + "app/insights/environmental-audio/page.tsx": 1, + "app/insights/falls/page.tsx": 1, + "app/insights/fat-free-mass/page.tsx": 1, + "app/insights/fat-mass/page.tsx": 1, + "app/insights/flights-climbed/page.tsx": 1, + "app/insights/grip-strength/page.tsx": 1, + "app/insights/headphone-audio/page.tsx": 1, + "app/insights/health-score/__tests__/page.test.tsx": 1, + "app/insights/health-score/page.tsx": 1, + "app/insights/hrv/page.tsx": 1, + "app/insights/layout.tsx": 4, + "app/insights/lean-body-mass/page.tsx": 1, + "app/insights/loading.tsx": 1, + "app/insights/medications/page.tsx": 8, + "app/insights/mood/page-client.tsx": 4, + "app/insights/muscle-mass/page.tsx": 1, + "app/insights/oxygen/page.tsx": 1, + "app/insights/page-client.tsx": 39, + "app/insights/pain/page.tsx": 1, + "app/insights/pulse-wave-velocity/page.tsx": 1, + "app/insights/pulse/__tests__/page.test.tsx": 1, + "app/insights/pulse/page.tsx": 5, + "app/insights/recovery/page.tsx": 2, + "app/insights/respiratory-rate/page.tsx": 1, + "app/insights/resting-pulse/page.tsx": 1, + "app/insights/scores/[metric]/page.tsx": 2, + "app/insights/six-minute-walk/page.tsx": 1, + "app/insights/skin-temperature/page.tsx": 2, + "app/insights/sleep/__tests__/empty-state-href.test.ts": 1, + "app/insights/sleep/page.tsx": 3, + "app/insights/sources/page.tsx": 1, + "app/insights/stair-ascent-speed/page.tsx": 1, + "app/insights/stair-descent-speed/page.tsx": 1, + "app/insights/step-length/page.tsx": 1, + "app/insights/steps/page.tsx": 2, + "app/insights/values/[type]/page.tsx": 2, + "app/insights/vascular-age/page.tsx": 1, + "app/insights/visceral-fat/page.tsx": 1, + "app/insights/waist-to-height/page.tsx": 1, + "app/insights/waist/page.tsx": 1, + "app/insights/walking-asymmetry/page.tsx": 1, + "app/insights/walking-distance/page.tsx": 1, + "app/insights/walking-heart-rate/page.tsx": 1, + "app/insights/walking-speed/page.tsx": 1, + "app/insights/walking-steadiness/page.tsx": 1, + "app/insights/weight/page.tsx": 8, + "app/insights/workouts/page-client.tsx": 2, + "app/insights/wrist-temperature/page.tsx": 1, + "app/invite/[token]/__tests__/page.test.tsx": 1, + "app/invite/[token]/page.tsx": 1, + "app/labs/[biomarkerId]/page.tsx": 1, + "app/labs/[biomarkerId]/values/page.tsx": 1, + "app/labs/page.tsx": 3, + "app/loading.tsx": 2, + "app/mcp/__tests__/route.test.ts": 5, + "app/measurements/page.tsx": 5, + "app/medications/__tests__/category-filter.test.tsx": 2, + "app/medications/__tests__/detail-page-history-surface.test.ts": 1, + "app/medications/__tests__/list-view-toggle.test.tsx": 5, + "app/medications/__tests__/page-prefetch.test.tsx": 1, + "app/medications/__tests__/take-all-due-affordance.test.tsx": 1, + "app/medications/[id]/history/page.tsx": 1, + "app/medications/[id]/page.tsx": 2, + "app/medications/new/page.tsx": 1, + "app/medications/page-client.tsx": 50, + "app/mental-wellbeing/page.tsx": 1, + "app/mood/page-client.tsx": 2, + "app/not-found.tsx": 2, + "app/onboarding/__tests__/page.test.tsx": 1, + "app/onboarding/[step]/page.tsx": 1, + "app/onboarding/handover/page.tsx": 1, + "app/onboarding/page.tsx": 2, + "app/page-client.tsx": 82, + "app/privacy/__tests__/page.test.tsx": 1, + "app/privacy/page.tsx": 1, + "app/settings/[section]/page.tsx": 1, + "app/settings/layout/[module]/page.tsx": 1, + "app/timeline/page.tsx": 1, + "app/vaccinations/page.tsx": 1, + "components/__tests__/delegated-write-affordances.test.tsx": 3, + "components/__tests__/design-consistency-guards.test.tsx": 1, + "components/__tests__/motion-reduce-spin-coverage.test.ts": 3, + "components/admin/__tests__/ai-quality-section.test.tsx": 2, + "components/admin/__tests__/ai-server-key-legacy-notice.test.tsx": 1, + "components/admin/__tests__/api-token-no-collapse.test.tsx": 3, + "components/admin/__tests__/api-token-overview-responsive.test.tsx": 1, + "components/admin/__tests__/api-token-truncate.test.tsx": 1, + "components/admin/__tests__/app-log-preview-summary-position.test.tsx": 1, + "components/admin/__tests__/backups-section-empty.test.tsx": 1, + "components/admin/__tests__/coach-feedback-section.test.tsx": 2, + "components/admin/__tests__/invite-status.test.tsx": 1, + "components/admin/__tests__/invite-tokens-section.test.tsx": 1, + "components/admin/__tests__/login-overview-auth-filter.test.tsx": 1, + "components/admin/__tests__/login-overview-csv.test.ts": 2, + "components/admin/__tests__/login-overview-no-collapse.test.tsx": 1, + "components/admin/__tests__/login-overview-pagination-position.test.tsx": 1, + "components/admin/__tests__/login-overview-provider.test.tsx": 1, + "components/admin/__tests__/provider-health-spend-today.test.tsx": 1, + "components/admin/__tests__/sections.test.tsx": 3, + "components/admin/__tests__/status-card-grid.test.tsx": 1, + "components/admin/__tests__/user-management-empty.test.tsx": 1, + "components/admin/_shared.tsx": 11, + "components/admin/admin-shell.tsx": 10, + "components/admin/ai-quality-section.tsx": 1, + "components/admin/ai-server-key-section.tsx": 3, + "components/admin/ai-usage-section.tsx": 1, + "components/admin/api-token-overview-section.tsx": 5, + "components/admin/app-log-preview-section.tsx": 1, + "components/admin/assistant-section.tsx": 1, + "components/admin/backups-section.tsx": 4, + "components/admin/coach-feedback-section.tsx": 1, + "components/admin/danger-zone-section.tsx": 1, + "components/admin/general-settings-section.tsx": 1, + "components/admin/invite-tokens-section.tsx": 1, + "components/admin/login-overview-section.tsx": 6, + "components/admin/module-availability-section.tsx": 1, + "components/admin/provider-health-section.tsx": 4, + "components/admin/recent-audit-preview.tsx": 1, + "components/admin/reminders-section.tsx": 1, + "components/admin/section-slugs.ts": 5, + "components/admin/system-status-section.tsx": 2, + "components/admin/user-management-section.tsx": 1, + "components/admin/version-tile-section.tsx": 1, + "components/auth/__tests__/claim-profile-sso.test.tsx": 1, + "components/auth/claim-profile.tsx": 3, + "components/auth/mfa-login-step.tsx": 1, + "components/charts/__tests__/bd-tile.test.tsx": 1, + "components/charts/__tests__/chart-error-state.test.tsx": 1, + "components/charts/__tests__/chart-overlay-controls.test.tsx": 3, + "components/charts/__tests__/chart-skeleton.test.tsx": 2, + "components/charts/__tests__/chart-tooltip.test.tsx": 1, + "components/charts/__tests__/health-chart-annotations.test.ts": 1, + "components/charts/__tests__/health-chart-comparison.test.tsx": 2, + "components/charts/__tests__/health-chart-day-links.test.tsx": 1, + "components/charts/__tests__/health-chart-mini.test.tsx": 2, + "components/charts/__tests__/health-chart-no-data-in-range.test.tsx": 1, + "components/charts/__tests__/health-chart-overlay-defaults.test.tsx": 1, + "components/charts/__tests__/health-chart-polish.test.tsx": 2, + "components/charts/__tests__/health-chart-preloaded-coverage.test.tsx": 1, + "components/charts/__tests__/health-chart-range.test.tsx": 3, + "components/charts/__tests__/health-chart-target-link-removed.test.ts": 1, + "components/charts/__tests__/health-chart-vertical-markers.test.ts": 1, + "components/charts/__tests__/medication-chart-polish.test.tsx": 1, + "components/charts/__tests__/medication-compliance-chart-empty-state-gate.test.tsx": 1, + "components/charts/__tests__/medication-compliance-chart.test.tsx": 2, + "components/charts/__tests__/medication-overlay-toggles.test.tsx": 1, + "components/charts/__tests__/mood-chart-aggregation.test.tsx": 2, + "components/charts/__tests__/mood-chart-empty-state-gate.test.tsx": 1, + "components/charts/__tests__/mood-chart-polish.test.tsx": 1, + "components/charts/__tests__/trend-card-7d-trend.test.tsx": 1, + "components/charts/__tests__/trend-card-baseline-alignment.test.tsx": 1, + "components/charts/__tests__/trend-card-comparison.test.tsx": 1, + "components/charts/__tests__/trend-card-stale-days.test.tsx": 1, + "components/charts/__tests__/trend-card-tile-height.test.tsx": 1, + "components/charts/__tests__/trend-card.test.tsx": 4, + "components/charts/chart-data-table.tsx": 1, + "components/charts/chart-overlay-controls.tsx": 4, + "components/charts/chart-range-tabs.tsx": 1, + "components/charts/chart-skeleton.tsx": 4, + "components/charts/chart-tooltip.tsx": 1, + "components/charts/compliance-heatmap.tsx": 6, + "components/charts/health-chart-dynamic.tsx": 3, + "components/charts/health-chart.tsx": 82, + "components/charts/medication-compliance-chart.tsx": 21, + "components/charts/mini-window.ts": 1, + "components/charts/mood-chart.tsx": 33, + "components/charts/mood-heatmap.tsx": 5, + "components/charts/nutrient-daily-bar-chart-dynamic.tsx": 1, + "components/charts/nutrient-daily-bar-chart.tsx": 2, + "components/charts/scatter-correlation-chart.tsx": 2, + "components/charts/trend-card-skeleton.tsx": 1, + "components/charts/trend-card.tsx": 11, + "components/clinician/__tests__/clinician-view.test.tsx": 1, + "components/clinician/share-unlock-gate.tsx": 1, + "components/common/medical-disclaimer.tsx": 1, + "components/custom-metrics/custom-metric-chart.tsx": 2, + "components/custom-metrics/custom-metric-detail.tsx": 1, + "components/custom-metrics/custom-metric-entry-form.tsx": 1, + "components/custom-metrics/custom-metric-form.tsx": 1, + "components/custom-metrics/custom-metric-history-list.tsx": 2, + "components/custom-metrics/custom-metric-list.tsx": 2, + "components/custom-metrics/custom-metric-values-list.tsx": 1, + "components/cycle/__tests__/bbt-chart.test.tsx": 1, + "components/cycle/__tests__/cycle-insight-summary-card.test.tsx": 1, + "components/cycle/__tests__/cycle-ring-tile.test.tsx": 1, + "components/cycle/bbt-chart.tsx": 3, + "components/cycle/cycle-calendar.tsx": 1, + "components/cycle/cycle-history-chart.tsx": 1, + "components/cycle/cycle-insight-summary-card.tsx": 1, + "components/cycle/cycle-phase-crosstab.tsx": 1, + "components/cycle/cycle-ring-tile.tsx": 1, + "components/cycle/cycle-ring.tsx": 2, + "components/cycle/cycle-settings.tsx": 1, + "components/cycle/cycle-symptom-patterns.tsx": 1, + "components/cycle/cycle-view.tsx": 2, + "components/cycle/field-info.tsx": 1, + "components/cycle/log-day-sheet.tsx": 5, + "components/cycle/phase-education-card.tsx": 1, + "components/cycle/phase-tokens.ts": 1, + "components/cycle/predictions-panel.tsx": 1, + "components/cycle/symptom-catalog.ts": 2, + "components/cycle/types.ts": 1, + "components/cycle/use-cycle.ts": 3, + "components/daily/__tests__/priority-card.test.tsx": 1, + "components/daily/__tests__/today-hero.test.tsx": 2, + "components/daily/priority-card.tsx": 1, + "components/daily/today-hero.tsx": 1, + "components/dashboard/__tests__/environment-chip.test.tsx": 1, + "components/dashboard/__tests__/medication-intake-quick-add.test.tsx": 3, + "components/dashboard/__tests__/recent-workouts-tile.test.tsx": 2, + "components/dashboard/chart-reveal.tsx": 1, + "components/dashboard/dashboard-gates.ts": 9, + "components/dashboard/medication-intake-quick-add.tsx": 11, + "components/dashboard/quick-entry-sheets.tsx": 6, + "components/dashboard/range-display.tsx": 1, + "components/dashboard/recent-workouts-tile.tsx": 3, + "components/data-list/__tests__/selection.test.ts": 1, + "components/data-list/delete-button.tsx": 2, + "components/data-list/index.ts": 1, + "components/data-list/selection-action-bar.tsx": 4, + "components/documents/__tests__/document-ai-section.test.tsx": 4, + "components/documents/__tests__/document-card.test.tsx": 2, + "components/documents/__tests__/document-coach-launch-wiring.test.ts": 1, + "components/documents/__tests__/vault-utils.test.ts": 1, + "components/documents/document-ai-panels.tsx": 1, + "components/documents/document-ai-section.tsx": 3, + "components/documents/document-ai-transport.ts": 2, + "components/documents/document-bulk-bar.tsx": 1, + "components/documents/document-card.tsx": 2, + "components/documents/document-detail-sheet.tsx": 6, + "components/documents/document-facts-review.tsx": 2, + "components/documents/document-summary-block.tsx": 2, + "components/documents/documents-view.tsx": 3, + "components/documents/use-content-index.ts": 1, + "components/documents/use-document-assist.ts": 1, + "components/documents/use-document-facts.ts": 1, + "components/documents/vault-utils.ts": 1, + "components/encounters/__tests__/encounter-form.test.tsx": 1, + "components/error-details.tsx": 1, + "components/gamification/__tests__/achievement-unlock-notifier.test.tsx": 1, + "components/gamification/__tests__/recent-achievements-card.test.tsx": 3, + "components/gamification/achievement-unlock-notifier.tsx": 3, + "components/gamification/recent-achievements-card.tsx": 2, + "components/i18n/__tests__/maintainership-banner.test.tsx": 2, + "components/i18n/maintainership-banner.tsx": 2, + "components/illness/episode-menu.tsx": 1, + "components/illness/illness-correlation-card.tsx": 1, + "components/illness/illness-day-timeline.tsx": 2, + "components/illness/illness-episode-detail.tsx": 2, + "components/illness/illness-insights-card.tsx": 2, + "components/illness/illness-settings.tsx": 2, + "components/illness/illness-view.tsx": 5, + "components/illness/log-day-sheet.tsx": 1, + "components/illness/new-episode-sheet.tsx": 2, + "components/illness/symptom-catalog.ts": 1, + "components/illness/types.ts": 1, + "components/illness/use-illness.ts": 2, + "components/insights/__tests__/coincident-deviation-card.test.tsx": 1, + "components/insights/__tests__/correlation-card-skeleton.test.ts": 1, + "components/insights/__tests__/correlation-card.test.tsx": 1, + "components/insights/__tests__/daily-briefing.test.tsx": 5, + "components/insights/__tests__/device-score-surfaces.test.tsx": 3, + "components/insights/__tests__/ecg-waveform.test.tsx": 1, + "components/insights/__tests__/health-score-card-geometry.test.tsx": 2, + "components/insights/__tests__/health-score-card.test.tsx": 3, + "components/insights/__tests__/health-score-coverage-axis-guard.test.tsx": 1, + "components/insights/__tests__/health-score-pillar-detail-bp-basis.test.tsx": 1, + "components/insights/__tests__/healthkit-metric-page-status.test.tsx": 2, + "components/insights/__tests__/hero-strip.test.tsx": 10, + "components/insights/__tests__/insight-status-card.test.tsx": 4, + "components/insights/__tests__/insights-tab-strip-ecg.test.tsx": 1, + "components/insights/__tests__/insights-tab-strip.test.tsx": 5, + "components/insights/__tests__/layout-coach-fab.test.tsx": 1, + "components/insights/__tests__/metric-stat-strip.test.tsx": 3, + "components/insights/__tests__/metric-target-summary-unit-preference.test.tsx": 1, + "components/insights/__tests__/metric-target-summary.test.tsx": 8, + "components/insights/__tests__/personal-record-badge.test.tsx": 1, + "components/insights/__tests__/rhythm-events-card.test.tsx": 1, + "components/insights/__tests__/sleep-stage-stacked-bar.test.tsx": 5, + "components/insights/__tests__/sub-page-shell.test.tsx": 4, + "components/insights/__tests__/trend-annotation.test.tsx": 1, + "components/insights/__tests__/trend-descriptor-caption.test.tsx": 1, + "components/insights/__tests__/trends-row.test.tsx": 8, + "components/insights/__tests__/use-insights-advisor-force.test.ts": 1, + "components/insights/__tests__/use-insights-advisor-timeout.test.ts": 2, + "components/insights/__tests__/workout-list.test.tsx": 1, + "components/insights/ask-coach-action.tsx": 2, + "components/insights/breathing-screening-card.tsx": 1, + "components/insights/coach-launch-button.tsx": 5, + "components/insights/coach-metric-scope.ts": 4, + "components/insights/coach-panel/__tests__/assumption-line.test.tsx": 1, + "components/insights/coach-panel/__tests__/chat-bubble-memo.test.tsx": 1, + "components/insights/coach-panel/__tests__/chat-bubble-unverified-notice.test.tsx": 1, + "components/insights/coach-panel/__tests__/coach-conversation-deeplink.test.tsx": 3, + "components/insights/coach-panel/__tests__/coach-doc-scope-banner.test.tsx": 1, + "components/insights/coach-panel/__tests__/coach-drawer-maximize.test.ts": 1, + "components/insights/coach-panel/__tests__/coach-drawer-mobile-trays.test.tsx": 6, + "components/insights/coach-panel/__tests__/coach-drawer-prefill.test.ts": 1, + "components/insights/coach-panel/__tests__/coach-input.test.tsx": 6, + "components/insights/coach-panel/__tests__/coach-launch-scope.test.ts": 2, + "components/insights/coach-panel/__tests__/coach-reasoning-field.test.tsx": 1, + "components/insights/coach-panel/__tests__/coach-results-states.test.tsx": 1, + "components/insights/coach-panel/__tests__/coach-results.test.tsx": 1, + "components/insights/coach-panel/__tests__/coach-send-target.test.ts": 1, + "components/insights/coach-panel/__tests__/guided-dialog.test.tsx": 1, + "components/insights/coach-panel/__tests__/guided-questions-machine.test.ts": 1, + "components/insights/coach-panel/__tests__/history-rail.test.tsx": 4, + "components/insights/coach-panel/__tests__/live-turn-keys.test.ts": 1, + "components/insights/coach-panel/__tests__/memory-note.test.tsx": 1, + "components/insights/coach-panel/__tests__/message-thread.test.tsx": 17, + "components/insights/coach-panel/__tests__/mobile-rail-tray.test.tsx": 2, + "components/insights/coach-panel/__tests__/reply-decisions.test.ts": 1, + "components/insights/coach-panel/__tests__/result-chart-compare.test.tsx": 1, + "components/insights/coach-panel/__tests__/result-table.test.tsx": 1, + "components/insights/coach-panel/__tests__/sources-rail.test.tsx": 2, + "components/insights/coach-panel/__tests__/suggested-replies.test.tsx": 1, + "components/insights/coach-panel/__tests__/use-coach-dialog.test.ts": 1, + "components/insights/coach-panel/__tests__/use-coach.test.ts": 1, + "components/insights/coach-panel/assumption-line.tsx": 1, + "components/insights/coach-panel/attachment-picker.tsx": 1, + "components/insights/coach-panel/attachment-pills.tsx": 2, + "components/insights/coach-panel/chat-bubble.tsx": 30, + "components/insights/coach-panel/coach-conversation.tsx": 40, + "components/insights/coach-panel/coach-drawer-body.tsx": 6, + "components/insights/coach-panel/coach-drawer.tsx": 14, + "components/insights/coach-panel/coach-hero.tsx": 2, + "components/insights/coach-panel/coach-input.tsx": 12, + "components/insights/coach-panel/coach-model-picker.tsx": 1, + "components/insights/coach-panel/coach-reasoning-field.tsx": 1, + "components/insights/coach-panel/coach-results.tsx": 2, + "components/insights/coach-panel/composer-focus.ts": 1, + "components/insights/coach-panel/copy-table-button.tsx": 1, + "components/insights/coach-panel/follow-up-chips.tsx": 2, + "components/insights/coach-panel/guided-dialog-bubbles.tsx": 1, + "components/insights/coach-panel/guided-questions-card.tsx": 1, + "components/insights/coach-panel/guided-questions-machine.ts": 2, + "components/insights/coach-panel/memory-note.tsx": 1, + "components/insights/coach-panel/message-thread.tsx": 31, + "components/insights/coach-panel/method-line.tsx": 1, + "components/insights/coach-panel/mobile-rail-tray.tsx": 2, + "components/insights/coach-panel/read-aloud.tsx": 8, + "components/insights/coach-panel/reminder-suggestion-card.tsx": 1, + "components/insights/coach-panel/result-chart.tsx": 1, + "components/insights/coach-panel/result-table.tsx": 1, + "components/insights/coach-panel/self-context-adopt-offer.tsx": 2, + "components/insights/coach-panel/sources-rail.tsx": 4, + "components/insights/coach-panel/streamed-prose.tsx": 2, + "components/insights/coach-panel/suggested-action-card.tsx": 1, + "components/insights/coach-panel/suggested-replies.tsx": 1, + "components/insights/coach-panel/turn-activity.tsx": 1, + "components/insights/coach-panel/use-coach-facts.ts": 1, + "components/insights/coach-panel/use-coach.ts": 48, + "components/insights/coach-panel/use-resettable-value.ts": 3, + "components/insights/coincident-deviation-card.tsx": 1, + "components/insights/correlation-card.tsx": 4, + "components/insights/daily-briefing.tsx": 15, + "components/insights/derived/__tests__/composite-score-anatomy-coach.test.tsx": 1, + "components/insights/derived/__tests__/health-score-provenance-claim.test.ts": 1, + "components/insights/derived/__tests__/score-anatomy-view.test.tsx": 1, + "components/insights/derived/__tests__/score-history-wiring.test.tsx": 1, + "components/insights/derived/__tests__/score-trend-chart.test.tsx": 1, + "components/insights/derived/band-tokens.ts": 1, + "components/insights/derived/coach-read-strip.tsx": 2, + "components/insights/derived/composite-score-anatomy.tsx": 6, + "components/insights/derived/coverage-meter.tsx": 1, + "components/insights/derived/index.ts": 1, + "components/insights/derived/provenance-explainer.tsx": 2, + "components/insights/derived/ring-hues.ts": 2, + "components/insights/derived/score-anatomy-view.tsx": 3, + "components/insights/derived/score-history.tsx": 1, + "components/insights/derived/score-ring.tsx": 4, + "components/insights/derived/score-trend-chart.tsx": 1, + "components/insights/derived/sparkline-delta-tile.tsx": 2, + "components/insights/derived/standards.ts": 1, + "components/insights/derived/use-dashboard-derived.ts": 2, + "components/insights/derived/use-derived-metric.ts": 3, + "components/insights/derived/vitals-dashboard.tsx": 11, + "components/insights/derived/wellness-scores.tsx": 4, + "components/insights/device-score-tile-skeleton.tsx": 1, + "components/insights/device-score-tile.tsx": 3, + "components/insights/ecg-detail.tsx": 1, + "components/insights/ecg-section.tsx": 3, + "components/insights/ecg-waveform.tsx": 1, + "components/insights/glp1-plateau-note.tsx": 1, + "components/insights/glp1-substance-curve-summary.tsx": 1, + "components/insights/glucose/__tests__/glucose-clinical-panel.test.tsx": 1, + "components/insights/glucose/glucose-advanced-disclosure.tsx": 1, + "components/insights/glucose/glucose-clinical-panel.tsx": 2, + "components/insights/glucose/glucose-tir-bar.tsx": 1, + "components/insights/health-score-card.tsx": 8, + "components/insights/health-status-card.tsx": 1, + "components/insights/healthkit-metric-page.tsx": 20, + "components/insights/hero-strip.tsx": 3, + "components/insights/insight-status-card.tsx": 12, + "components/insights/insights-edit-mode.tsx": 5, + "components/insights/insights-layout-shell.tsx": 11, + "components/insights/insights-tab-strip.tsx": 35, + "components/insights/intraday-pulse-chart.tsx": 1, + "components/insights/labs-changes-card.tsx": 1, + "components/insights/layout-coach-fab.tsx": 12, + "components/insights/layout-coach-mount.tsx": 5, + "components/insights/measurement-diversity-nudge.tsx": 3, + "components/insights/medication-efficacy-summary.tsx": 1, + "components/insights/metric-correlation-card.tsx": 1, + "components/insights/metric-empty-state.tsx": 1, + "components/insights/metric-stat-strip.tsx": 8, + "components/insights/metric-status-card.tsx": 2, + "components/insights/metric-target-summary.tsx": 3, + "components/insights/mood/__tests__/mood-discovered-relations.test.tsx": 3, + "components/insights/mood/__tests__/mood-insights-sections-in-target.test.tsx": 1, + "components/insights/mood/__tests__/mood-insights-sections-reveal.test.tsx": 1, + "components/insights/mood/__tests__/mood-notes-timeline-removed.test.ts": 4, + "components/insights/mood/__tests__/mood-tag-bars-muted.test.tsx": 1, + "components/insights/mood/mood-better-days.tsx": 2, + "components/insights/mood/mood-correlation-cards.tsx": 3, + "components/insights/mood/mood-dimension-trends.tsx": 2, + "components/insights/mood/mood-discovered-relations.tsx": 8, + "components/insights/mood/mood-distribution-chart.tsx": 7, + "components/insights/mood/mood-explainer-icon.tsx": 2, + "components/insights/mood/mood-factor-metric-crosstab.tsx": 1, + "components/insights/mood/mood-in-target-tile.tsx": 2, + "components/insights/mood/mood-insights-breakdowns.tsx": 3, + "components/insights/mood/mood-insights-sections.tsx": 6, + "components/insights/mood/mood-insights-shared.tsx": 3, + "components/insights/mood/mood-narrative-feed.tsx": 2, + "components/insights/mood/mood-patterns.tsx": 1, + "components/insights/mood/mood-stability-tile.tsx": 1, + "components/insights/mood/mood-structured-tag-breakdown.tsx": 2, + "components/insights/mood/mood-tag-breakdown.tsx": 2, + "components/insights/mood/mood-tag-influence.tsx": 2, + "components/insights/mood/mood-tag-metric-crosstab.tsx": 2, + "components/insights/mood/mood-time-of-day-chart.tsx": 2, + "components/insights/mood/mood-weekday-chart.tsx": 4, + "components/insights/mood/mood-what-stands-out.tsx": 1, + "components/insights/nutrients/caffeine-card.tsx": 1, + "components/insights/nutrients/hydration-card.tsx": 1, + "components/insights/nutrients/micronutrients-card.tsx": 3, + "components/insights/nutrients/types.ts": 4, + "components/insights/period-narrative-card.tsx": 1, + "components/insights/personal-record-badge.tsx": 2, + "components/insights/prose-blocks.tsx": 1, + "components/insights/recovery/recovery-metric-block.tsx": 2, + "components/insights/recovery/recovery-section.tsx": 3, + "components/insights/recovery/resilience-tile.tsx": 2, + "components/insights/rest-mode-banner.tsx": 1, + "components/insights/rhythm-events-card.tsx": 1, + "components/insights/sleep-duration-chart.tsx": 3, + "components/insights/sleep-overview.tsx": 5, + "components/insights/sleep-source-discrepancy-marker.tsx": 1, + "components/insights/sleep-stage-stacked-bar.tsx": 6, + "components/insights/sleep/__tests__/chronotype-section.test.tsx": 2, + "components/insights/sleep/__tests__/sleep-rhythm-cards.test.tsx": 1, + "components/insights/sleep/__tests__/sleep-rhythm-section.test.tsx": 2, + "components/insights/sleep/average-sleep-card.tsx": 1, + "components/insights/sleep/average-sleep-section.tsx": 1, + "components/insights/sleep/chronotype-card.tsx": 2, + "components/insights/sleep/chronotype-section.tsx": 2, + "components/insights/sleep/sleep-debt-card.tsx": 1, + "components/insights/sleep/sleep-quality-section.tsx": 1, + "components/insights/sleep/sleep-rhythm-section.tsx": 2, + "components/insights/sleep/use-sleep-rhythm.ts": 1, + "components/insights/slug-insight-status-card.tsx": 1, + "components/insights/sub-page-shell.tsx": 22, + "components/insights/tile-header.tsx": 1, + "components/insights/trend-annotation.tsx": 4, + "components/insights/trends-row.tsx": 10, + "components/insights/use-insights-advisor.ts": 24, + "components/insights/workout-detail/day-context.tsx": 1, + "components/insights/workout-detail/header.tsx": 1, + "components/insights/workout-list.tsx": 2, + "components/labs/__tests__/lab-form-result-type.test.tsx": 1, + "components/labs/__tests__/lab-list-loading.test.tsx": 1, + "components/labs/biomarker-aliases.tsx": 1, + "components/labs/biomarker-form.tsx": 1, + "components/labs/biomarker-manager.tsx": 2, + "components/labs/biomarker-merge-sheet.tsx": 1, + "components/labs/lab-biomarker-chart.tsx": 3, + "components/labs/lab-biomarker-detail.tsx": 13, + "components/labs/lab-form.tsx": 6, + "components/labs/lab-history-list.tsx": 4, + "components/labs/lab-list.tsx": 6, + "components/labs/lab-reference-range-bar.tsx": 1, + "components/labs/lab-trend-card.tsx": 1, + "components/labs/lab-trend-sparkline.tsx": 1, + "components/labs/lab-values-list.tsx": 1, + "components/labs/labs-settings.tsx": 2, + "components/labs/marker-match-field.tsx": 1, + "components/labs/ocr-review-dialog.tsx": 2, + "components/labs/ocr-review-types.ts": 2, + "components/labs/ocr-row-editor.tsx": 2, + "components/labs/reference-range-badge.tsx": 2, + "components/labs/types.ts": 8, + "components/labs/use-ocr-extract.ts": 2, + "components/layout/__tests__/auth-shell-coach-hoist.test.ts": 3, + "components/layout/__tests__/auth-shell-public-paths.test.tsx": 1, + "components/layout/__tests__/auth-shell-shared-record-doors.test.tsx": 1, + "components/layout/__tests__/bottom-nav.test.tsx": 2, + "components/layout/__tests__/capture-picker-discard.test.tsx": 1, + "components/layout/__tests__/capture-picker.test.tsx": 1, + "components/layout/__tests__/nav-model.test.ts": 7, + "components/layout/__tests__/offline-banner.test.tsx": 3, + "components/layout/__tests__/shared-record-chrome.test.tsx": 1, + "components/layout/__tests__/sidebar-nav.test.tsx": 3, + "components/layout/account-switcher-menu.tsx": 1, + "components/layout/auth-shell.tsx": 15, + "components/layout/bottom-nav.tsx": 8, + "components/layout/capture-picker.tsx": 5, + "components/layout/nav-model.ts": 17, + "components/layout/offline-banner.tsx": 3, + "components/layout/record-session-fence-bridge.tsx": 1, + "components/layout/shared-record-banner.tsx": 1, + "components/layout/shared-record-grant-loss-bridge.tsx": 1, + "components/layout/shared-record-unavailable.tsx": 1, + "components/layout/sidebar-nav.tsx": 5, + "components/layout/top-bar.tsx": 4, + "components/measurement-reminders/__tests__/vorsorge-section.test.tsx": 7, + "components/measurement-reminders/vorsorge-dashboard-card.tsx": 2, + "components/measurement-reminders/vorsorge-section.tsx": 26, + "components/measurement-reminders/vorsorge-settings.tsx": 2, + "components/measurements/__tests__/measurement-form-unit-label.test.tsx": 1, + "components/measurements/__tests__/measurement-list-apple-health-badge.test.tsx": 4, + "components/measurements/__tests__/measurement-list-bp-badge.test.tsx": 3, + "components/measurements/__tests__/measurement-list-empty.test.tsx": 1, + "components/measurements/__tests__/measurement-list-filter-select.test.tsx": 3, + "components/measurements/__tests__/measurement-list-locked-type.test.tsx": 2, + "components/measurements/__tests__/measurement-list-step-grouping.test.tsx": 1, + "components/measurements/measurement-form.tsx": 11, + "components/measurements/measurement-list.tsx": 39, + "components/medications/__tests__/add-inventory-dialog.test.tsx": 1, + "components/medications/__tests__/card-parts.test.tsx": 13, + "components/medications/__tests__/detail-page-cascade.test.tsx": 1, + "components/medications/__tests__/dose-history-add-dialog.test.ts": 1, + "components/medications/__tests__/dose-history-ledger-compute.test.ts": 2, + "components/medications/__tests__/dose-strength-curve.test.tsx": 1, + "components/medications/__tests__/injection-site-picker.test.tsx": 1, + "components/medications/__tests__/intake-edit-dialog-seed.test.tsx": 1, + "components/medications/__tests__/intake-history-list-v2-row-actions.test.tsx": 1, + "components/medications/__tests__/intake-history-list-v2.test.tsx": 4, + "components/medications/__tests__/intake-history-preview-bulk-toolbar.test.tsx": 2, + "components/medications/__tests__/inventory-section-actions.test.tsx": 1, + "components/medications/__tests__/inventory-section-summary.test.tsx": 2, + "components/medications/__tests__/log-injection-site-dialog.test.tsx": 1, + "components/medications/__tests__/medication-card-glp1.test.tsx": 14, + "components/medications/__tests__/medication-card-header.test.tsx": 1, + "components/medications/__tests__/medication-card-next-due.test.tsx": 4, + "components/medications/__tests__/medication-card-symmetry.test.tsx": 4, + "components/medications/__tests__/medication-detail-summary-course.test.tsx": 1, + "components/medications/__tests__/medication-form-v15.test.tsx": 2, + "components/medications/__tests__/medication-order-editor.test.tsx": 1, + "components/medications/__tests__/medication-table.test.tsx": 3, + "components/medications/__tests__/side-effects-section.test.tsx": 2, + "components/medications/__tests__/take-all-due.test.tsx": 2, + "components/medications/__tests__/titration-timeline.test.ts": 1, + "components/medications/__tests__/use-medication-intake.test.ts": 3, + "components/medications/card-parts/__tests__/displayed-slot-instant.test.ts": 1, + "components/medications/card-parts/displayed-slot-instant.ts": 2, + "components/medications/card-parts/medication-card-body.tsx": 10, + "components/medications/card-parts/medication-compliance-bars.tsx": 6, + "components/medications/card-parts/medication-cycle-status.tsx": 1, + "components/medications/card-parts/medication-intake-actions.tsx": 1, + "components/medications/card-parts/medication-next-last-slot.tsx": 1, + "components/medications/card-parts/medication-state-badges.tsx": 5, + "components/medications/card-parts/medication-status-pill.tsx": 5, + "components/medications/course-fields.ts": 1, + "components/medications/detail/efficacy/efficacy-chart.tsx": 2, + "components/medications/detail/efficacy/efficacy-tab.tsx": 4, + "components/medications/detail/medication-detail-tabs.tsx": 28, + "components/medications/dose-history-add-dialog.tsx": 2, + "components/medications/dose-history-ledger-compute.ts": 5, + "components/medications/dose-history-ledger.tsx": 7, + "components/medications/dose-strength-curve.tsx": 2, + "components/medications/drug-level-chart.tsx": 2, + "components/medications/glp1-medication-card.tsx": 36, + "components/medications/injection-site-picker.tsx": 2, + "components/medications/intake-edit-dialog.tsx": 2, + "components/medications/intake-history-editable.tsx": 2, + "components/medications/intake-history-list-v2.tsx": 13, + "components/medications/intake-import-dialog.tsx": 1, + "components/medications/log-injection-site-dialog.tsx": 2, + "components/medications/log-intake-dialog.tsx": 4, + "components/medications/medication-card-header.tsx": 5, + "components/medications/medication-card-menu.tsx": 3, + "components/medications/medication-card.tsx": 37, + "components/medications/medication-categories-sheet.tsx": 1, + "components/medications/medication-category-add-row.tsx": 1, + "components/medications/medication-detail-section.tsx": 2, + "components/medications/medication-detail-summary.tsx": 5, + "components/medications/medication-order-editor.tsx": 1, + "components/medications/medication-table.tsx": 15, + "components/medications/medication-view-toggle.tsx": 1, + "components/medications/scheduling/__tests__/cadence-picker.test.tsx": 1, + "components/medications/scheduling/__tests__/course-window-row.test.tsx": 1, + "components/medications/scheduling/__tests__/dose-window.test.ts": 1, + "components/medications/scheduling/__tests__/schedule-history-timeline.test.tsx": 1, + "components/medications/scheduling/__tests__/times-of-day-chips.test.tsx": 1, + "components/medications/scheduling/cadence-picker.tsx": 1, + "components/medications/scheduling/course-window-row.tsx": 1, + "components/medications/scheduling/dose-window-editor.tsx": 2, + "components/medications/scheduling/dose-window.ts": 1, + "components/medications/scheduling/legacy-bridge.ts": 2, + "components/medications/scheduling/natural-language-extractor.tsx": 1, + "components/medications/scheduling/schedule-history-timeline.tsx": 2, + "components/medications/scheduling/schedule-times-editor.tsx": 2, + "components/medications/scheduling/times-of-day-chips.tsx": 1, + "components/medications/scheduling/types.ts": 1, + "components/medications/sections/__tests__/notifications-section.test.tsx": 1, + "components/medications/sections/api-tokens-row.tsx": 2, + "components/medications/sections/auto-mark-taken-section.tsx": 1, + "components/medications/sections/composition-section.tsx": 1, + "components/medications/sections/courses-section.tsx": 1, + "components/medications/sections/data-portability-row.tsx": 1, + "components/medications/sections/destructive-zone-section.tsx": 5, + "components/medications/sections/intake-history-preview.tsx": 3, + "components/medications/sections/intake-tracking-section.tsx": 1, + "components/medications/sections/inventory-dialogs.tsx": 3, + "components/medications/sections/inventory-section.tsx": 8, + "components/medications/sections/inventory-shared.ts": 1, + "components/medications/sections/notifications-section.tsx": 4, + "components/medications/sections/phase-config-sheet.tsx": 1, + "components/medications/sections/settings-section.tsx": 8, + "components/medications/sections/use-composition.ts": 1, + "components/medications/settings-group.tsx": 4, + "components/medications/side-effects-section.tsx": 5, + "components/medications/take-all-due-dialog.tsx": 1, + "components/medications/take-all-due.ts": 6, + "components/medications/titration-timeline.tsx": 1, + "components/medications/use-medication-categories.ts": 1, + "components/medications/use-medication-intake.ts": 10, + "components/medications/wizard/__tests__/wizard-payload.test.ts": 4, + "components/medications/wizard/medication-wizard-dialog.tsx": 7, + "components/medications/wizard/nl-bridge.ts": 1, + "components/medications/wizard/steps/step3-dose.tsx": 2, + "components/medications/wizard/steps/step5-cadence.tsx": 1, + "components/medications/wizard/steps/step8-summary.tsx": 1, + "components/medications/wizard/wizard-payload.ts": 45, + "components/medications/wizard/wizard-stepper.tsx": 1, + "components/mental-health/__tests__/mental-wellbeing.test.tsx": 3, + "components/mental-health/assessment-history-chart.tsx": 4, + "components/mental-health/assessment-history.tsx": 2, + "components/mental-health/assessment-result.tsx": 2, + "components/mental-health/assessment-trend-note.tsx": 1, + "components/mental-health/check-in-nav.ts": 2, + "components/mental-health/check-in-wizard.tsx": 4, + "components/mental-health/crisis-card.tsx": 1, + "components/mental-health/instrument-card.tsx": 5, + "components/mental-health/instrument-detail.tsx": 3, + "components/mental-health/mental-wellbeing.tsx": 4, + "components/mental-health/types.ts": 2, + "components/module-list/module-order-editor.tsx": 1, + "components/module-list/module-view-toggle.tsx": 1, + "components/monitoring/web-vitals-reporter.tsx": 2, + "components/mood/__tests__/mood-list-filter-select.test.tsx": 2, + "components/mood/__tests__/mood-note-fulltext.test.tsx": 1, + "components/mood/__tests__/mood-qol-fixes.test.tsx": 3, + "components/mood/__tests__/mood-tag-icon-picker.test.tsx": 1, + "components/mood/__tests__/mood-tag-picker-custom.test.tsx": 1, + "components/mood/manage/__tests__/catalog-helpers.test.ts": 1, + "components/mood/manage/archived-tags-card.tsx": 1, + "components/mood/manage/tag-editor-sheet.tsx": 1, + "components/mood/manage/tag-groups-card.tsx": 2, + "components/mood/manage/tag-manager-card.tsx": 3, + "components/mood/manage/use-mood-tag-manage.ts": 2, + "components/mood/mood-form.tsx": 23, + "components/mood/mood-list.tsx": 22, + "components/mood/mood-quick-tags.tsx": 1, + "components/mood/mood-tag-icon-picker.tsx": 1, + "components/mood/mood-tag-icons.ts": 4, + "components/mood/mood-tag-picker.tsx": 7, + "components/mood/recent-tags.ts": 1, + "components/onboarding/__tests__/anamnesis-card.test.tsx": 1, + "components/onboarding/__tests__/confirm-failure-recovery.test.ts": 1, + "components/onboarding/__tests__/confirm-screen.test.tsx": 1, + "components/onboarding/__tests__/done-screen.test.tsx": 1, + "components/onboarding/__tests__/first-result-screen.test.tsx": 2, + "components/onboarding/__tests__/getting-started-checklist.test.tsx": 1, + "components/onboarding/__tests__/question-screen.test.tsx": 1, + "components/onboarding/__tests__/tour-launcher-gate.test.ts": 1, + "components/onboarding/__tests__/tour-launcher-keys.test.ts": 2, + "components/onboarding/__tests__/units-screen.test.tsx": 1, + "components/onboarding/anamnesis-card.tsx": 2, + "components/onboarding/baseline-fields.tsx": 1, + "components/onboarding/baseline-form.tsx": 3, + "components/onboarding/confirm-screen.tsx": 5, + "components/onboarding/done-screen.tsx": 2, + "components/onboarding/first-result-screen.tsx": 3, + "components/onboarding/getting-started-checklist.tsx": 13, + "components/onboarding/onboarding-shell.tsx": 1, + "components/onboarding/question-screen.tsx": 1, + "components/onboarding/tour-launcher.tsx": 16, + "components/onboarding/tour.tsx": 7, + "components/onboarding/units-screen.tsx": 1, + "components/onboarding/welcome-screen.tsx": 1, + "components/profile/__tests__/height-entry-surfaces.test.tsx": 1, + "components/profile/height-field-control.tsx": 1, + "components/providers.tsx": 3, + "components/records/about-me-note-manager.tsx": 3, + "components/records/ai-profile-inclusion-manager.tsx": 1, + "components/records/allergy-form.tsx": 1, + "components/records/allergy-manager.tsx": 1, + "components/records/audit-c-panel.tsx": 1, + "components/records/conditions-manager.tsx": 1, + "components/records/family-history-form.tsx": 1, + "components/records/family-history-manager.tsx": 1, + "components/records/free-text-take-over.tsx": 1, + "components/records/life-circumstances-manager.tsx": 1, + "components/records/screening-hints-card.tsx": 1, + "components/settings/__tests__/about-section-update-badge.test.tsx": 1, + "components/settings/__tests__/account-section-timezone-seed.test.ts": 1, + "components/settings/__tests__/advanced-account-delete.test.tsx": 2, + "components/settings/__tests__/ai-section.test.tsx": 5, + "components/settings/__tests__/api-section-responsive.test.tsx": 1, + "components/settings/__tests__/coach-links-without-coach.test.tsx": 1, + "components/settings/__tests__/coach-memory-section.test.tsx": 1, + "components/settings/__tests__/coach-prefs-sources-summary.test.tsx": 1, + "components/settings/__tests__/coach-section.test.tsx": 4, + "components/settings/__tests__/dashboard-layout-section.test.tsx": 10, + "components/settings/__tests__/export-section.test.tsx": 5, + "components/settings/__tests__/gesundheitsakte-section.test.tsx": 2, + "components/settings/__tests__/import-panel.test.tsx": 1, + "components/settings/__tests__/insights-pill-order-ecg.test.tsx": 1, + "components/settings/__tests__/integrations-section.test.tsx": 3, + "components/settings/__tests__/layout-groups.test.ts": 2, + "components/settings/__tests__/layout-section.test.tsx": 2, + "components/settings/__tests__/medications-section.test.tsx": 6, + "components/settings/__tests__/modules-section.test.tsx": 5, + "components/settings/__tests__/mood-reminder-card.test.tsx": 2, + "components/settings/__tests__/mood-section.test.tsx": 4, + "components/settings/__tests__/notifications-section-module-gating.test.tsx": 2, + "components/settings/__tests__/nutrient-intake-card.test.tsx": 1, + "components/settings/__tests__/oauth-return-outcome.test.ts": 1, + "components/settings/__tests__/privacy-section.test.tsx": 1, + "components/settings/__tests__/score-refusal-consumer-guard.test.ts": 1, + "components/settings/__tests__/section-frame-harness.tsx": 1, + "components/settings/__tests__/sections-i18n-parity.test.ts": 1, + "components/settings/__tests__/sections.test.tsx": 21, + "components/settings/__tests__/settings-shell.test.tsx": 41, + "components/settings/__tests__/sharing-section.test.tsx": 1, + "components/settings/__tests__/timezone-picker.test.tsx": 1, + "components/settings/__tests__/unit-preference-select.test.tsx": 1, + "components/settings/about-section.tsx": 3, + "components/settings/access-section.tsx": 2, + "components/settings/access/__tests__/access-panel.test.tsx": 1, + "components/settings/access/__tests__/grant-access-level.test.tsx": 4, + "components/settings/access/__tests__/managed-profile-affordances.test.tsx": 3, + "components/settings/access/__tests__/managed-profile-handover-errors.test.ts": 1, + "components/settings/access/grant-action-error.tsx": 3, + "components/settings/access/grant-invite-card.tsx": 1, + "components/settings/access/grant-row.tsx": 1, + "components/settings/access/grants-given-card.tsx": 2, + "components/settings/access/grants-received-card.tsx": 2, + "components/settings/access/handover-decision.tsx": 1, + "components/settings/access/managed-profile-card.tsx": 2, + "components/settings/access/managed-profile-create-form.tsx": 2, + "components/settings/access/managed-profile-edit-form.tsx": 1, + "components/settings/access/managed-profile-guardians.tsx": 1, + "components/settings/access/managed-profile-handover.tsx": 1, + "components/settings/access/record-activity-card.tsx": 1, + "components/settings/account-section/__tests__/display-preferences-card.test.tsx": 1, + "components/settings/account-section/account-section-utils.ts": 2, + "components/settings/account-section/index.tsx": 6, + "components/settings/account-section/setup-card.tsx": 1, + "components/settings/advanced-section.tsx": 5, + "components/settings/ai-section.tsx": 6, + "components/settings/ai/ai-consent-card.tsx": 1, + "components/settings/ai/ai-insights-card.tsx": 3, + "components/settings/ai/ai-usage-card.tsx": 1, + "components/settings/ai/auto-read-card.tsx": 1, + "components/settings/ai/fallback-chain-card.tsx": 1, + "components/settings/ai/reasoning-effort-field.tsx": 1, + "components/settings/ai/response-timeout-card.tsx": 1, + "components/settings/ai/shared.ts": 4, + "components/settings/anamnesis-section.tsx": 2, + "components/settings/api-section.tsx": 2, + "components/settings/coach-memory-section.tsx": 3, + "components/settings/coach-nudge-card.tsx": 4, + "components/settings/coach-prefs-section.tsx": 2, + "components/settings/coach-reminders-section.tsx": 2, + "components/settings/coach-section.tsx": 3, + "components/settings/dashboard-layout-section.tsx": 30, + "components/settings/dashboard-section.tsx": 1, + "components/settings/environment-section.tsx": 3, + "components/settings/export-section.tsx": 4, + "components/settings/gesundheitsakte-section.tsx": 3, + "components/settings/illness-section.tsx": 1, + "components/settings/import-panel/health-connect-import-card.tsx": 1, + "components/settings/import-panel/index.tsx": 2, + "components/settings/injection-sites-card.tsx": 1, + "components/settings/insights-overview-arrange-section.tsx": 1, + "components/settings/insights-pill-order-section.tsx": 7, + "components/settings/insights-section.tsx": 2, + "components/settings/integrations-section.tsx": 3, + "components/settings/integrations/__tests__/apple-health-card.test.tsx": 2, + "components/settings/integrations/apple-health-card.tsx": 1, + "components/settings/integrations/connections-panel.tsx": 5, + "components/settings/integrations/garmin-info-note.tsx": 1, + "components/settings/integrations/notification-channels-panel.tsx": 1, + "components/settings/integrations/setup-guide-link.tsx": 5, + "components/settings/integrations/shared.tsx": 3, + "components/settings/labs-section.tsx": 1, + "components/settings/layout-groups.ts": 2, + "components/settings/layout-section.tsx": 1, + "components/settings/low-stock-card.tsx": 4, + "components/settings/medications-section.tsx": 3, + "components/settings/module-icons.ts": 8, + "components/settings/modules-section.tsx": 3, + "components/settings/mood-reminder-card.tsx": 4, + "components/settings/mood-section.tsx": 2, + "components/settings/notifications-section.tsx": 1, + "components/settings/privacy-section.tsx": 2, + "components/settings/record-anamnesis-section.tsx": 1, + "components/settings/record-settings-section-gate.tsx": 1, + "components/settings/report-selection/selection-panel.tsx": 1, + "components/settings/security-activity-card.tsx": 2, + "components/settings/security-section/__tests__/totp-card.test.tsx": 1, + "components/settings/security-sessions-card.tsx": 1, + "components/settings/settings-anchor-forward.tsx": 3, + "components/settings/settings-hub-back-link.tsx": 1, + "components/settings/settings-shell.tsx": 8, + "components/settings/share-link-create-form.tsx": 3, + "components/settings/sharing-section.tsx": 3, + "components/settings/sources-section.tsx": 7, + "components/settings/thresholds-editor-section.tsx": 2, + "components/settings/thresholds-section.tsx": 2, + "components/settings/timezone-picker.tsx": 2, + "components/settings/trusted-devices-card.tsx": 1, + "components/settings/unit-preference-select.tsx": 1, + "components/settings/vorsorge-section.tsx": 2, + "components/symptoms/symptom-entry-form.tsx": 1, + "components/symptoms/use-symptoms.ts": 1, + "components/targets/__tests__/consistency-strip.test.tsx": 1, + "components/targets/__tests__/range-bar.test.tsx": 2, + "components/targets/__tests__/target-edit-sheet.test.tsx": 3, + "components/targets/__tests__/target-status-pill.test.tsx": 2, + "components/targets/consistency-strip.tsx": 1, + "components/targets/target-edit-sheet.tsx": 4, + "components/targets/target-status-pill.tsx": 2, + "components/timeline/__tests__/chronicle-model.test.ts": 1, + "components/timeline/__tests__/label-keys.test.ts": 1, + "components/timeline/__tests__/medication-rows.test.ts": 1, + "components/timeline/__tests__/no-raw-code-guard.test.tsx": 1, + "components/timeline/__tests__/no-template-key-guard.test.ts": 1, + "components/timeline/__tests__/readiness-and-draft.test.ts": 1, + "components/timeline/__tests__/timeline-components.test.tsx": 1, + "components/timeline/__tests__/timeline-default-zoom.test.tsx": 1, + "components/timeline/__tests__/timeline-geometry.test.ts": 1, + "components/timeline/__tests__/timeline-rework.test.tsx": 1, + "components/timeline/__tests__/timeline-view-keys.test.tsx": 1, + "components/timeline/__tests__/value-options.test.ts": 1, + "components/timeline/life-event-form.lazy.tsx": 1, + "components/ui/__tests__/filter-bar.test.tsx": 1, + "components/ui/back-link.tsx": 1, + "components/ui/button.tsx": 1, + "components/ui/card.tsx": 1, + "components/ui/checkbox.tsx": 1, + "components/ui/date-time-field.tsx": 1, + "components/ui/dialog.tsx": 2, + "components/ui/dropdown-menu.tsx": 1, + "components/ui/filter-bar.tsx": 2, + "components/ui/input.tsx": 4, + "components/ui/learn-more-link.tsx": 2, + "components/ui/learning-gate.tsx": 1, + "components/ui/list-row.tsx": 1, + "components/ui/native-select.tsx": 2, + "components/ui/password-input.tsx": 1, + "components/ui/popover.tsx": 1, + "components/ui/pull-to-refresh-indicator.tsx": 1, + "components/ui/responsive-sheet.tsx": 2, + "components/ui/select.tsx": 2, + "components/ui/sheet-section.tsx": 1, + "components/ui/sheet.tsx": 1, + "components/ui/skeleton.tsx": 1, + "components/ui/switch.tsx": 1, + "components/ui/tabs.tsx": 2, + "components/ui/textarea.tsx": 1, + "components/vaccinations/__tests__/custom-vaccine.test.tsx": 1, + "components/vaccinations/catalog-picker.tsx": 1, + "components/vaccinations/custom-vaccine-list.tsx": 1, + "components/vaccinations/custom-vaccine-sheet.tsx": 1, + "components/vaccinations/use-vaccinations.ts": 2, + "components/vaccinations/vaccinations-view.tsx": 2, + "components/version-poller.tsx": 3, + "components/workouts/manual-workout-form.tsx": 1, + "components/workouts/training-load-card.tsx": 1, + "hooks/__tests__/use-auth-retry.test.ts": 1, + "hooks/__tests__/use-insight-status.test.ts": 2, + "hooks/__tests__/use-measurement-reminders-refetch.test.ts": 1, + "hooks/__tests__/use-record-capabilities.test.ts": 1, + "hooks/__tests__/use-roving-radio-group.test.ts": 1, + "hooks/_internal/use-query-client-safe.ts": 1, + "hooks/use-account-switch.ts": 2, + "hooks/use-ai-capability.ts": 3, + "hooks/use-ai-run.ts": 1, + "hooks/use-auth.ts": 34, + "hooks/use-chart-domain-stats.ts": 1, + "hooks/use-chart-overlay-prefs.ts": 2, + "hooks/use-coach-message-results.ts": 1, + "hooks/use-coach-plans.ts": 1, + "hooks/use-coach-prefs.ts": 5, + "hooks/use-coach-reminders.ts": 1, + "hooks/use-count-up.ts": 1, + "hooks/use-fenced-object-url.ts": 1, + "hooks/use-insight-status.ts": 8, + "hooks/use-insights-analytics.ts": 2, + "hooks/use-insights-layout-prefs.ts": 1, + "hooks/use-insights-layout.ts": 2, + "hooks/use-is-mobile.ts": 1, + "hooks/use-load-more-sentinel.ts": 1, + "hooks/use-measurement-reminders.ts": 7, + "hooks/use-module-enabled.ts": 1, + "hooks/use-module-toggle.ts": 2, + "hooks/use-mounted.ts": 1, + "hooks/use-pull-to-refresh.ts": 2, + "hooks/use-record-capabilities.ts": 7, + "hooks/use-roving-radio-group.ts": 1, + "hooks/use-scroll-reset-on-route.ts": 1, + "hooks/use-unit-display.ts": 1, + "hooks/use-workouts.ts": 3, + "lib/__tests__/achievements-no-insults.test.ts": 1, + "lib/__tests__/acting-account-resolver.test.ts": 8, + "lib/__tests__/api-response-zod.test.ts": 2, + "lib/__tests__/dashboard-layout.test.ts": 20, + "lib/__tests__/db.test.ts": 1, + "lib/__tests__/doctor-report-data.test.ts": 1, + "lib/__tests__/doctor-report-module-backstop.test.ts": 1, + "lib/__tests__/doctor-report-pdf-core.test.ts": 3, + "lib/__tests__/export.test.ts": 3, + "lib/__tests__/format-active-locale.test.ts": 1, + "lib/__tests__/format-date-or-relative.test.ts": 3, + "lib/__tests__/format-locale.test.ts": 4, + "lib/__tests__/geo-asn.test.ts": 1, + "lib/__tests__/geo.test.ts": 7, + "lib/__tests__/get-client-ip.test.ts": 2, + "lib/__tests__/glucose.test.ts": 1, + "lib/__tests__/i18n-locale-integrity.test.ts": 3, + "lib/__tests__/idempotency.test.ts": 2, + "lib/__tests__/insights-layout-reorder.test.ts": 1, + "lib/__tests__/insights-layout.test.ts": 3, + "lib/__tests__/medication-category.test.ts": 1, + "lib/__tests__/medication-list-layout.test.ts": 1, + "lib/__tests__/query-keys.test.ts": 18, + "lib/__tests__/rate-limit-auth-surface.test.ts": 1, + "lib/__tests__/require-auth-bearer.test.ts": 1, + "lib/__tests__/ssrf-pin-inventory.test.ts": 3, + "lib/ai/__tests__/ai-budgets.test.ts": 2, + "lib/ai/__tests__/anthropic-client.test.ts": 2, + "lib/ai/__tests__/anthropic-reasoning.test.ts": 1, + "lib/ai/__tests__/briefing-grounding.test.ts": 2, + "lib/ai/__tests__/citation-coverage-logging.test.ts": 1, + "lib/ai/__tests__/coach-prompt-v423.test.ts": 4, + "lib/ai/__tests__/codex-client.test.ts": 3, + "lib/ai/__tests__/codex-reasoning.test.ts": 1, + "lib/ai/__tests__/codex-timeout.test.ts": 1, + "lib/ai/__tests__/confidence-schema.test.ts": 1, + "lib/ai/__tests__/daily-briefing-schema.test.ts": 3, + "lib/ai/__tests__/insight-interpretation.test.ts": 1, + "lib/ai/__tests__/legacy-payload-detection.test.ts": 2, + "lib/ai/__tests__/local-client-egress.test.ts": 1, + "lib/ai/__tests__/local-client.test.ts": 2, + "lib/ai/__tests__/local-host-allowlist.test.ts": 1, + "lib/ai/__tests__/local-reasoning.test.ts": 1, + "lib/ai/__tests__/medical-reference-prompt.test.ts": 4, + "lib/ai/__tests__/medical-references.test.ts": 1, + "lib/ai/__tests__/model-availability.test.ts": 1, + "lib/ai/__tests__/no-default-positivity-opener.test.ts": 1, + "lib/ai/__tests__/openai-client.test.ts": 1, + "lib/ai/__tests__/openai-compatible-client.test.ts": 1, + "lib/ai/__tests__/openai-reasoning.test.ts": 1, + "lib/ai/__tests__/provider-health-ledger.test.ts": 2, + "lib/ai/__tests__/provider-legacy-private-host.test.ts": 1, + "lib/ai/__tests__/provider-runner.test.ts": 8, + "lib/ai/__tests__/provider-state-never-persisted.test.ts": 1, + "lib/ai/__tests__/rationale-prompt.test.ts": 1, + "lib/ai/__tests__/rationale-schema.test.ts": 1, + "lib/ai/__tests__/reasoning-passthrough.test.ts": 1, + "lib/ai/__tests__/reference-id-schema.test.ts": 1, + "lib/ai/__tests__/server-provider-health.test.ts": 1, + "lib/ai/__tests__/trend-annotations-schema.test.ts": 2, + "lib/ai/ai-budgets.ts": 13, + "lib/ai/anthropic-client.ts": 14, + "lib/ai/briefing-grounding.ts": 2, + "lib/ai/capabilities/load.ts": 1, + "lib/ai/capabilities/resolve.ts": 1, + "lib/ai/capabilities/types.ts": 5, + "lib/ai/capabilities/usage.ts": 1, + "lib/ai/citation-coverage.ts": 2, + "lib/ai/coach/__tests__/budget.test.ts": 4, + "lib/ai/coach/__tests__/clarify-tool.test.ts": 1, + "lib/ai/coach/__tests__/clusters.test.ts": 1, + "lib/ai/coach/__tests__/coach-memory-shared.test.ts": 1, + "lib/ai/coach/__tests__/coach-prose-grounding-ledger.test.ts": 1, + "lib/ai/coach/__tests__/coach-prose-grounding-no-tools.test.ts": 1, + "lib/ai/coach/__tests__/coach-prose-grounding.test.ts": 4, + "lib/ai/coach/__tests__/correlations-module-gate.test.ts": 1, + "lib/ai/coach/__tests__/correlations-read.test.ts": 2, + "lib/ai/coach/__tests__/data-field-injection.test.ts": 1, + "lib/ai/coach/__tests__/facts-deterministic.test.ts": 6, + "lib/ai/coach/__tests__/glp1-snapshot.test.ts": 1, + "lib/ai/coach/__tests__/grounding-ledger.test.ts": 1, + "lib/ai/coach/__tests__/illness-scores.test.ts": 1, + "lib/ai/coach/__tests__/illness-snapshot-body-site.test.ts": 1, + "lib/ai/coach/__tests__/illness-snapshot-label.test.ts": 1, + "lib/ai/coach/__tests__/illness-snapshot-symptoms.test.ts": 1, + "lib/ai/coach/__tests__/keyvalues-parse.test.ts": 1, + "lib/ai/coach/__tests__/latest-messages.test.ts": 1, + "lib/ai/coach/__tests__/learn-link-guard.test.ts": 1, + "lib/ai/coach/__tests__/medication-extract-prompt.test.ts": 1, + "lib/ai/coach/__tests__/memory-snapshot.test.ts": 1, + "lib/ai/coach/__tests__/persistence-document-scope.test.ts": 2, + "lib/ai/coach/__tests__/persistence-rename.test.ts": 1, + "lib/ai/coach/__tests__/persistence-results.test.ts": 1, + "lib/ai/coach/__tests__/persistence-title-encryption.test.ts": 1, + "lib/ai/coach/__tests__/persistence-trail.test.ts": 1, + "lib/ai/coach/__tests__/plans.test.ts": 1, + "lib/ai/coach/__tests__/reminders.test.ts": 2, + "lib/ai/coach/__tests__/self-context.test.ts": 2, + "lib/ai/coach/__tests__/snapshot-budget.test.ts": 4, + "lib/ai/coach/__tests__/snapshot-new-metrics.test.ts": 9, + "lib/ai/coach/__tests__/snapshot.test.ts": 19, + "lib/ai/coach/__tests__/stream-events-ids.test.ts": 1, + "lib/ai/coach/__tests__/suggest-action.test.ts": 1, + "lib/ai/coach/__tests__/system-prompt-contract-parity.test.ts": 1, + "lib/ai/coach/__tests__/system-prompt.test.ts": 11, + "lib/ai/coach/__tests__/tools-executor.test.ts": 6, + "lib/ai/coach/__tests__/tools-inventory.test.ts": 2, + "lib/ai/coach/__tests__/tools-loop-budget.test.ts": 2, + "lib/ai/coach/__tests__/tools-loop-callbacks.test.ts": 1, + "lib/ai/coach/__tests__/tools-loop-final-round-wire.test.ts": 1, + "lib/ai/coach/__tests__/tools-loop.test.ts": 3, + "lib/ai/coach/__tests__/tools-source-keys.test.ts": 1, + "lib/ai/coach/__tests__/visits-snapshot.test.ts": 1, + "lib/ai/coach/about-me.ts": 3, + "lib/ai/coach/activity/__tests__/recorder.test.ts": 1, + "lib/ai/coach/activity/catalog.ts": 1, + "lib/ai/coach/activity/contract.ts": 1, + "lib/ai/coach/activity/recorder.ts": 1, + "lib/ai/coach/activity/screen.ts": 1, + "lib/ai/coach/budget.ts": 24, + "lib/ai/coach/chat-request-builder.ts": 8, + "lib/ai/coach/clusters.ts": 1, + "lib/ai/coach/coach-memory-refresh-worker.ts": 3, + "lib/ai/coach/coach-memory-shared.ts": 2, + "lib/ai/coach/coach-prose-grounding.ts": 4, + "lib/ai/coach/conversation-summary.ts": 2, + "lib/ai/coach/data-fence.ts": 1, + "lib/ai/coach/derived-snapshot.ts": 8, + "lib/ai/coach/eval/__tests__/grade-voice.test.ts": 1, + "lib/ai/coach/eval/__tests__/scenarios-live.test.ts": 1, + "lib/ai/coach/eval/__tests__/scenarios.test.ts": 3, + "lib/ai/coach/eval/grade-voice.ts": 1, + "lib/ai/coach/eval/red-team.ts": 3, + "lib/ai/coach/eval/run-case.ts": 1, + "lib/ai/coach/eval/scenarios.ts": 10, + "lib/ai/coach/experiment-flag.ts": 1, + "lib/ai/coach/facts.ts": 6, + "lib/ai/coach/follow-ups/__tests__/continue.test.ts": 1, + "lib/ai/coach/follow-ups/__tests__/derive.test.ts": 1, + "lib/ai/coach/follow-ups/__tests__/resolve.test.ts": 1, + "lib/ai/coach/follow-ups/resolve.ts": 1, + "lib/ai/coach/glp1-snapshot.ts": 5, + "lib/ai/coach/grounding-ledger.ts": 1, + "lib/ai/coach/illness-snapshot.ts": 2, + "lib/ai/coach/keyvalues.ts": 3, + "lib/ai/coach/labs-snapshot.ts": 3, + "lib/ai/coach/learn-catalog.ts": 1, + "lib/ai/coach/learn-link-guard.ts": 1, + "lib/ai/coach/medication-extract-prompt.ts": 1, + "lib/ai/coach/memory-snapshot.ts": 2, + "lib/ai/coach/memory/__tests__/context-block.test.ts": 1, + "lib/ai/coach/memory/__tests__/plan-progress.test.ts": 1, + "lib/ai/coach/memory/__tests__/propose-plan.test.ts": 1, + "lib/ai/coach/memory/__tests__/remember.test.ts": 1, + "lib/ai/coach/memory/context-block.ts": 1, + "lib/ai/coach/memory/contract.ts": 1, + "lib/ai/coach/memory/lexicon.ts": 1, + "lib/ai/coach/memory/plan-progress.ts": 1, + "lib/ai/coach/memory/propose-plan.ts": 1, + "lib/ai/coach/memory/remember.ts": 1, + "lib/ai/coach/memory/shared.ts": 1, + "lib/ai/coach/memory/trail-read.ts": 1, + "lib/ai/coach/memory/turn-writes.ts": 1, + "lib/ai/coach/outbound-guard.ts": 1, + "lib/ai/coach/persistence.ts": 37, + "lib/ai/coach/plans.ts": 5, + "lib/ai/coach/prompt-cache-key.ts": 1, + "lib/ai/coach/reference-grounding.ts": 1, + "lib/ai/coach/reminders.ts": 2, + "lib/ai/coach/results/__tests__/compare.test.ts": 1, + "lib/ai/coach/results/__tests__/executor-results.test.ts": 1, + "lib/ai/coach/results/__tests__/loop-result-tables.test.ts": 1, + "lib/ai/coach/results/__tests__/metric-table-tool.test.ts": 1, + "lib/ai/coach/results/__tests__/prior-result-refs.test.ts": 1, + "lib/ai/coach/results/__tests__/project.test.ts": 1, + "lib/ai/coach/results/__tests__/refs.test.ts": 1, + "lib/ai/coach/scope-gate.ts": 2, + "lib/ai/coach/self-context-questions.ts": 2, + "lib/ai/coach/self-report-fence.ts": 2, + "lib/ai/coach/snapshot-blocks/__tests__/sleep-block-night-key.test.ts": 1, + "lib/ai/coach/snapshot-blocks/compliance-block.ts": 5, + "lib/ai/coach/snapshot-blocks/glucose-block.ts": 3, + "lib/ai/coach/snapshot-blocks/sleep-block.ts": 2, + "lib/ai/coach/snapshot-blocks/workouts-block.ts": 3, + "lib/ai/coach/snapshot-cache.ts": 2, + "lib/ai/coach/snapshot-prelude.ts": 17, + "lib/ai/coach/snapshot-series.ts": 5, + "lib/ai/coach/snapshot.ts": 42, + "lib/ai/coach/source-measurement-types.ts": 1, + "lib/ai/coach/suggest-action.ts": 1, + "lib/ai/coach/suggest-gate.ts": 1, + "lib/ai/coach/suggest-reminder.ts": 1, + "lib/ai/coach/system-prompt.ts": 17, + "lib/ai/coach/tools/__tests__/environment-read.test.ts": 1, + "lib/ai/coach/tools/__tests__/progress.test.ts": 1, + "lib/ai/coach/tools/__tests__/turn-budget.test.ts": 2, + "lib/ai/coach/tools/availability.ts": 2, + "lib/ai/coach/tools/compare-series.ts": 1, + "lib/ai/coach/tools/correlations-read.ts": 7, + "lib/ai/coach/tools/definitions.ts": 11, + "lib/ai/coach/tools/dialog-tools.ts": 1, + "lib/ai/coach/tools/environment-read.ts": 2, + "lib/ai/coach/tools/executor.ts": 17, + "lib/ai/coach/tools/index.ts": 1, + "lib/ai/coach/tools/inventory.ts": 10, + "lib/ai/coach/tools/loop.ts": 22, + "lib/ai/coach/tools/progress.ts": 1, + "lib/ai/coach/tools/source-keys.ts": 4, + "lib/ai/coach/tools/system-addendum.ts": 2, + "lib/ai/coach/tools/turn-budget.ts": 2, + "lib/ai/coach/trajectory-snapshot.ts": 1, + "lib/ai/coach/turn/__tests__/continue-turn.test.ts": 1, + "lib/ai/coach/turn/__tests__/conversation-names.test.ts": 1, + "lib/ai/coach/turn/__tests__/dialog-frames.test.ts": 1, + "lib/ai/coach/turn/__tests__/follow-up-turn.test.ts": 1, + "lib/ai/coach/turn/__tests__/ledger-reasoning.test.ts": 1, + "lib/ai/coach/turn/__tests__/model-results.test.ts": 1, + "lib/ai/coach/turn/__tests__/reply-guards-clarify.test.ts": 1, + "lib/ai/coach/turn/__tests__/sse-frames.test.ts": 1, + "lib/ai/coach/turn/__tests__/steps.test.ts": 1, + "lib/ai/coach/turn/addenda/followups.ts": 1, + "lib/ai/coach/turn/addenda/index.ts": 1, + "lib/ai/coach/turn/addenda/results.ts": 1, + "lib/ai/coach/turn/budget.ts": 6, + "lib/ai/coach/turn/cards.ts": 1, + "lib/ai/coach/turn/chain.ts": 3, + "lib/ai/coach/turn/context.ts": 14, + "lib/ai/coach/turn/conversation.ts": 4, + "lib/ai/coach/turn/errors.ts": 1, + "lib/ai/coach/turn/model.ts": 48, + "lib/ai/coach/turn/persist.ts": 12, + "lib/ai/coach/turn/pipeline.ts": 8, + "lib/ai/coach/turn/provenance.ts": 15, + "lib/ai/coach/turn/reasoning.ts": 1, + "lib/ai/coach/turn/reply-guards.ts": 18, + "lib/ai/coach/turn/reuse-turn.ts": 1, + "lib/ai/coach/turn/sse.ts": 10, + "lib/ai/coach/turn/steps.ts": 3, + "lib/ai/coach/turn/types.ts": 17, + "lib/ai/coach/types.ts": 58, + "lib/ai/coach/visits-snapshot.ts": 1, + "lib/ai/codex-client.ts": 18, + "lib/ai/codex-slug-cache.ts": 1, + "lib/ai/consent-guard.ts": 1, + "lib/ai/feedback-attribution.ts": 2, + "lib/ai/insight-interpretation.ts": 1, + "lib/ai/legacy-payload.ts": 2, + "lib/ai/legacy-private-host-report.ts": 1, + "lib/ai/local-client.ts": 19, + "lib/ai/medical-references.ts": 1, + "lib/ai/mock-client.ts": 5, + "lib/ai/openai-client.ts": 18, + "lib/ai/prompts/__tests__/assessment-prompt-contract.test.ts": 5, + "lib/ai/prompts/__tests__/briefing-personalisation.test.ts": 1, + "lib/ai/prompts/__tests__/coach-prompt-locale.test.ts": 1, + "lib/ai/prompts/__tests__/drug-level-refusal.probe.test.ts": 1, + "lib/ai/prompts/__tests__/interpretation-block.test.ts": 1, + "lib/ai/prompts/__tests__/opener-archetype.test.ts": 1, + "lib/ai/prompts/__tests__/prompt-comparison.test.ts": 1, + "lib/ai/prompts/__tests__/refusal-probe.test.ts": 1, + "lib/ai/prompts/__tests__/safety-contracts-parity.test.ts": 1, + "lib/ai/prompts/__tests__/shared-contracts-coverage.test.ts": 15, + "lib/ai/prompts/base-system.ts": 6, + "lib/ai/prompts/biomarker.ts": 1, + "lib/ai/prompts/blood-pressure.ts": 2, + "lib/ai/prompts/bmi.ts": 2, + "lib/ai/prompts/compact-sections.ts": 1, + "lib/ai/prompts/general-status.ts": 3, + "lib/ai/prompts/insight-generator.ts": 10, + "lib/ai/prompts/insight-system-prompt.ts": 2, + "lib/ai/prompts/interpretation-block.ts": 1, + "lib/ai/prompts/medication-compliance.ts": 2, + "lib/ai/prompts/metric-archetypes.ts": 4, + "lib/ai/prompts/mood.ts": 2, + "lib/ai/prompts/native-prompts.ts": 1, + "lib/ai/prompts/opener-archetype.ts": 1, + "lib/ai/prompts/pulse.ts": 2, + "lib/ai/prompts/safety-contracts.ts": 1, + "lib/ai/prompts/shared-contracts.ts": 7, + "lib/ai/prompts/status-batch.ts": 1, + "lib/ai/prompts/weight.ts": 2, + "lib/ai/provider-chain.ts": 1, + "lib/ai/provider-health-ledger.ts": 3, + "lib/ai/provider-runner.ts": 15, + "lib/ai/provider.ts": 10, + "lib/ai/reasoning/__tests__/controls.test.ts": 1, + "lib/ai/reasoning/controls.ts": 2, + "lib/ai/reasoning/dialect-cache.ts": 1, + "lib/ai/reasoning/levels.ts": 1, + "lib/ai/reasoning/resolve.ts": 1, + "lib/ai/reasoning/support.ts": 1, + "lib/ai/safety/__tests__/outbound-screen-guard2.test.ts": 1, + "lib/ai/safety/outbound-screen.ts": 4, + "lib/ai/schema.ts": 15, + "lib/ai/server-provider-health.ts": 1, + "lib/ai/types.ts": 31, + "lib/ai/usage-level.ts": 1, + "lib/ai/vision-capability.ts": 1, + "lib/analytics/__tests__/bp-grade.test.ts": 2, + "lib/analytics/__tests__/bp-in-target-fast-path.test.ts": 4, + "lib/analytics/__tests__/bp-in-target.test.ts": 8, + "lib/analytics/__tests__/classifications.test.ts": 1, + "lib/analytics/__tests__/compliance-courses.test.ts": 1, + "lib/analytics/__tests__/correlations-fast-path.test.ts": 3, + "lib/analytics/__tests__/effective-range.test.ts": 1, + "lib/analytics/__tests__/glucose-panel-coherence.test.ts": 1, + "lib/analytics/__tests__/glucose-panel-window.test.ts": 2, + "lib/analytics/__tests__/merge-slim-thick.test.ts": 3, + "lib/analytics/__tests__/mood-series.test.ts": 1, + "lib/analytics/__tests__/range-delta.test.ts": 1, + "lib/analytics/__tests__/resting-pulse.test.ts": 1, + "lib/analytics/__tests__/source-priority.test.ts": 5, + "lib/analytics/__tests__/summaries-slice.test.ts": 18, + "lib/analytics/__tests__/summaries-slice.wmy-cap.test.ts": 1, + "lib/analytics/__tests__/trend-delta.test.ts": 1, + "lib/analytics/__tests__/trends.test.ts": 2, + "lib/analytics/__tests__/window-trend.test.ts": 1, + "lib/analytics/bp-grade.ts": 2, + "lib/analytics/bp-in-target-fast-path.ts": 24, + "lib/analytics/bp-in-target.ts": 13, + "lib/analytics/classifications.ts": 2, + "lib/analytics/compliance.ts": 1, + "lib/analytics/compliance/__tests__/as-needed-compliance-exclusion.test.ts": 1, + "lib/analytics/compliance/__tests__/compliance-bundle.test.ts": 1, + "lib/analytics/compliance/__tests__/compliance-pause-era.test.ts": 1, + "lib/analytics/compliance/__tests__/compliance.test.ts": 27, + "lib/analytics/compliance/__tests__/schedule-compliance-select.test.ts": 1, + "lib/analytics/compliance/adapters.ts": 6, + "lib/analytics/compliance/cycle.ts": 5, + "lib/analytics/compliance/display.ts": 18, + "lib/analytics/compliance/dose-status.ts": 4, + "lib/analytics/compliance/ledger.ts": 7, + "lib/analytics/compliance/occurrences.ts": 9, + "lib/analytics/compliance/parsing.ts": 1, + "lib/analytics/compliance/types.ts": 24, + "lib/analytics/compliance/windows.ts": 4, + "lib/analytics/correlations-fast-path.ts": 11, + "lib/analytics/correlations.ts": 1, + "lib/analytics/intraday-pulse-io.ts": 2, + "lib/analytics/intraday-pulse.ts": 3, + "lib/analytics/merge-slim-thick.ts": 2, + "lib/analytics/mood-series.ts": 2, + "lib/analytics/range-delta.ts": 1, + "lib/analytics/range-shared.ts": 1, + "lib/analytics/resting-pulse.ts": 2, + "lib/analytics/schedule-anchored-compliance.ts": 7, + "lib/analytics/score/__tests__/breadth.test.ts": 1, + "lib/analytics/score/__tests__/composite.test.ts": 1, + "lib/analytics/score/__tests__/composition-source.test.ts": 2, + "lib/analytics/score/__tests__/config-change-is-not-a-health-event.test.ts": 2, + "lib/analytics/score/__tests__/config.test.ts": 1, + "lib/analytics/score/__tests__/reader.test.ts": 1, + "lib/analytics/score/__tests__/reference-citation-guard.test.ts": 1, + "lib/analytics/score/breadth.ts": 1, + "lib/analytics/score/config.ts": 1, + "lib/analytics/score/modules.ts": 1, + "lib/analytics/score/reader.ts": 4, + "lib/analytics/score/shared.ts": 1, + "lib/analytics/score/types.ts": 5, + "lib/analytics/sleep-night.ts": 3, + "lib/analytics/source-priority.ts": 2, + "lib/analytics/source-rank-sql.ts": 3, + "lib/analytics/summaries-slice.ts": 37, + "lib/analytics/trend-delta.ts": 1, + "lib/analytics/trends.ts": 6, + "lib/analytics/value-bands.ts": 1, + "lib/analytics/window-confidence.ts": 1, + "lib/analytics/window-trend.ts": 1, + "lib/api-errors.ts": 2, + "lib/api-handler.ts": 22, + "lib/api-response.ts": 8, + "lib/api/__tests__/api-fetch.test.ts": 1, + "lib/api/__tests__/optimistic-token.test.ts": 1, + "lib/api/read-error.ts": 1, + "lib/api/record-fence.ts": 1, + "lib/auth/__tests__/audit.test.ts": 2, + "lib/auth/__tests__/invite-token.test.ts": 1, + "lib/auth/__tests__/passkey.test.ts": 1, + "lib/auth/__tests__/profile-update.test.ts": 1, + "lib/auth/acting-carrier.ts": 1, + "lib/auth/audit.ts": 2, + "lib/auth/device-fingerprint.ts": 1, + "lib/auth/handover-token.ts": 1, + "lib/auth/hibp.ts": 1, + "lib/auth/invite-token.ts": 2, + "lib/auth/login-alert.ts": 1, + "lib/auth/login-response.ts": 1, + "lib/auth/oidc-native-handoff.ts": 1, + "lib/auth/oidc.ts": 1, + "lib/auth/passkey.ts": 4, + "lib/auth/profile-update.ts": 5, + "lib/auth/security-activity.ts": 1, + "lib/auth/session.ts": 13, + "lib/avatar.ts": 1, + "lib/cache/__tests__/invalidate.test.ts": 13, + "lib/cache/invalidate.ts": 22, + "lib/cache/server-cache.ts": 26, + "lib/charts/__tests__/comparison-shift-edge-cases.test.ts": 1, + "lib/charts/__tests__/comparison-shift.test.ts": 1, + "lib/charts/__tests__/data-ready-latch.test.ts": 1, + "lib/charts/__tests__/x-axis-density.test.ts": 1, + "lib/charts/comparison-shift.ts": 1, + "lib/charts/data-ready-latch.ts": 1, + "lib/charts/window-stats.ts": 1, + "lib/charts/x-axis-density.ts": 6, + "lib/clinician-share/__tests__/passphrase.test.ts": 1, + "lib/clinician-share/__tests__/resolve-share-gate-state.test.ts": 1, + "lib/clinician-share/__tests__/resolve-share-token.test.ts": 1, + "lib/clinician-share/__tests__/share-token-reject-on-authed-route.test.ts": 1, + "lib/clinician-share/__tests__/share-view-data.test.ts": 3, + "lib/clinician-share/__tests__/unlock-cookie.test.ts": 1, + "lib/clinician-share/exif-strip.ts": 1, + "lib/clinician-share/passphrase.ts": 1, + "lib/clinician-share/resolve-share-token.ts": 3, + "lib/clinician-share/share-view-data.ts": 6, + "lib/clinician-share/unlock-cookie.ts": 1, + "lib/consent/receipts.ts": 2, + "lib/consent/withdrawal.ts": 1, + "lib/crypto.ts": 1, + "lib/crypto/encrypted-columns.ts": 20, + "lib/crypto/encryption-corpus.ts": 1, + "lib/crypto/note-cipher.ts": 4, + "lib/custom-metrics/custom-metric-store.ts": 1, + "lib/cycle/__tests__/custom-symptoms.test.ts": 1, + "lib/cycle/__tests__/engine-adapter.test.ts": 1, + "lib/cycle/__tests__/gate.test.ts": 2, + "lib/cycle/__tests__/resolve-symptom-ids.test.ts": 1, + "lib/cycle/custom-symptoms-shared.ts": 1, + "lib/cycle/custom-symptoms.ts": 1, + "lib/cycle/day-log-write.ts": 1, + "lib/cycle/gate.ts": 1, + "lib/cycle/import-accumulator.ts": 1, + "lib/cycle/phase-copy.ts": 1, + "lib/cycle/phase-crosstab.ts": 1, + "lib/cycle/symptom-phase.ts": 1, + "lib/daily/__tests__/digest.test.ts": 1, + "lib/daily/__tests__/load-digest.test.ts": 1, + "lib/daily/__tests__/morning-refresh-trigger.test.ts": 1, + "lib/daily/digest.ts": 7, + "lib/daily/load-digest.ts": 1, + "lib/daily/morning-refresh-trigger.ts": 1, + "lib/dashboard-layout.ts": 50, + "lib/dashboard/__tests__/batch-chart-types.test.ts": 1, + "lib/dashboard/__tests__/meds-today.test.ts": 1, + "lib/dashboard/__tests__/score-narrative.test.ts": 1, + "lib/dashboard/__tests__/score-rings.test.ts": 1, + "lib/dashboard/__tests__/snapshot-flag.test.ts": 1, + "lib/dashboard/__tests__/snapshot.test.ts": 15, + "lib/dashboard/__tests__/target-bands.test.ts": 2, + "lib/dashboard/bands.ts": 2, + "lib/dashboard/batch-chart-types.ts": 1, + "lib/dashboard/meds-today.ts": 3, + "lib/dashboard/score-narrative.ts": 1, + "lib/dashboard/score-rings.ts": 2, + "lib/dashboard/snapshot-read.ts": 1, + "lib/dashboard/snapshot.ts": 56, + "lib/dashboard/summary-reads.ts": 5, + "lib/dashboard/widget-modules.ts": 1, + "lib/data-wipe/wipe-plan.ts": 11, + "lib/day/sections.ts": 1, + "lib/day/values.ts": 1, + "lib/day/visit-preparation.ts": 1, + "lib/day/wire-schemas.ts": 1, + "lib/db-compat.ts": 1, + "lib/devices/__tests__/revoke.test.ts": 1, + "lib/devices/revoke.ts": 1, + "lib/doctor-report-helpers.ts": 1, + "lib/doctor-report-pdf-core.ts": 4, + "lib/doctor-report-pdf/clinical-records-notes-section.ts": 6, + "lib/doctor-report-pdf/header-profile-section.ts": 3, + "lib/doctor-report-pdf/measurements-charts-section.ts": 3, + "lib/doctor-report-pdf/medication-mood-wellness-section.ts": 7, + "lib/doctor-report-types.ts": 21, + "lib/doctor-report/__tests__/leaf-gating-sweep.test.ts": 1, + "lib/doctor-report/clinical-records.ts": 2, + "lib/doctor-report/collect.ts": 4, + "lib/documents/__tests__/commit.test.ts": 1, + "lib/documents/__tests__/extract.test.ts": 1, + "lib/documents/__tests__/fenced-chat-module-graph.test.ts": 1, + "lib/documents/__tests__/fenced-chat-pipeline.test.ts": 1, + "lib/documents/__tests__/search-synonyms.test.ts": 1, + "lib/documents/ai-route-support.ts": 2, + "lib/documents/ai-runs/types.ts": 1, + "lib/documents/assist.ts": 1, + "lib/documents/attach-validate.ts": 1, + "lib/documents/auto-stage-labs.ts": 1, + "lib/documents/commit.ts": 3, + "lib/documents/content-index.ts": 7, + "lib/documents/describe.ts": 1, + "lib/documents/document-chat-prompt.ts": 1, + "lib/documents/document-settings.ts": 1, + "lib/documents/extract.ts": 2, + "lib/documents/fenced-chat.ts": 3, + "lib/documents/index-document.ts": 3, + "lib/documents/native-canvas-support.ts": 1, + "lib/documents/search-synonyms.ts": 1, + "lib/documents/source-key.ts": 1, + "lib/documents/store.ts": 2, + "lib/environment/__tests__/air-quality-history.test.ts": 1, + "lib/environment/__tests__/coarse-location.test.ts": 1, + "lib/environment/__tests__/day-flags.test.ts": 1, + "lib/environment/__tests__/location-cipher.test.ts": 1, + "lib/environment/__tests__/open-meteo-air-quality.test.ts": 1, + "lib/environment/__tests__/request-budget.test.ts": 1, + "lib/environment/__tests__/service-air-quality.test.ts": 1, + "lib/environment/__tests__/service.test.ts": 1, + "lib/environment/air-quality-history.ts": 1, + "lib/environment/fields.ts": 4, + "lib/environment/open-meteo-air-quality.ts": 1, + "lib/environment/open-meteo.ts": 2, + "lib/environment/service.ts": 2, + "lib/export.ts": 4, + "lib/export/__tests__/coach-backup-title.test.ts": 1, + "lib/export/__tests__/full-backup-payload.test.ts": 3, + "lib/export/__tests__/intraday-profile-backup.test.ts": 1, + "lib/export/__tests__/profile-backup-decrypt-failures.test.ts": 1, + "lib/export/__tests__/records-backup.test.ts": 2, + "lib/export/__tests__/restore-backup-round-trip.test.ts": 1, + "lib/export/account-settings-backup.ts": 2, + "lib/export/backup-plan.ts": 13, + "lib/export/coach-backup.ts": 13, + "lib/export/full-backup-payload.ts": 8, + "lib/export/intraday-profile-backup.ts": 1, + "lib/export/passphrase-archive.ts": 1, + "lib/export/profile-backup.ts": 10, + "lib/export/records-backup.ts": 4, + "lib/export/reminders-backup.ts": 4, + "lib/export/restore-backup.ts": 17, + "lib/export/visits-backup.ts": 5, + "lib/feature-flags/__tests__/index.test.ts": 2, + "lib/fhir/build-bundle.ts": 10, + "lib/fhir/illness-snomed.ts": 1, + "lib/fhir/lab-loinc.ts": 1, + "lib/fhir/lab-qualitative.ts": 1, + "lib/fhir/loinc-map.ts": 2, + "lib/fhir/records.ts": 1, + "lib/fhir/resources/anamnesis.ts": 1, + "lib/fhir/resources/common.ts": 1, + "lib/fhir/resources/conditions.ts": 1, + "lib/fhir/resources/cycle.ts": 1, + "lib/fhir/resources/index.ts": 1, + "lib/fhir/resources/labs.ts": 1, + "lib/fhir/resources/medications.ts": 2, + "lib/fhir/resources/observation.ts": 2, + "lib/fhir/rest.ts": 1, + "lib/fhir/types.ts": 18, + "lib/fitbit/__tests__/poll-cohort.test.ts": 1, + "lib/fitbit/__tests__/sync-user-fitbit.test.ts": 1, + "lib/fitbit/client.ts": 1, + "lib/fitbit/sync.ts": 1, + "lib/format-locale.ts": 1, + "lib/format.ts": 4, + "lib/gamification/__tests__/achievements.test.ts": 1, + "lib/gamification/__tests__/care-metrics.test.ts": 1, + "lib/gamification/__tests__/expansion-metrics.test.ts": 1, + "lib/gamification/achievements-result.ts": 31, + "lib/gamification/achievements.ts": 14, + "lib/gamification/care-metrics.ts": 1, + "lib/gamification/expansion-metrics.ts": 13, + "lib/geo.ts": 9, + "lib/google-health/__tests__/date-filter-fallback.test.ts": 1, + "lib/google-health/__tests__/sync-intraday-daily-wide.test.ts": 1, + "lib/google-health/__tests__/sync-intraday-overlap.test.ts": 1, + "lib/google-health/__tests__/sync-sleep-morning-refresh.test.ts": 1, + "lib/google-health/client.ts": 1, + "lib/google-health/sync-core.ts": 2, + "lib/google-health/sync-metrics.ts": 1, + "lib/i18n/__tests__/relative-time-updated.test.ts": 2, + "lib/i18n/config.ts": 1, + "lib/i18n/context.tsx": 1, + "lib/i18n/relative-time.ts": 4, + "lib/i18n/shared-resolve.ts": 1, + "lib/i18n/source-labels.ts": 1, + "lib/idempotency.ts": 2, + "lib/illness/__tests__/correlation-read.test.ts": 1, + "lib/illness/__tests__/correlation.test.ts": 1, + "lib/illness/__tests__/day-log-write.test.ts": 1, + "lib/illness/__tests__/episode-window-invariant.test.ts": 1, + "lib/illness/__tests__/gate.test.ts": 1, + "lib/illness/__tests__/red-flag-notify.test.ts": 1, + "lib/illness/__tests__/rest-mode.test.ts": 1, + "lib/illness/__tests__/retrospective.test.ts": 1, + "lib/illness/__tests__/safety-floor-notify.test.ts": 1, + "lib/illness/__tests__/safety-floor-sustained.test.ts": 1, + "lib/illness/__tests__/safety-floors.test.ts": 1, + "lib/illness/day-log-write.ts": 2, + "lib/illness/red-flag-notify.ts": 1, + "lib/import/__tests__/unzip-export-xml.test.ts": 2, + "lib/import/unzip-export-xml.ts": 2, + "lib/insights-layout-reorder.ts": 2, + "lib/insights-layout.ts": 15, + "lib/insights/__tests__/background-reasoning-wire.test.ts": 1, + "lib/insights/__tests__/biomarker-status.test.ts": 1, + "lib/insights/__tests__/blood-pressure-status.test.ts": 2, + "lib/insights/__tests__/bmi-status.test.ts": 4, + "lib/insights/__tests__/briefing-failure-marker.test.ts": 1, + "lib/insights/__tests__/chart-tokens.test.ts": 4, + "lib/insights/__tests__/coach-launch-context.test.tsx": 4, + "lib/insights/__tests__/coach-result-clipboard.test.ts": 1, + "lib/insights/__tests__/comparison-snapshot-window.test.ts": 1, + "lib/insights/__tests__/comprehensive-aggregator.test.ts": 18, + "lib/insights/__tests__/comprehensive-briefing-grounding-disposal.test.ts": 1, + "lib/insights/__tests__/comprehensive-briefing-reroll.test.ts": 4, + "lib/insights/__tests__/comprehensive-budget-refusal.test.ts": 1, + "lib/insights/__tests__/comprehensive-generate-force.test.ts": 4, + "lib/insights/__tests__/comprehensive-json-retry.test.ts": 4, + "lib/insights/__tests__/comprehensive-outbound-repair.test.ts": 3, + "lib/insights/__tests__/comprehensive-plan-progress.test.ts": 3, + "lib/insights/__tests__/comprehensive-timeout-honored.test.ts": 2, + "lib/insights/__tests__/comprehensive-warm-timeout.test.ts": 2, + "lib/insights/__tests__/correlation-channel-series-rollup-swap.test.ts": 1, + "lib/insights/__tests__/correlation-extended-channels.test.ts": 1, + "lib/insights/__tests__/correlations.test.ts": 4, + "lib/insights/__tests__/ecg-decimate.test.ts": 1, + "lib/insights/__tests__/features.test.ts": 1, + "lib/insights/__tests__/general-status.test.ts": 1, + "lib/insights/__tests__/glp1-plateau.test.ts": 1, + "lib/insights/__tests__/invalidate-status-insights.test.ts": 4, + "lib/insights/__tests__/measurement-diversity.test.ts": 1, + "lib/insights/__tests__/medication-compliance-status.test.ts": 1, + "lib/insights/__tests__/metric-availability.test.ts": 2, + "lib/insights/__tests__/metric-correlation-context.test.ts": 1, + "lib/insights/__tests__/metric-status.test.ts": 1, + "lib/insights/__tests__/mood-aggregates.test.ts": 2, + "lib/insights/__tests__/mood-staleness.test.ts": 1, + "lib/insights/__tests__/mood-status.test.ts": 1, + "lib/insights/__tests__/no-key-fallbacks.test.ts": 1, + "lib/insights/__tests__/payload-budget.test.ts": 2, + "lib/insights/__tests__/prompt-soft-delete-filter.test.ts": 2, + "lib/insights/__tests__/pulse-status-timeout.test.ts": 4, + "lib/insights/__tests__/pulse-status.test.ts": 2, + "lib/insights/__tests__/recovery-score.test.ts": 2, + "lib/insights/__tests__/sanitize.test.ts": 1, + "lib/insights/__tests__/score-history.test.ts": 1, + "lib/insights/__tests__/score-row.test.ts": 2, + "lib/insights/__tests__/snapshot-hash.test.ts": 1, + "lib/insights/__tests__/status-cache-delegated.test.ts": 1, + "lib/insights/__tests__/status-cache.test.ts": 2, + "lib/insights/__tests__/status-provider-budget.test.ts": 1, + "lib/insights/__tests__/strain-score.test.ts": 1, + "lib/insights/__tests__/streak-detector.test.ts": 1, + "lib/insights/__tests__/stress-score.test.ts": 1, + "lib/insights/__tests__/sub-page-manager-group.test.ts": 1, + "lib/insights/__tests__/sub-page-metric-reverse.test.ts": 1, + "lib/insights/__tests__/trend-chart-select.test.ts": 3, + "lib/insights/__tests__/trend-descriptor.test.ts": 1, + "lib/insights/__tests__/values-back-link.test.ts": 1, + "lib/insights/__tests__/weight-status.test.ts": 5, + "lib/insights/assessment-context.ts": 1, + "lib/insights/biomarker-status.ts": 1, + "lib/insights/blood-pressure-status.ts": 18, + "lib/insights/bmi-status.ts": 14, + "lib/insights/breathing-screening.ts": 1, + "lib/insights/briefing-failure-marker.ts": 2, + "lib/insights/briefing-payload.ts": 1, + "lib/insights/briefing-provider.ts": 4, + "lib/insights/briefing-short-form.ts": 1, + "lib/insights/cached-text-cipher.ts": 1, + "lib/insights/chart-tokens.ts": 24, + "lib/insights/coach-launch-context.tsx": 10, + "lib/insights/comprehensive-aggregator.ts": 27, + "lib/insights/comprehensive-generate.ts": 48, + "lib/insights/correlation-channel-series.ts": 16, + "lib/insights/correlation-discovery.ts": 30, + "lib/insights/correlation-series-builders.ts": 1, + "lib/insights/correlations.ts": 4, + "lib/insights/derived-briefing.ts": 1, + "lib/insights/derived/__tests__/adherence-storyline-engine-parity.test.ts": 1, + "lib/insights/derived/__tests__/coach-read-locale.test.ts": 1, + "lib/insights/derived/__tests__/coach-read.test.ts": 1, + "lib/insights/derived/__tests__/coincident-deviation.test.ts": 1, + "lib/insights/derived/__tests__/derived-assessment-ai.test.ts": 1, + "lib/insights/derived/__tests__/derived-assessment.test.ts": 2, + "lib/insights/derived/__tests__/health-score-coverage-contract.test.ts": 1, + "lib/insights/derived/__tests__/score-grounding.test.ts": 1, + "lib/insights/derived/__tests__/sleep-rhythm.test.ts": 1, + "lib/insights/derived/__tests__/sleep-score-history.test.ts": 1, + "lib/insights/derived/__tests__/wellness-scores.test.ts": 1, + "lib/insights/derived/adherence-storyline.ts": 4, + "lib/insights/derived/baseline.ts": 3, + "lib/insights/derived/bmi.ts": 1, + "lib/insights/derived/changepoint.ts": 3, + "lib/insights/derived/chronotype.ts": 1, + "lib/insights/derived/coach-read-shape.ts": 1, + "lib/insights/derived/coach-read.ts": 1, + "lib/insights/derived/coincident-deviation.ts": 4, + "lib/insights/derived/coverage.ts": 1, + "lib/insights/derived/derived-assessment-ai.ts": 10, + "lib/insights/derived/derived-assessment.ts": 6, + "lib/insights/derived/dispatch.ts": 2, + "lib/insights/derived/fitness-age.ts": 1, + "lib/insights/derived/hrv-balance.ts": 1, + "lib/insights/derived/index.ts": 1, + "lib/insights/derived/norms.ts": 2, + "lib/insights/derived/readiness.ts": 2, + "lib/insights/derived/recovery-resolve.ts": 2, + "lib/insights/derived/registry.ts": 8, + "lib/insights/derived/same-time-baseline.ts": 1, + "lib/insights/derived/score-grounding.ts": 2, + "lib/insights/derived/signal-trust.ts": 1, + "lib/insights/derived/six-minute-walk.ts": 1, + "lib/insights/derived/sleep-debt.ts": 1, + "lib/insights/derived/sleep-rhythm.ts": 6, + "lib/insights/derived/sleep-score.ts": 2, + "lib/insights/derived/trajectory.ts": 1, + "lib/insights/derived/types.ts": 1, + "lib/insights/derived/vascular-age.ts": 1, + "lib/insights/derived/wellness-scores.ts": 3, + "lib/insights/discovery-matrix.ts": 4, + "lib/insights/ecg-decimate.ts": 1, + "lib/insights/exclude-filter.ts": 1, + "lib/insights/feature-blocks.ts": 5, + "lib/insights/feature-cache.ts": 2, + "lib/insights/features.ts": 38, + "lib/insights/general-status.ts": 14, + "lib/insights/glp1-plateau.ts": 2, + "lib/insights/graded-series.ts": 3, + "lib/insights/health-status.ts": 3, + "lib/insights/illness-cycle-briefing.ts": 1, + "lib/insights/labs-changes.ts": 1, + "lib/insights/measurement-diversity.ts": 1, + "lib/insights/medication-compliance-status.ts": 20, + "lib/insights/memory.ts": 2, + "lib/insights/metric-availability.ts": 11, + "lib/insights/metric-catalog.ts": 2, + "lib/insights/metric-correlation-context.ts": 1, + "lib/insights/metric-signal.ts": 1, + "lib/insights/metric-status-registry.ts": 5, + "lib/insights/metric-status.ts": 11, + "lib/insights/mood-aggregates.ts": 37, + "lib/insights/mood-crosstab.ts": 2, + "lib/insights/mood-dimension-series.ts": 1, + "lib/insights/mood-narratives.ts": 3, + "lib/insights/mood-status.ts": 25, + "lib/insights/narrative/__tests__/period-narrative-db.test.ts": 2, + "lib/insights/narrative/__tests__/period-narrative-deterministic.test.ts": 1, + "lib/insights/narrative/__tests__/period-narrative-generate.test.ts": 1, + "lib/insights/narrative/narrative-grounding.ts": 1, + "lib/insights/narrative/period-narrative-deterministic.ts": 2, + "lib/insights/narrative/period-narrative-generate.ts": 5, + "lib/insights/narrative/period-narrative.ts": 2, + "lib/insights/no-key-fallbacks.ts": 3, + "lib/insights/pulse-status.ts": 13, + "lib/insights/recovery-score.ts": 2, + "lib/insights/sanitize.ts": 1, + "lib/insights/score-row.ts": 2, + "lib/insights/signals-of-day.ts": 4, + "lib/insights/snapshot-hash.ts": 1, + "lib/insights/status-batch.ts": 1, + "lib/insights/status-cache.ts": 7, + "lib/insights/status-card-generation.ts": 1, + "lib/insights/status-invalidation.ts": 6, + "lib/insights/status-provider.ts": 9, + "lib/insights/status-shared.ts": 1, + "lib/insights/strain-score.ts": 8, + "lib/insights/streak-detector.ts": 1, + "lib/insights/stress-score.ts": 1, + "lib/insights/sub-page-metric.ts": 26, + "lib/insights/timeout-fallback.ts": 2, + "lib/insights/training-load.ts": 1, + "lib/insights/trend-chart-select.ts": 1, + "lib/insights/trend-descriptor.ts": 1, + "lib/insights/trend-sentiment.ts": 1, + "lib/insights/values-back-link.ts": 1, + "lib/insights/weight-status.ts": 15, + "lib/insights/with-timeout.ts": 1, + "lib/integrations/__tests__/admin-alert.test.ts": 4, + "lib/integrations/__tests__/status.test.ts": 11, + "lib/integrations/healthkit-type-sync.ts": 4, + "lib/integrations/http-status-classifier.ts": 1, + "lib/integrations/metric-freshness.ts": 1, + "lib/integrations/status.ts": 19, + "lib/intl/__tests__/formatter-cache.test.ts": 1, + "lib/jobs/__tests__/apple-health-import-reconcile.test.ts": 2, + "lib/jobs/__tests__/apple-health-import-send-policy.test.ts": 1, + "lib/jobs/__tests__/coach-memory-refresh-queue.test.ts": 2, + "lib/jobs/__tests__/coach-message-cleanup-queue.test.ts": 1, + "lib/jobs/__tests__/coach-nudge.test.ts": 3, + "lib/jobs/__tests__/coach-reminder-sweep.test.ts": 1, + "lib/jobs/__tests__/cumulative-pr-rederive-queue.test.ts": 1, + "lib/jobs/__tests__/cycle-reminder-queue.test.ts": 2, + "lib/jobs/__tests__/cycle-reminder.test.ts": 2, + "lib/jobs/__tests__/data-arrival-queue.test.ts": 1, + "lib/jobs/__tests__/drain-cumulative-queue.test.ts": 4, + "lib/jobs/__tests__/feedback-aggregator.test.ts": 2, + "lib/jobs/__tests__/fitbit-backfill.test.ts": 1, + "lib/jobs/__tests__/fitbit-queues.test.ts": 2, + "lib/jobs/__tests__/geo-backfill.test.ts": 3, + "lib/jobs/__tests__/google-health-queues.test.ts": 1, + "lib/jobs/__tests__/google-health-sleep-repair.test.ts": 1, + "lib/jobs/__tests__/hero-line-json-envelope.test.ts": 1, + "lib/jobs/__tests__/insight-pregenerate-retry-enqueue.test.ts": 1, + "lib/jobs/__tests__/insight-pregenerate.test.ts": 16, + "lib/jobs/__tests__/insight-status-generate.test.ts": 2, + "lib/jobs/__tests__/intake-auto-skip.test.ts": 3, + "lib/jobs/__tests__/intake-auto-take.test.ts": 1, + "lib/jobs/__tests__/intake-slot-dedup-queue.test.ts": 6, + "lib/jobs/__tests__/job-run-report.test.ts": 1, + "lib/jobs/__tests__/mean-consolidation-queue.test.ts": 2, + "lib/jobs/__tests__/measurement-reminder-queue.test.ts": 2, + "lib/jobs/__tests__/measurement-reminder.test.ts": 6, + "lib/jobs/__tests__/measurement-tombstone-cleanup.test.ts": 2, + "lib/jobs/__tests__/medication-inventory-expire.test.ts": 1, + "lib/jobs/__tests__/medication-low-stock.test.ts": 2, + "lib/jobs/__tests__/medication-reminder-check.test.ts": 2, + "lib/jobs/__tests__/mood-reminder.test.ts": 2, + "lib/jobs/__tests__/nightscout-queues.test.ts": 3, + "lib/jobs/__tests__/period-narrative-warm.test.ts": 1, + "lib/jobs/__tests__/reaction-line-degradation.test.ts": 2, + "lib/jobs/__tests__/recovery-score-queue.test.ts": 2, + "lib/jobs/__tests__/recovery-score.test.ts": 1, + "lib/jobs/__tests__/reminder-phases.test.ts": 1, + "lib/jobs/__tests__/reminder-satisfy.test.ts": 3, + "lib/jobs/__tests__/report-worker-error.test.ts": 1, + "lib/jobs/__tests__/run-job.test.ts": 1, + "lib/jobs/__tests__/sleep-timeline-backfill.test.ts": 1, + "lib/jobs/__tests__/sleep-timeline-queues.test.ts": 2, + "lib/jobs/__tests__/status-cron-candidates.test.ts": 2, + "lib/jobs/__tests__/step-consolidation-repair-queue.test.ts": 1, + "lib/jobs/__tests__/stress-strain-retention-queue.test.ts": 2, + "lib/jobs/__tests__/tls-pin-monitor-queue.test.ts": 2, + "lib/jobs/__tests__/whoop-backfill.test.ts": 1, + "lib/jobs/__tests__/whoop-queues.test.ts": 2, + "lib/jobs/__tests__/whoop-resource-sync.test.ts": 1, + "lib/jobs/__tests__/withings-queues.test.ts": 2, + "lib/jobs/__tests__/workout-insight-queue.test.ts": 1, + "lib/jobs/apple-health-import-worker.ts": 5, + "lib/jobs/coach-message-cleanup.ts": 1, + "lib/jobs/coach-nudge-ai.ts": 3, + "lib/jobs/coach-nudge.ts": 17, + "lib/jobs/coach-plan-review.ts": 1, + "lib/jobs/coach-reminder-sweep.ts": 2, + "lib/jobs/cycle-reminder.ts": 2, + "lib/jobs/data-arrival.ts": 2, + "lib/jobs/dawarich-sync.ts": 1, + "lib/jobs/dense-intraday-hourly-rebuild.ts": 1, + "lib/jobs/dense-intraday-retention.ts": 2, + "lib/jobs/document-ai-run.ts": 1, + "lib/jobs/document-content-index-backfill.ts": 2, + "lib/jobs/document-index.ts": 1, + "lib/jobs/document-purge.ts": 1, + "lib/jobs/document-summary.ts": 1, + "lib/jobs/document-thumbnail-backfill.ts": 1, + "lib/jobs/encryption-key-rotate.ts": 1, + "lib/jobs/environment-air-quality-history.ts": 1, + "lib/jobs/environment-fetch.ts": 2, + "lib/jobs/feedback-aggregator.ts": 6, + "lib/jobs/fitbit-backfill.ts": 1, + "lib/jobs/fitbit-oauth-state-cleanup.ts": 1, + "lib/jobs/free-text-encryption-backfill.ts": 8, + "lib/jobs/geo-backfill.ts": 4, + "lib/jobs/google-health-backfill.ts": 1, + "lib/jobs/google-health-oauth-state-cleanup.ts": 1, + "lib/jobs/google-health-sleep-repair.ts": 1, + "lib/jobs/insight-pregenerate-shared.ts": 1, + "lib/jobs/insight-pregenerate.ts": 39, + "lib/jobs/insight-status-generate-shared.ts": 3, + "lib/jobs/insight-status-generate.ts": 3, + "lib/jobs/intake-auto-skip.ts": 8, + "lib/jobs/intake-auto-take.ts": 1, + "lib/jobs/integration-status-null-probe.ts": 3, + "lib/jobs/job-outcome.ts": 11, + "lib/jobs/lab-biomarker-backfill.ts": 2, + "lib/jobs/mean-consolidation.ts": 1, + "lib/jobs/measurement-fold-repair.ts": 1, + "lib/jobs/measurement-reminder.ts": 14, + "lib/jobs/med-notes-encryption-backfill.ts": 2, + "lib/jobs/medication-intake-import.ts": 1, + "lib/jobs/medication-inventory-expire.ts": 1, + "lib/jobs/medication-low-stock.ts": 13, + "lib/jobs/mood-reminder.ts": 4, + "lib/jobs/note-encryption-backfill.ts": 2, + "lib/jobs/oidc-handoff-cleanup.ts": 1, + "lib/jobs/oura-backfill.ts": 1, + "lib/jobs/period-narrative-shared.ts": 1, + "lib/jobs/period-narrative-warm.ts": 1, + "lib/jobs/pr-detection.ts": 2, + "lib/jobs/reaction-line.ts": 5, + "lib/jobs/recovery-score.ts": 1, + "lib/jobs/reminder-phases.ts": 1, + "lib/jobs/reminder-satisfy.ts": 1, + "lib/jobs/reminder-worker.ts": 4, + "lib/jobs/reminder/__tests__/cleanup-handlers-visibility.test.ts": 1, + "lib/jobs/reminder/__tests__/queue-retention.test.ts": 1, + "lib/jobs/reminder/cleanup-handlers.ts": 7, + "lib/jobs/reminder/fitbit-sync.ts": 2, + "lib/jobs/reminder/google-health-sync.ts": 1, + "lib/jobs/reminder/insights-handlers.ts": 3, + "lib/jobs/reminder/medication-maintenance.ts": 3, + "lib/jobs/reminder/medication-reminder-check.ts": 13, + "lib/jobs/reminder/mood-cycle-checks.ts": 3, + "lib/jobs/reminder/ops-handlers.ts": 3, + "lib/jobs/reminder/oura-sync.ts": 1, + "lib/jobs/reminder/polar-sync.ts": 2, + "lib/jobs/reminder/poll-cohort.ts": 2, + "lib/jobs/reminder/register-integration-sync.ts": 63, + "lib/jobs/reminder/register-maintenance.ts": 114, + "lib/jobs/reminder/register-reminders.ts": 15, + "lib/jobs/reminder/register-rollup.ts": 37, + "lib/jobs/reminder/register-status.ts": 38, + "lib/jobs/reminder/registrar-shared.ts": 2, + "lib/jobs/reminder/strava-sync.ts": 1, + "lib/jobs/reminder/whoop-sync.ts": 3, + "lib/jobs/reminder/withings-sync.ts": 5, + "lib/jobs/reminder/worker-lifecycle.ts": 2, + "lib/jobs/sleep-timeline-backfill.ts": 1, + "lib/jobs/step-consolidation-repair.ts": 1, + "lib/jobs/step-consolidation.ts": 1, + "lib/jobs/strain-score.ts": 1, + "lib/jobs/strava-backfill.ts": 1, + "lib/jobs/stress-score.ts": 1, + "lib/jobs/subsystem-surface.ts": 2, + "lib/jobs/whoop-backfill.ts": 1, + "lib/jobs/whoop-oauth-state-cleanup.ts": 2, + "lib/labs/__tests__/inverted-range.test.ts": 1, + "lib/labs/__tests__/note-update.test.ts": 1, + "lib/labs/biomarker-catalog.ts": 10, + "lib/labs/biomarker-store.ts": 2, + "lib/labs/homa-ir.ts": 1, + "lib/labs/lab-trend.ts": 1, + "lib/labs/local-ocr.ts": 1, + "lib/labs/note-update.ts": 1, + "lib/labs/ocr-capability.ts": 1, + "lib/labs/ocr-extract.ts": 2, + "lib/labs/ocr-upload.ts": 1, + "lib/labs/reference-range.ts": 1, + "lib/labs/serialise.ts": 2, + "lib/labs/store.ts": 1, + "lib/learn-links.ts": 1, + "lib/links/__tests__/link-surface-guard.test.ts": 1, + "lib/links/link-service.ts": 3, + "lib/logging/__tests__/redact.test.ts": 7, + "lib/logging/__tests__/signal.test.ts": 1, + "lib/logging/context.ts": 2, + "lib/logging/event-builder.ts": 5, + "lib/logging/redact.ts": 8, + "lib/logging/types.ts": 4, + "lib/managed-profiles/__tests__/handover-pure.test.ts": 1, + "lib/managed-profiles/claim-cookie.ts": 1, + "lib/managed-profiles/claim-gate.ts": 2, + "lib/managed-profiles/claim-sso.ts": 1, + "lib/managed-profiles/guardian-list.ts": 1, + "lib/managed-profiles/handover-access.ts": 1, + "lib/managed-profiles/handover-notify.ts": 1, + "lib/managed-profiles/handover.ts": 4, + "lib/managed-profiles/lifecycle.ts": 1, + "lib/mcp/__tests__/doctor-report-module-gate.test.ts": 1, + "lib/mcp/__tests__/resource-templates.test.ts": 2, + "lib/mcp/__tests__/resources.test.ts": 2, + "lib/mcp/__tests__/rich-reads-module-gate.test.ts": 1, + "lib/mcp/__tests__/rich-reads.test.ts": 4, + "lib/mcp/__tests__/search-fetch.test.ts": 4, + "lib/mcp/__tests__/tools.test.ts": 28, + "lib/mcp/doctor-visit-summary.ts": 1, + "lib/mcp/prompts.ts": 1, + "lib/mcp/record-search.ts": 1, + "lib/mcp/resources.ts": 6, + "lib/mcp/rich-reads.ts": 8, + "lib/mcp/tools.ts": 18, + "lib/mcp/writes.ts": 2, + "lib/measurement-reminders/__tests__/holds-open.test.ts": 1, + "lib/measurement-reminders/__tests__/resolve.test.ts": 2, + "lib/measurement-reminders/__tests__/satisfy.test.ts": 3, + "lib/measurement-reminders/__tests__/scheduling.test.ts": 2, + "lib/measurement-reminders/dto.ts": 4, + "lib/measurement-reminders/due-day.ts": 2, + "lib/measurement-reminders/edit-body.ts": 1, + "lib/measurement-reminders/holds-open.ts": 1, + "lib/measurement-reminders/resolve.ts": 3, + "lib/measurement-reminders/satisfy.ts": 4, + "lib/measurement-reminders/scheduling.ts": 4, + "lib/measurement-reminders/screening.ts": 1, + "lib/measurements/__tests__/aggregated-hr-bucket.test.ts": 2, + "lib/measurements/__tests__/apple-health-mapping.test.ts": 22, + "lib/measurements/__tests__/consolidate-daily-mean.test.ts": 1, + "lib/measurements/__tests__/consolidate-legacy-steps.test.ts": 1, + "lib/measurements/__tests__/cross-source-merge.test.ts": 1, + "lib/measurements/__tests__/cumulative-day-sum.test.ts": 3, + "lib/measurements/__tests__/daily-series-read-tiered.test.ts": 2, + "lib/measurements/__tests__/dense-intraday-drain-exemption.test.ts": 1, + "lib/measurements/__tests__/dense-intraday-hourly-fold.test.ts": 1, + "lib/measurements/__tests__/dense-intraday-hourly-rebuild.test.ts": 1, + "lib/measurements/__tests__/dense-intraday-retention.test.ts": 3, + "lib/measurements/__tests__/dense-intraday-spo2-summary.test.ts": 2, + "lib/measurements/__tests__/display-transform.test.ts": 1, + "lib/measurements/__tests__/drain-per-sample-cumulative.test.ts": 7, + "lib/measurements/__tests__/folded-window.test.ts": 1, + "lib/measurements/__tests__/telegram-chat-id-unique.test.ts": 1, + "lib/measurements/apple-health-mapping.ts": 28, + "lib/measurements/categories.ts": 7, + "lib/measurements/consolidate-daily-mean.ts": 2, + "lib/measurements/consolidate-legacy-steps.ts": 1, + "lib/measurements/create-from-telegram.ts": 1, + "lib/measurements/cross-source-merge.ts": 1, + "lib/measurements/cumulative-day-sum.ts": 6, + "lib/measurements/daily-series-read.ts": 2, + "lib/measurements/dense-intraday-hourly-rebuild.ts": 1, + "lib/measurements/dense-intraday-retention.ts": 4, + "lib/measurements/display-transform.ts": 6, + "lib/measurements/drain-per-sample-cumulative.ts": 8, + "lib/measurements/import-apple-health-export.ts": 5, + "lib/measurements/intraday-cumulative-profile.ts": 3, + "lib/measurements/last-used-type.ts": 1, + "lib/measurements/list/day-grouped.ts": 1, + "lib/measurements/notes-limit.ts": 1, + "lib/measurements/pick-canonical-workout-rows.ts": 1, + "lib/measurements/range-aggregation.ts": 1, + "lib/measurements/series-batch-read.ts": 1, + "lib/measurements/sources.ts": 12, + "lib/measurements/unit-aliases.ts": 1, + "lib/measurements/unit-map.ts": 1, + "lib/medication-list-layout.ts": 1, + "lib/medications/__tests__/auto-mark-taken.test.ts": 1, + "lib/medications/__tests__/auto-take-window.test.ts": 1, + "lib/medications/__tests__/category-label.test.ts": 1, + "lib/medications/__tests__/intake-slot-dedup-converge.test.ts": 1, + "lib/medications/__tests__/intake-slot-dedup-inventory.test.ts": 1, + "lib/medications/__tests__/intake-slot-dedup-isolation.test.ts": 2, + "lib/medications/__tests__/intake-slot-dedup-pin.test.ts": 1, + "lib/medications/__tests__/lifecycle.test.ts": 1, + "lib/medications/__tests__/medication-order.test.ts": 1, + "lib/medications/__tests__/supply-runway.test.ts": 2, + "lib/medications/__tests__/window-status.test.ts": 2, + "lib/medications/auto-mark-taken.ts": 1, + "lib/medications/auto-take.ts": 1, + "lib/medications/category-filter.ts": 1, + "lib/medications/category-label.ts": 2, + "lib/medications/compliance-payload.ts": 12, + "lib/medications/course-window.ts": 1, + "lib/medications/courses.ts": 3, + "lib/medications/dose-string.ts": 1, + "lib/medications/efficacy/build-efficacy.ts": 2, + "lib/medications/glp1-knowledge.ts": 1, + "lib/medications/glp1-pk.ts": 1, + "lib/medications/ingredients/__tests__/composition.test.ts": 1, + "lib/medications/ingredients/__tests__/label-extract.test.ts": 1, + "lib/medications/ingredients/__tests__/recompute.test.ts": 1, + "lib/medications/ingredients/composition.ts": 1, + "lib/medications/ingredients/label-extract.ts": 1, + "lib/medications/ingredients/recompute-job.ts": 1, + "lib/medications/ingredients/recompute.ts": 1, + "lib/medications/injection-site-write.ts": 1, + "lib/medications/injection-sites.ts": 6, + "lib/medications/intake-actionable.ts": 1, + "lib/medications/intake-slot-dedup.ts": 18, + "lib/medications/intake-tracking.ts": 1, + "lib/medications/inventory/__tests__/consumption.test.ts": 1, + "lib/medications/inventory/__tests__/summary.test.ts": 2, + "lib/medications/inventory/consumption.ts": 7, + "lib/medications/inventory/service.ts": 6, + "lib/medications/inventory/state-machine.ts": 2, + "lib/medications/inventory/summary.ts": 4, + "lib/medications/lifecycle.ts": 3, + "lib/medications/list-read.ts": 26, + "lib/medications/med-target-map.ts": 1, + "lib/medications/medication-order.ts": 1, + "lib/medications/outstanding-doses.ts": 2, + "lib/medications/route-guards.ts": 1, + "lib/medications/schedule-units-dto.ts": 1, + "lib/medications/scheduled-doses.ts": 1, + "lib/medications/scheduling/__tests__/apply-canonical-slot-write.test.ts": 1, + "lib/medications/scheduling/__tests__/attribute-intake.test.ts": 1, + "lib/medications/scheduling/__tests__/attribution.test.ts": 2, + "lib/medications/scheduling/__tests__/band-minter.test.ts": 1, + "lib/medications/scheduling/__tests__/cadence.test.ts": 2, + "lib/medications/scheduling/__tests__/compliance.test.ts": 2, + "lib/medications/scheduling/__tests__/dose-history.test.ts": 5, + "lib/medications/scheduling/__tests__/next-due.test.ts": 6, + "lib/medications/scheduling/__tests__/pin-conflict.test.ts": 1, + "lib/medications/scheduling/__tests__/recurrence.test.ts": 6, + "lib/medications/scheduling/__tests__/resolve-slot-by-band-eras.test.ts": 1, + "lib/medications/scheduling/__tests__/resolve-slot-instant.test.ts": 2, + "lib/medications/scheduling/__tests__/schedule-eras.test.ts": 1, + "lib/medications/scheduling/__tests__/should-mint-missed-dose.test.ts": 1, + "lib/medications/scheduling/__tests__/worker-helpers.test.ts": 1, + "lib/medications/scheduling/attribute-intake.ts": 3, + "lib/medications/scheduling/attribution.ts": 3, + "lib/medications/scheduling/band-minter.ts": 1, + "lib/medications/scheduling/cadence.ts": 29, + "lib/medications/scheduling/compliance.ts": 1, + "lib/medications/scheduling/dose-history.ts": 14, + "lib/medications/scheduling/next-due.ts": 4, + "lib/medications/scheduling/project-today-intakes.ts": 5, + "lib/medications/scheduling/recurrence.ts": 16, + "lib/medications/scheduling/resolve-slot-instant.ts": 2, + "lib/medications/scheduling/schedule-eras.ts": 2, + "lib/medications/scheduling/slot-resolution.ts": 2, + "lib/medications/scheduling/slot-upsert.ts": 26, + "lib/medications/scheduling/worker-helpers.ts": 7, + "lib/medications/side-effects/__tests__/drift-guard.test.ts": 1, + "lib/medications/side-effects/__tests__/taxonomy.test.ts": 1, + "lib/medications/side-effects/taxonomy.ts": 1, + "lib/medications/side-effects/validators.ts": 3, + "lib/medications/supply-runway.ts": 9, + "lib/medications/use-injection-site-prefs.ts": 1, + "lib/medications/window-status.ts": 14, + "lib/mental-health/__tests__/instruments.test.ts": 2, + "lib/mental-health/assessment-trend.ts": 1, + "lib/mental-health/audit-c.ts": 1, + "lib/mental-health/crisis-resources.ts": 1, + "lib/mental-health/instruments.ts": 2, + "lib/module-list-prefs.ts": 1, + "lib/modules/__tests__/gate.test.ts": 4, + "lib/modules/__tests__/measurement-scope.test.ts": 1, + "lib/modules/__tests__/onboarding-needs-map.test.ts": 1, + "lib/modules/domain-data.ts": 1, + "lib/modules/gate.ts": 1, + "lib/modules/measurement-scope.ts": 3, + "lib/modules/operator-availability.ts": 1, + "lib/modules/registry.ts": 10, + "lib/modules/surface.ts": 3, + "lib/mood/__tests__/create-from-telegram.test.ts": 2, + "lib/mood/__tests__/date-key.test.ts": 1, + "lib/mood/__tests__/tag-layout.test.ts": 1, + "lib/mood/__tests__/tag-links.test.ts": 1, + "lib/mood/create-from-telegram.ts": 4, + "lib/mood/custom-tags.ts": 10, + "lib/mood/date-key.ts": 1, + "lib/mood/icon-catalog.ts": 1, + "lib/mood/labels.ts": 1, + "lib/mood/linked-context.ts": 1, + "lib/mood/list-read.ts": 8, + "lib/mood/note-backfill.ts": 1, + "lib/mood/tag-groups.ts": 1, + "lib/mood/tag-layout.ts": 1, + "lib/mood/tag-links.ts": 7, + "lib/motion.ts": 1, + "lib/navigation/shared-record.ts": 1, + "lib/notifications/__tests__/admin-locale.test.ts": 1, + "lib/notifications/__tests__/delegated-intake.test.ts": 1, + "lib/notifications/__tests__/dispatch-localised-cache.test.ts": 1, + "lib/notifications/__tests__/dispatcher.test.ts": 1, + "lib/notifications/__tests__/medication-intake-sync.test.ts": 1, + "lib/notifications/__tests__/web-push-dose-clear.test.ts": 1, + "lib/notifications/delegated-intake.ts": 2, + "lib/notifications/dispatch-localised.ts": 3, + "lib/notifications/dispatcher.ts": 3, + "lib/notifications/dose-tag.ts": 1, + "lib/notifications/medication-intake-sync.ts": 1, + "lib/notifications/senders/__tests__/apns.test.ts": 7, + "lib/notifications/senders/__tests__/push-attempt-record.test.ts": 1, + "lib/notifications/senders/__tests__/telegram.test.ts": 1, + "lib/notifications/senders/__tests__/urgency.test.ts": 1, + "lib/notifications/senders/apns.ts": 11, + "lib/notifications/senders/push-attempt-record.ts": 1, + "lib/notifications/senders/telegram-keyboards.ts": 1, + "lib/notifications/senders/telegram.ts": 4, + "lib/notifications/senders/web-push.ts": 5, + "lib/notifications/senders/webhook.ts": 1, + "lib/notifications/types.ts": 21, + "lib/notifications/web-push-clear.ts": 1, + "lib/nutrients/__tests__/catalog.test.ts": 1, + "lib/nutrients/catalog.ts": 8, + "lib/oauth/signed-state.ts": 1, + "lib/observability/__tests__/redact-payload.test.ts": 1, + "lib/observability/redact-payload.ts": 10, + "lib/onboarding/__tests__/confirm-summary.test.ts": 1, + "lib/onboarding/__tests__/dashboard-seed.test.ts": 1, + "lib/onboarding/__tests__/first-result-config.test.ts": 3, + "lib/onboarding/__tests__/needs.test.ts": 1, + "lib/onboarding/__tests__/question-config.test.ts": 1, + "lib/onboarding/__tests__/wizard-steps.test.ts": 2, + "lib/onboarding/checklist.ts": 8, + "lib/onboarding/confirm-summary.ts": 2, + "lib/onboarding/dashboard-seed.ts": 1, + "lib/onboarding/disclaimer.ts": 1, + "lib/onboarding/first-result-config.ts": 1, + "lib/onboarding/load-flow-state.ts": 1, + "lib/onboarding/needs-apply.ts": 1, + "lib/onboarding/needs-store.ts": 1, + "lib/onboarding/needs.ts": 1, + "lib/onboarding/question-config.ts": 3, + "lib/onboarding/tour-progress.ts": 1, + "lib/onboarding/tour-state.ts": 5, + "lib/onboarding/wizard-steps.ts": 1, + "lib/openapi/__tests__/medication-auth-contract-drift.test.ts": 1, + "lib/openapi/registry.ts": 1, + "lib/openapi/routes/account-sharing.ts": 7, + "lib/openapi/routes/admin.ts": 2, + "lib/openapi/routes/auth.ts": 1, + "lib/openapi/routes/coach.ts": 4, + "lib/openapi/routes/cycle.ts": 2, + "lib/openapi/routes/dashboard.ts": 1, + "lib/openapi/routes/environment.ts": 1, + "lib/openapi/routes/health-record.ts": 8, + "lib/openapi/routes/import.ts": 1, + "lib/openapi/routes/index.ts": 11, + "lib/openapi/routes/insights/schemas.ts": 23, + "lib/openapi/routes/integrations.ts": 2, + "lib/openapi/routes/labs.ts": 1, + "lib/openapi/routes/measurements.ts": 6, + "lib/openapi/routes/medications/schemas.ts": 10, + "lib/openapi/routes/meta.ts": 2, + "lib/openapi/routes/mood.ts": 5, + "lib/openapi/routes/onboarding.ts": 2, + "lib/openapi/routes/profile.ts": 14, + "lib/openapi/routes/settings.ts": 1, + "lib/openapi/routes/vaccinations.ts": 1, + "lib/openapi/routes/workouts.ts": 4, + "lib/optimistic-lock.ts": 1, + "lib/oura/__tests__/sync.test.ts": 1, + "lib/oura/client.ts": 1, + "lib/oura/credentials.ts": 3, + "lib/oura/cycle-sync.ts": 1, + "lib/oura/response-classifier.ts": 1, + "lib/oura/sync.ts": 2, + "lib/personal-records/__tests__/cumulative-pr-rederivation.test.ts": 1, + "lib/personal-records/__tests__/pr-detection-worker.test.ts": 4, + "lib/personal-records/__tests__/pr-direction.test.ts": 2, + "lib/personal-records/cumulative-pr-rederivation.ts": 2, + "lib/personal-records/pr-detection-worker.ts": 6, + "lib/personal-records/pr-direction.ts": 26, + "lib/polar/__tests__/dedup.test.ts": 1, + "lib/polar/client.ts": 1, + "lib/polar/credentials.ts": 3, + "lib/polar/response-classifier.ts": 1, + "lib/polar/sync-workouts.ts": 1, + "lib/polar/sync.ts": 1, + "lib/practitioners/dto.ts": 1, + "lib/profile/__tests__/height-unit-display.test.ts": 1, + "lib/profile/audit-c-entries.ts": 1, + "lib/profile/height-unit-display.ts": 2, + "lib/profile/life-circumstances.ts": 1, + "lib/profile/life-derived.ts": 1, + "lib/profile/life-report-rows.ts": 1, + "lib/pwa/__tests__/sw-cache-policy.test.ts": 3, + "lib/pwa/query-client-options.ts": 1, + "lib/pwa/query-persister.ts": 3, + "lib/pwa/sw-cache-policy.ts": 2, + "lib/queries/__tests__/use-achievements-query.test.tsx": 1, + "lib/queries/__tests__/use-dashboard-snapshot.test.tsx": 4, + "lib/queries/__tests__/use-medication-compliance-summary.test.ts": 1, + "lib/queries/__tests__/use-medication-list-layout.test.ts": 2, + "lib/queries/prefetch-medications.ts": 2, + "lib/queries/use-account-grants.ts": 1, + "lib/queries/use-achievements-query.ts": 1, + "lib/queries/use-analytics-query.ts": 3, + "lib/queries/use-dashboard-snapshot.ts": 5, + "lib/queries/use-handover-decision.ts": 1, + "lib/queries/use-managed-profiles.ts": 2, + "lib/queries/use-medication-compliance-summary.ts": 3, + "lib/queries/use-medication-list-layout.ts": 3, + "lib/queries/use-profile-claim.ts": 4, + "lib/query-keys/admin.ts": 13, + "lib/query-keys/ai-runs.ts": 1, + "lib/query-keys/auth.ts": 9, + "lib/query-keys/coach.ts": 13, + "lib/query-keys/custom-metrics.ts": 1, + "lib/query-keys/cycle.ts": 1, + "lib/query-keys/dashboard.ts": 5, + "lib/query-keys/encounters.ts": 1, + "lib/query-keys/environment.ts": 1, + "lib/query-keys/illness.ts": 6, + "lib/query-keys/index.ts": 26, + "lib/query-keys/insights.ts": 17, + "lib/query-keys/integrations.ts": 10, + "lib/query-keys/labs.ts": 5, + "lib/query-keys/measurement-reminders.ts": 1, + "lib/query-keys/measurements.ts": 10, + "lib/query-keys/medications.ts": 13, + "lib/query-keys/mood.ts": 8, + "lib/query-keys/nutrients.ts": 2, + "lib/query-keys/record-scope.ts": 1, + "lib/query-keys/record-session-transition.ts": 1, + "lib/query-keys/settings.ts": 8, + "lib/query-keys/sharing.ts": 5, + "lib/query-keys/vaccinations.ts": 1, + "lib/query-keys/workouts.ts": 3, + "lib/rate-limit.ts": 5, + "lib/record-activity/activity-verb.ts": 2, + "lib/record-settings/classification.ts": 1, + "lib/record-settings/integrations.ts": 1, + "lib/record-settings/modules.ts": 1, + "lib/reference-ranges.ts": 1, + "lib/report-selection/__tests__/catalogue-guard.test.ts": 1, + "lib/report-selection/catalogue.ts": 1, + "lib/request-cache.ts": 1, + "lib/rollups/__tests__/collapse-rollup-rows-by-source.test.ts": 1, + "lib/rollups/__tests__/compose-regression.test.ts": 1, + "lib/rollups/__tests__/measurement-read-wmy.test.ts": 5, + "lib/rollups/__tests__/measurement-read.test.ts": 1, + "lib/rollups/__tests__/measurement-rollups.test.ts": 11, + "lib/rollups/__tests__/mood-rollups.test.ts": 3, + "lib/rollups/__tests__/rollup-live-parity.test.ts": 1, + "lib/rollups/__tests__/tiered-context.test.ts": 3, + "lib/rollups/measurement-coverage.ts": 1, + "lib/rollups/measurement-read-wmy.ts": 4, + "lib/rollups/measurement-read.ts": 10, + "lib/rollups/measurement-rollups.ts": 18, + "lib/rollups/mood-rollups.ts": 4, + "lib/rollups/tiered-context.ts": 6, + "lib/score-config/rows.ts": 1, + "lib/settings/section-slugs.ts": 20, + "lib/sharing/__tests__/account-access-parse.test.ts": 3, + "lib/sharing/__tests__/account-access.test.ts": 2, + "lib/sharing/__tests__/account-label.test.ts": 1, + "lib/sharing/__tests__/grants.test.ts": 1, + "lib/sharing/account-access-schema.ts": 2, + "lib/sharing/account-access-view.ts": 8, + "lib/sharing/account-access.ts": 4, + "lib/sharing/acting-session.ts": 3, + "lib/sharing/audit-details.ts": 1, + "lib/sharing/delegated-generation.ts": 1, + "lib/sharing/grant-view.ts": 2, + "lib/sharing/grants.ts": 4, + "lib/sharing/module-disclosure.ts": 1, + "lib/sharing/record-session-fence-contract.ts": 1, + "lib/sharing/record-session-fence.ts": 1, + "lib/sharing/scope.ts": 2, + "lib/signals/registry.ts": 2, + "lib/sources/__tests__/source-priority-whoop-applehealth.test.ts": 2, + "lib/sources/__tests__/source-priority-withings-applehealth.test.ts": 1, + "lib/sse/__tests__/create-stream.test.ts": 1, + "lib/sse/create-stream.ts": 3, + "lib/strava/__tests__/dedup.test.ts": 1, + "lib/strava/client.ts": 1, + "lib/strava/credentials.ts": 1, + "lib/strava/response-classifier.ts": 1, + "lib/strava/sync.ts": 1, + "lib/sync/__tests__/cursor.test.ts": 1, + "lib/sync/cursor.ts": 1, + "lib/targets/__tests__/builders.test.ts": 1, + "lib/targets/__tests__/glucose-targets.test.ts": 1, + "lib/targets/__tests__/mood-stability-label.test.ts": 1, + "lib/targets/__tests__/source-link.test.ts": 1, + "lib/targets/__tests__/target-unit-display.test.ts": 1, + "lib/targets/build-response.ts": 3, + "lib/targets/mood-stability-label.ts": 1, + "lib/targets/target-unit-display.ts": 2, + "lib/targets/types.ts": 2, + "lib/targets/vitals-builder.ts": 2, + "lib/telegram-cleanup.ts": 1, + "lib/telegram-webhook-handlers.ts": 18, + "lib/telegram.ts": 2, + "lib/time-window-format.ts": 1, + "lib/timeline/__tests__/lane-access.test.ts": 1, + "lib/tz/__tests__/intl-cache.test.ts": 1, + "lib/tz/__tests__/resolver.test.ts": 1, + "lib/tz/format.ts": 2, + "lib/tz/resolver.ts": 1, + "lib/tz/start-of-utc-day.ts": 1, + "lib/tz/wall-clock.ts": 1, + "lib/vaccinations/__tests__/resolve-vaccine-entry.test.ts": 1, + "lib/vaccinations/document-suggestion.ts": 1, + "lib/vaccinations/dto.ts": 1, + "lib/vaccinations/service.ts": 1, + "lib/validations/__tests__/backup.test.ts": 1, + "lib/validations/__tests__/coach-prefs.test.ts": 4, + "lib/validations/__tests__/measurement-reminders.test.ts": 3, + "lib/validations/__tests__/measurement.test.ts": 9, + "lib/validations/__tests__/medication-dose-windows.test.ts": 1, + "lib/validations/__tests__/medication-drug-codes.test.ts": 1, + "lib/validations/__tests__/medication-intake-event.test.ts": 2, + "lib/validations/__tests__/medication-inventory.test.ts": 2, + "lib/validations/__tests__/mood.test.ts": 1, + "lib/validations/__tests__/notification-prefs.test.ts": 2, + "lib/validations/__tests__/onboarding-needs.test.ts": 1, + "lib/validations/__tests__/source-priority.test.ts": 8, + "lib/validations/about-me.ts": 4, + "lib/validations/account-sharing.ts": 3, + "lib/validations/admin.ts": 2, + "lib/validations/ai-usage.ts": 1, + "lib/validations/auth.ts": 5, + "lib/validations/backup-summary.ts": 13, + "lib/validations/backup.ts": 40, + "lib/validations/biomarker-merge.ts": 1, + "lib/validations/biomarkers.ts": 3, + "lib/validations/claim-sso.ts": 1, + "lib/validations/clinician-share-link.ts": 4, + "lib/validations/coach-fact.ts": 1, + "lib/validations/coach-plan.ts": 1, + "lib/validations/coach-prefs.ts": 9, + "lib/validations/coach-reminder-suggestion.ts": 1, + "lib/validations/coach-reminder.ts": 1, + "lib/validations/consent.ts": 2, + "lib/validations/custom-metrics.ts": 1, + "lib/validations/cycle.ts": 1, + "lib/validations/dawarich.ts": 1, + "lib/validations/encounters.ts": 1, + "lib/validations/environment.ts": 2, + "lib/validations/inbound-documents.ts": 7, + "lib/validations/invite.ts": 1, + "lib/validations/labs-ocr.ts": 3, + "lib/validations/labs.ts": 7, + "lib/validations/life-circumstances.ts": 1, + "lib/validations/managed-profile-handover.ts": 1, + "lib/validations/managed-profiles.ts": 1, + "lib/validations/measurement-reminders.ts": 8, + "lib/validations/measurement.ts": 22, + "lib/validations/medication-efficacy.ts": 1, + "lib/validations/medication-ingredients.ts": 1, + "lib/validations/medication/base.ts": 10, + "lib/validations/medication/course.ts": 1, + "lib/validations/medication/create-update.ts": 31, + "lib/validations/medication/intake.ts": 9, + "lib/validations/medication/inventory.ts": 6, + "lib/validations/medication/schedule.ts": 8, + "lib/validations/mental-health.ts": 1, + "lib/validations/modules.ts": 1, + "lib/validations/mood.ts": 18, + "lib/validations/notification-prefs.ts": 36, + "lib/validations/nutrients.ts": 3, + "lib/validations/onboarding-needs.ts": 1, + "lib/validations/onboarding.ts": 1, + "lib/validations/pulse-day-mean.ts": 1, + "lib/validations/recommendation-feedback.ts": 1, + "lib/validations/relation-links.ts": 1, + "lib/validations/schedule-revision.ts": 2, + "lib/validations/series-batch.ts": 1, + "lib/validations/source-priority.ts": 33, + "lib/validations/vaccinations.ts": 2, + "lib/whoop/__tests__/sync-body.test.ts": 1, + "lib/whoop/__tests__/sync-sleep-sweep.test.ts": 1, + "lib/whoop/__tests__/sync-user-whoop.test.ts": 1, + "lib/whoop/__tests__/sync-workout-overlap.test.ts": 1, + "lib/whoop/__tests__/sync.test.ts": 1, + "lib/whoop/__tests__/webhook-notification.test.ts": 1, + "lib/whoop/client.ts": 3, + "lib/whoop/connect-ticket.ts": 1, + "lib/whoop/oauth-state.ts": 1, + "lib/whoop/return-scheme.ts": 1, + "lib/withings/__tests__/client.test.ts": 1, + "lib/withings/__tests__/ecg-waveform-codec.test.ts": 1, + "lib/withings/__tests__/response-classifier.test.ts": 2, + "lib/withings/__tests__/sync-activity.test.ts": 6, + "lib/withings/__tests__/sync-ecg.test.ts": 1, + "lib/withings/__tests__/sync-measure-watermark.test.ts": 1, + "lib/withings/__tests__/sync-sleep.test.ts": 4, + "lib/withings/__tests__/sync-typed-classification.test.ts": 1, + "lib/withings/__tests__/sync.test.ts": 1, + "lib/withings/client.ts": 5, + "lib/withings/oauth-state.ts": 1, + "lib/withings/response-classifier.ts": 1, + "lib/withings/sync-activity.ts": 8, + "lib/withings/sync-ecg.ts": 4, + "lib/withings/sync-sleep.ts": 10, + "lib/withings/sync.ts": 9, + "lib/withings/webhook-handler.ts": 3, + "lib/workouts/list-read.ts": 1, + "proxy.ts": 29, + "types/analytics.ts": 8, + "types/backups.ts": 1 + } +} diff --git a/src/__tests__/helpers/ratchet.ts b/src/__tests__/helpers/ratchet.ts new file mode 100644 index 000000000..53445ca3f --- /dev/null +++ b/src/__tests__/helpers/ratchet.ts @@ -0,0 +1,151 @@ +/** + * A ratchet: a committed baseline of counts that may only fall. + * + * Each ratchet guard measures something per key (lines per file, `as never` + * per test file, one per file on a list) and compares it with a JSON baseline + * under `src/__tests__/baselines/`. Three things fail: + * + * - a key that is not in the baseline (a new offender); + * - a key whose value rose above its baseline (an offender that grew); + * - a key that fell below its baseline by more than the ratchet's `slack`, + * or disappeared (an improvement nobody wrote down). + * + * The third is what makes it a ratchet rather than a ceiling: an improvement + * that is not written back leaves room for the next regression to hide in. + * Writing back is one command, printed in every failure: + * + * RATCHET_WRITE=1 pnpm vitest run + * + * which rewrites the baseline with the lower of the recorded and the + * measured value for every key, drops keys that reached zero, and never + * raises anything. Raising a value, or adding a key, is a hand edit of the + * JSON in a reviewed change, with the reason in the PR. + */ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; + +export const BASELINE_DIR = join(process.cwd(), "src/__tests__/baselines"); + +export interface RatchetBaseline { + $comment: string; + /** Number of keys, kept in the file so a reader sees the headline. */ + count: number; + /** Sum of `entries`. */ + total: number; + entries: Record; +} + +export interface RatchetResult { + count: number; + total: number; + baselineCount: number; + baselineTotal: number; + /** New keys and keys above their baseline, as readable lines. */ + grew: string[]; + /** Keys that fell below their baseline beyond the slack, or vanished. */ + stale: string[]; +} + +function sum(entries: Record): number { + return Object.values(entries).reduce((a, b) => a + b, 0); +} + +export function readBaseline(file: string): RatchetBaseline { + return JSON.parse( + readFileSync(join(BASELINE_DIR, file), "utf8"), + ) as RatchetBaseline; +} + +/** + * Compare measured counts against the baseline in `file`. With + * `RATCHET_WRITE=1` in the environment the lower numbers are written back + * first, so the comparison that follows passes on everything but growth. + */ +export function runRatchet( + file: string, + measured: Record, + options: { slack?: number; comment?: string } = {}, +): RatchetResult { + const slack = options.slack ?? 0; + const current = Object.fromEntries( + Object.entries(measured).filter(([, v]) => v > 0), + ); + const write = process.env.RATCHET_WRITE === "1"; + // The one case where writing records the measured numbers as they are: a + // baseline that does not exist yet. Deleting a baseline to re-seed it + // higher shows up as a whole-file diff in review. + if (write && !existsSync(join(BASELINE_DIR, file))) { + mkdirSync(BASELINE_DIR, { recursive: true }); + writeFileSync( + join(BASELINE_DIR, file), + `${JSON.stringify({ $comment: options.comment ?? "", count: 0, total: 0, entries: current }, null, 2)}\n`, + ); + } + let baseline = readBaseline(file); + + if (write) { + const lowered: Record = {}; + for (const [key, recorded] of Object.entries(baseline.entries)) { + const now = current[key] ?? 0; + const value = Math.min(recorded, now); + if (value > 0) lowered[key] = value; + } + const sorted = Object.fromEntries( + Object.entries(lowered).sort(([a], [b]) => a.localeCompare(b)), + ); + baseline = { + $comment: baseline.$comment, + count: Object.keys(sorted).length, + total: sum(sorted), + entries: sorted, + }; + writeFileSync( + join(BASELINE_DIR, file), + `${JSON.stringify(baseline, null, 2)}\n`, + ); + } + + const grew: string[] = []; + const stale: string[] = []; + for (const [key, value] of Object.entries(current)) { + const recorded = baseline.entries[key]; + if (recorded === undefined) grew.push(`${key}: new, ${value}`); + else if (value > recorded) grew.push(`${key}: ${recorded} → ${value}`); + } + for (const [key, recorded] of Object.entries(baseline.entries)) { + const value = current[key] ?? 0; + if (value < recorded - slack) stale.push(`${key}: ${recorded} → ${value}`); + } + return { + count: Object.keys(current).length, + total: sum(current), + baselineCount: baseline.count, + baselineTotal: baseline.total, + grew: grew.sort(), + stale: stale.sort(), + }; +} + +export function writeBackHint(guardFile: string): string { + return ( + `The baseline records a higher number than the tree now has. Write the ` + + `lower numbers back so the ratchet tightens: ` + + `RATCHET_WRITE=1 pnpm vitest run ${guardFile}` + ); +} + +/** Read a baseline of another shape than counts per key, or null if absent. */ +export function readBaselineJson(file: string): T | null { + const path = join(BASELINE_DIR, file); + return existsSync(path) + ? (JSON.parse(readFileSync(path, "utf8")) as T) + : null; +} + +export function writeBaselineJson(file: string, value: unknown): void { + mkdirSync(BASELINE_DIR, { recursive: true }); + writeFileSync( + join(BASELINE_DIR, file), + `${JSON.stringify(value, null, 2)}\n`, + ); +} diff --git a/src/__tests__/helpers/source-metrics.ts b/src/__tests__/helpers/source-metrics.ts new file mode 100644 index 000000000..f03d8b365 --- /dev/null +++ b/src/__tests__/helpers/source-metrics.ts @@ -0,0 +1,376 @@ +/** + * Measurements over the production source tree, shared by the ratchet + * guards: which files count as production code, how long each file and each + * function is, and the value-import graph between modules. + * + * Everything is read through the TypeScript compiler, not grepped, so a + * function is a function node (declaration, expression, arrow, method, + * constructor, accessor) and an import's kind is the parser's answer, not a + * regex's guess. `typescript` is already a dependency; nothing new is pulled + * in. + * + * Production code means every `.ts`/`.tsx` file under `src/` outside + * `src/generated/`, test files, `__tests__/`, `__mocks__/`, `test-utils` and + * declaration files. The walk goes through `walkSourceFiles`, so dot-prefixed + * directories (`app/.well-known/`) are included and an empty walk throws. + */ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +import ts from "typescript"; + +import { requireFloor, walkSourceFiles } from "./source-files"; + +export const SRC = join(process.cwd(), "src"); + +const TEST_PATH = + /(^|\/)__tests__\/|(^|\/)__mocks__\/|\.(test|spec)\.tsx?$|(^|\/)test-utils(\/|\.)/; + +export function isTestPath(rel: string): boolean { + return TEST_PATH.test(rel); +} + +/** Production source files, relative to `src/`, sorted. */ +export function productionSources(): string[] { + const files = walkSourceFiles(SRC, { floor: 3000 }).filter( + (rel) => + !rel.startsWith("generated/") && + !rel.endsWith(".d.ts") && + !isTestPath(rel), + ); + return requireFloor("production source files", files, 3000) as string[]; +} + +/** Test files under `src/`, relative to `src/`, sorted. */ +export function testSources(): string[] { + const files = walkSourceFiles(SRC, { floor: 3000 }).filter( + (rel) => !rel.startsWith("generated/") && /\.(test|spec)\.tsx?$/.test(rel), + ); + return requireFloor("test files", files, 2000) as string[]; +} + +export function readSrc(rel: string): string { + return readFileSync(join(SRC, rel), "utf8"); +} + +/** Line count the way an editor shows it: a trailing newline adds none. */ +export function lineCount(text: string): number { + if (text.length === 0) return 0; + const lines = text.split("\n").length; + return text.endsWith("\n") ? lines - 1 : lines; +} + +export function parse(rel: string, text: string): ts.SourceFile { + return ts.createSourceFile( + rel, + text, + ts.ScriptTarget.Latest, + true, + rel.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS, + ); +} + +type FunctionNode = + | ts.FunctionDeclaration + | ts.FunctionExpression + | ts.ArrowFunction + | ts.MethodDeclaration + | ts.ConstructorDeclaration + | ts.GetAccessorDeclaration + | ts.SetAccessorDeclaration; + +function isFunctionNode(node: ts.Node): node is FunctionNode { + return ( + ts.isFunctionDeclaration(node) || + ts.isFunctionExpression(node) || + ts.isArrowFunction(node) || + ts.isMethodDeclaration(node) || + ts.isConstructorDeclaration(node) || + ts.isGetAccessorDeclaration(node) || + ts.isSetAccessorDeclaration(node) + ); +} + +function propertyNameText(name: ts.PropertyName): string | null { + if ( + ts.isIdentifier(name) || + ts.isStringLiteral(name) || + ts.isNumericLiteral(name) || + ts.isPrivateIdentifier(name) + ) { + return name.text; + } + return null; +} + +function calleeText(call: ts.CallExpression, sf: ts.SourceFile): string { + const text = call.expression.getText(sf).replace(/\s+/g, ""); + return text.length > 40 ? `${text.slice(0, 40)}…` : text; +} + +/** + * A readable, stable name for a function node: its own name, the variable or + * property it is assigned to, or the call it is passed to + * (`apiHandler(arg)`, `$transaction(arg)`). Positions are deliberately not + * part of it, so an edit above a function does not rename it. + */ +function ownName(node: FunctionNode, sf: ts.SourceFile): string { + if (ts.isConstructorDeclaration(node)) return "constructor"; + if ( + (ts.isFunctionDeclaration(node) || + ts.isFunctionExpression(node) || + ts.isMethodDeclaration(node) || + ts.isGetAccessorDeclaration(node) || + ts.isSetAccessorDeclaration(node)) && + node.name + ) { + const text = ts.isIdentifier(node.name) + ? node.name.text + : propertyNameText(node.name as ts.PropertyName); + if (text) return text; + } + const parent = node.parent; + if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) { + return parent.name.text; + } + if (ts.isPropertyAssignment(parent)) { + return propertyNameText(parent.name) ?? "(property)"; + } + if (ts.isPropertyDeclaration(parent)) { + return propertyNameText(parent.name) ?? "(property)"; + } + if (ts.isExportAssignment(parent)) return "default"; + if (ts.isCallExpression(parent)) return `${calleeText(parent, sf)}(arg)`; + if (ts.isJsxExpression(parent)) return "(jsx callback)"; + return "(anonymous)"; +} + +export interface MeasuredFunction { + /** `outer > inner` chain of names, unique within the file. */ + name: string; + /** 1-based first line. */ + line: number; + lines: number; +} + +/** + * Every function in the file with its length in lines, nested functions + * included (a 2,800-line function holding a 2,100-line callback reports + * both). Names that collide within a file get a `#2`, `#3` suffix in source + * order. + */ +export function measureFunctions(sf: ts.SourceFile): MeasuredFunction[] { + const out: MeasuredFunction[] = []; + const seen = new Map(); + const visit = (node: ts.Node, chain: string[]): void => { + let next = chain; + if (isFunctionNode(node)) { + const base = [...chain, ownName(node, sf)].join(" > "); + const n = (seen.get(base) ?? 0) + 1; + seen.set(base, n); + const name = n === 1 ? base : `${base} #${n}`; + const start = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line; + const end = sf.getLineAndCharacterOfPosition(node.getEnd()).line; + out.push({ name, line: start + 1, lines: end - start + 1 }); + next = [...chain, ownName(node, sf)]; + } + ts.forEachChild(node, (child) => visit(child, next)); + }; + visit(sf, []); + return out; +} + +// ── Value-import graph ────────────────────────────────────────────────────── + +const COMPILER_OPTIONS: ts.CompilerOptions = { + baseUrl: process.cwd(), + paths: { "@/*": ["./src/*"] }, + moduleResolution: ts.ModuleResolutionKind.Bundler, + module: ts.ModuleKind.ESNext, + jsx: ts.JsxEmit.ReactJSX, + resolveJsonModule: true, + allowJs: true, +}; + +export interface ModuleImports { + /** Specifiers this module evaluates at runtime, static and dynamic. */ + value: string[]; + /** True when every statement is an `export … from` (or an import). */ + reexportOnly: boolean; +} + +/** + * The specifiers a module needs at runtime: static imports and re-exports + * that are not type-only, side-effect imports, and dynamic `import("…")` + * with a literal specifier. `import type`, `export type` and lists whose + * every name is `type` are erased by the compiler and left out. + */ +export function moduleImports(sf: ts.SourceFile): ModuleImports { + const value: string[] = []; + let reexports = 0; + let other = 0; + for (const st of sf.statements) { + if (ts.isImportDeclaration(st)) { + const clause = st.importClause; + const typeOnly = + !!clause && + (clause.isTypeOnly || + (!clause.name && + !!clause.namedBindings && + ts.isNamedImports(clause.namedBindings) && + clause.namedBindings.elements.length > 0 && + clause.namedBindings.elements.every((e) => e.isTypeOnly))); + if (!typeOnly && ts.isStringLiteral(st.moduleSpecifier)) { + value.push(st.moduleSpecifier.text); + } + continue; + } + if (ts.isExportDeclaration(st) && st.moduleSpecifier) { + reexports += 1; + const typeOnly = + st.isTypeOnly || + (!!st.exportClause && + ts.isNamedExports(st.exportClause) && + st.exportClause.elements.length > 0 && + st.exportClause.elements.every((e) => e.isTypeOnly)); + if (!typeOnly && ts.isStringLiteral(st.moduleSpecifier)) { + value.push(st.moduleSpecifier.text); + } + continue; + } + other += 1; + } + const visit = (node: ts.Node): void => { + if ( + ts.isCallExpression(node) && + node.expression.kind === ts.SyntaxKind.ImportKeyword && + node.arguments[0] && + ts.isStringLiteralLike(node.arguments[0]) + ) { + value.push(node.arguments[0].text); + } + ts.forEachChild(node, visit); + }; + visit(sf); + return { value, reexportOnly: reexports > 0 && other === 0 }; +} + +export interface ImportGraph { + /** Module → modules it imports at runtime, both relative to `src/`. */ + edges: Map; + /** Modules whose every statement is a re-export. */ + reexportOnly: string[]; + /** Specifiers resolved to a module inside `src/`. */ + resolved: number; +} + +/** + * The runtime import graph over the production sources, resolved with the + * compiler's own module resolution under the app's `@/*` path mapping. + * Imports of `src/generated/` and of packages are not edges. + */ +export function buildImportGraph(files: readonly string[]): ImportGraph { + const cache = ts.createModuleResolutionCache( + process.cwd(), + (x) => x, + COMPILER_OPTIONS, + ); + const known = new Set(files); + const edges = new Map(); + const reexportOnly: string[] = []; + let resolved = 0; + for (const rel of files) { + const abs = join(SRC, rel); + const sf = parse(rel, readFileSync(abs, "utf8")); + const imports = moduleImports(sf); + if (imports.reexportOnly) reexportOnly.push(rel); + const deps = new Set(); + for (const spec of imports.value) { + const hit = ts.resolveModuleName( + spec, + abs, + COMPILER_OPTIONS, + ts.sys, + cache, + ).resolvedModule; + if (!hit || hit.isExternalLibraryImport) continue; + const target = hit.resolvedFileName; + if (!target.startsWith(`${SRC}/`)) continue; + const targetRel = target.slice(SRC.length + 1); + if (!known.has(targetRel)) continue; + deps.add(targetRel); + resolved += 1; + } + edges.set(rel, [...deps].sort()); + } + return { edges, reexportOnly, resolved }; +} + +/** Strongly connected components with more than one module, or a self-loop. */ +export function stronglyConnected(edges: Map): string[][] { + let index = 0; + const idx = new Map(); + const low = new Map(); + const onStack = new Set(); + const stack: string[] = []; + const out: string[][] = []; + + const strong = (v: string): void => { + idx.set(v, index); + low.set(v, index); + index += 1; + stack.push(v); + onStack.add(v); + for (const w of edges.get(v) ?? []) { + if (!idx.has(w)) { + strong(w); + low.set(v, Math.min(low.get(v)!, low.get(w)!)); + } else if (onStack.has(w)) { + low.set(v, Math.min(low.get(v)!, idx.get(w)!)); + } + } + if (low.get(v) === idx.get(v)) { + const component: string[] = []; + let w: string; + do { + w = stack.pop()!; + onStack.delete(w); + component.push(w); + } while (w !== v); + if (component.length > 1 || (edges.get(v) ?? []).includes(v)) { + out.push(component.sort()); + } + } + }; + + for (const v of [...edges.keys()].sort()) if (!idx.has(v)) strong(v); + return out.sort((a, b) => a[0].localeCompare(b[0])); +} + +/** + * Cycles the way a depth-first walk in sorted order meets them: one cycle + * per back edge, each listed from the module the walk re-entered. This is + * the count `madge --circular` reports, so the number reads the same as the + * audits that measured it. + */ +export function backEdgeCycles(edges: Map): string[][] { + const visited = new Set(); + const cycles: string[][] = []; + const path: string[] = []; + const onPath = new Set(); + const walk = (v: string): void => { + if (onPath.has(v)) { + cycles.push([...path.slice(path.indexOf(v)), v]); + return; + } + if (visited.has(v)) return; + visited.add(v); + path.push(v); + onPath.add(v); + for (const w of edges.get(v) ?? []) walk(w); + path.pop(); + onPath.delete(v); + }; + for (const v of [...edges.keys()].sort()) walk(v); + return cycles; +} diff --git a/src/__tests__/source-size-ratchet.test.ts b/src/__tests__/source-size-ratchet.test.ts new file mode 100644 index 000000000..e17e1fe8f --- /dev/null +++ b/src/__tests__/source-size-ratchet.test.ts @@ -0,0 +1,168 @@ +/** + * File and function size may only shrink. + * + * Two caps over the production source (see `helpers/source-metrics.ts` for + * what counts): a file over 800 lines, and a function over 200 lines. Most of + * the large files are one large function, so both are measured, through the + * TypeScript compiler. + * + * Every file and function over its cap today is in a baseline under + * `baselines/` with its current length. From here: + * + * - a new file or function over the cap fails; + * - a baselined one that grows past its recorded length fails. If the + * growth is deliberate, raising the number is a hand edit of the JSON in + * a reviewed change, with the reason in the PR; + * - a baselined one that shrank by more than the slack (or fell under the + * cap) fails until the lower number is written back with + * `RATCHET_WRITE=1 pnpm vitest run src/__tests__/source-size-ratchet.test.ts`. + * The slack (25 lines for a file, 10 for a function) keeps a small edit + * from forcing a baseline commit; a real split always crosses it. + * + * Catalog and data files are exempt from both caps: their length is the + * length of the thing they list, and splitting them changes navigation, not + * cohesion. Each is named below with its reason, and a named file that no + * longer exists fails, so the list cannot outlive its entries. + */ +import { existsSync } from "node:fs"; +import { join } from "node:path"; + +import { describe, expect, it } from "vitest"; + +import { requireFloor } from "./helpers/source-files"; +import { + SRC, + lineCount, + measureFunctions, + parse, + productionSources, + readSrc, +} from "./helpers/source-metrics"; +import { runRatchet, writeBackHint } from "./helpers/ratchet"; + +const FILE_CAP = 800; +const FUNCTION_CAP = 200; +const GUARD = "src/__tests__/source-size-ratchet.test.ts"; + +/** + * Exempt from both caps. A key ending in `/` covers the folder. Paths are + * relative to `src/`. + */ +const CATALOGS: Readonly> = { + "lib/openapi/routes/": + "the OpenAPI contract: one schema and one path entry per route, grows with the API surface", + "lib/signals/registry.ts": "the SIGNALS record, one entry per signal", + "lib/ai/coach/eval/golden-cases.ts": "Coach evaluation fixtures", + "lib/ai/coach/system-prompt.ts": "prompt text", + "lib/measurements/apple-health-mapping.ts": + "the HealthKit identifier mapping table", + "lib/labs/biomarker-catalog.ts": "the biomarker catalogue", + "app/privacy/page.tsx": + "legal copy rendered as React text, one long static component", +}; + +function isCatalog(rel: string): boolean { + return Object.keys(CATALOGS).some((key) => + key.endsWith("/") ? rel.startsWith(key) : rel === key, + ); +} + +function measure() { + const files = productionSources(); + const fileLines: Record = {}; + const functionLines: Record = {}; + let functionsSeen = 0; + for (const rel of files) { + if (isCatalog(rel)) continue; + const text = readSrc(rel); + const lines = lineCount(text); + if (lines > FILE_CAP) fileLines[rel] = lines; + for (const fn of measureFunctions(parse(rel, text))) { + functionsSeen += 1; + if (fn.lines > FUNCTION_CAP) + functionLines[`${rel} :: ${fn.name}`] = fn.lines; + } + } + return { files: files.length, functionsSeen, fileLines, functionLines }; +} + +describe("source size ratchet", () => { + const m = measure(); + + it("reads the whole tree", () => { + expect(m.files).toBeGreaterThan(3000); + expect(m.functionsSeen).toBeGreaterThan(20_000); + requireFloor("files over the cap", Object.keys(m.fileLines), 100); + requireFloor("functions over the cap", Object.keys(m.functionLines), 300); + }); + + it(`no file over ${FILE_CAP} lines grows, and none is added`, () => { + const r = runRatchet("source-file-lines.json", m.fileLines, { + slack: 25, + comment: `Production files over ${FILE_CAP} lines and their length. Maintained by ${GUARD}.`, + }); + expect( + r.grew, + `Files over ${FILE_CAP} lines that are new or longer than their baseline. Split the file, or raise its number in src/__tests__/baselines/source-file-lines.json in a reviewed change with the reason.`, + ).toEqual([]); + expect(r.stale, writeBackHint(GUARD)).toEqual([]); + }); + + it(`no function over ${FUNCTION_CAP} lines grows, and none is added`, () => { + const r = runRatchet("source-function-lines.json", m.functionLines, { + slack: 10, + comment: `Production functions over ${FUNCTION_CAP} lines, keyed "file :: name", and their length. Maintained by ${GUARD}.`, + }); + expect( + r.grew, + `Functions over ${FUNCTION_CAP} lines that are new or longer than their baseline. Extract a part, or raise its number in src/__tests__/baselines/source-function-lines.json in a reviewed change with the reason.`, + ).toEqual([]); + expect(r.stale, writeBackHint(GUARD)).toEqual([]); + }); + + it("names no catalog that is gone", () => { + const missing = Object.keys(CATALOGS).filter( + (key) => !existsSync(join(SRC, key)), + ); + expect(missing).toEqual([]); + }); +}); + +describe("the function measure", () => { + const fns = (text: string) => measureFunctions(parse("probe.ts", text)); + + it("names declarations, assignments, methods and call arguments", () => { + const names = fns( + [ + "export function a() {}", + "const b = () => {};", + "const o = { c() {}, d: function () {} };", + "class K { constructor() {} e() {} }", + "wrap(async () => {});", + "function f() { return () => 1; }", + ].join("\n"), + ).map((f) => f.name); + expect(names).toEqual([ + "a", + "b", + "c", + "d", + "constructor", + "e", + "wrap(arg)", + "f", + "f > (anonymous)", + ]); + }); + + it("counts lines from the first to the last line of the node", () => { + const [fn] = fns("function a() {\n x();\n y();\n}\n"); + expect(fn.lines).toBe(4); + expect(fn.line).toBe(1); + }); + + it("keeps two same-named functions apart", () => { + const names = fns("call(() => 1);\ncall(() => 2);").map((f) => f.name); + expect(names).toEqual(["call(arg)", "call(arg) #2"]); + }); +}); diff --git a/src/__tests__/test-realism-ratchet.test.ts b/src/__tests__/test-realism-ratchet.test.ts new file mode 100644 index 000000000..e35e78a9c --- /dev/null +++ b/src/__tests__/test-realism-ratchet.test.ts @@ -0,0 +1,196 @@ +/** + * Test shapes that cannot see the bugs this repo keeps shipping may only + * become rarer. + * + * Four ratchets over the test files under `src/`, each against a baseline in + * `baselines/` (see `helpers/ratchet.ts` for the mechanics): + * + * - route tests that replace the database client wholesale + * (`vi.mock("@/lib/db", …)`): they assert the query the route sends, not + * what Postgres does with it, so a `where` that matches the wrong rows + * passes. Behaviour belongs in `tests/integration/`; + * - route tests that stub `@/lib/api-handler`: they skip auth, scope, the + * envelope and idempotency, which is where the handler arity class hid; + * - `as never` casts in tests: they switch the type checker off for exactly + * the fixture rows that would otherwise drift loudly when a model + * changes; + * - tests that read a source file by path and assert on its text. A split + * or rename leaves such a test reading the wrong file, and a negative + * assertion then passes for the wrong reason. New ones go through + * `readSource(path, anchors)` or `readSourceFolder(dir, { floor })` in + * `helpers/source-files.ts`, which this count does not include. + * + * A new offender fails; one that disappears (or loses casts) fails until + * the lower number is written back with + * `RATCHET_WRITE=1 pnpm vitest run src/__tests__/test-realism-ratchet.test.ts`. + * + * Limits, stated: the database mock is matched on `vi.mock`/`vi.doMock` with + * the `@/lib/db` specifier or a relative path ending in `lib/db`; a mock of + * the Prisma client entry itself, or a spy on every delegate, is not a + * pattern in use and is not matched. The source-read check is a text + * heuristic: a file that calls `readFileSync`/`readFile` and names a source + * path (a literal ending in `.ts`, `.tsx`, `.js` or `.mjs`). + */ +import { describe, expect, it } from "vitest"; +import ts from "typescript"; + +import { requireFloor } from "./helpers/source-files"; +import { parse, readSrc, testSources } from "./helpers/source-metrics"; +import { runRatchet, writeBackHint } from "./helpers/ratchet"; + +const GUARD = "src/__tests__/test-realism-ratchet.test.ts"; + +const DB_MOCK = + /vi\.(?:do)?[mM]ock\(\s*["'`](?:@\/lib\/db|(?:\.\.?\/)+(?:[\w-]+\/)*lib\/db)["'`]/; +const HANDLER_STUB = /vi\.(?:do)?[mM]ock\(\s*["'`]@\/lib\/api-handler["'`]/; +const READS_FILE = /\b(?:readFileSync|readFile)\s*\(/; +const SOURCE_PATH = /["'`][^"'`\n]*\.(?:tsx?|mjs|js)["'`]/; + +function asNeverCount(rel: string, text: string): number { + if (!text.includes("as never")) return 0; + let n = 0; + const visit = (node: ts.Node): void => { + if ( + ts.isAsExpression(node) && + node.type.kind === ts.SyntaxKind.NeverKeyword + ) { + n += 1; + } + ts.forEachChild(node, visit); + }; + visit(parse(rel, text)); + return n; +} + +function classifyTest(rel: string, text: string) { + const routeTest = rel.startsWith("app/api/"); + return { + dbMock: routeTest && DB_MOCK.test(text), + handlerStub: routeTest && HANDLER_STUB.test(text), + asNever: asNeverCount(rel, text), + sourceRead: READS_FILE.test(text) && SOURCE_PATH.test(text), + }; +} + +function measure() { + const tests = testSources(); + const dbMocks: Record = {}; + const handlerStubs: Record = {}; + const asNever: Record = {}; + const sourceReads: Record = {}; + let routeTests = 0; + for (const rel of tests) { + const c = classifyTest(rel, readSrc(rel)); + if (rel.startsWith("app/api/")) routeTests += 1; + if (c.dbMock) dbMocks[rel] = 1; + if (c.handlerStub) handlerStubs[rel] = 1; + if (c.asNever > 0) asNever[rel] = c.asNever; + if (c.sourceRead) sourceReads[rel] = 1; + } + return { + tests: tests.length, + routeTests, + dbMocks, + handlerStubs, + asNever, + sourceReads, + }; +} + +describe("test realism ratchet", () => { + const m = measure(); + + it("reads the whole test tree", () => { + expect(m.tests).toBeGreaterThan(2000); + expect(m.routeTests).toBeGreaterThan(300); + requireFloor("route tests mocking the db", Object.keys(m.dbMocks), 100); + requireFloor("test files with as never", Object.keys(m.asNever), 100); + requireFloor("source-reading tests", Object.keys(m.sourceReads), 100); + }); + + it.each([ + [ + "route tests that mock @/lib/db wholesale", + "route-test-db-mocks.json", + "dbMocks", + "Drive the route against Postgres in tests/integration/ instead.", + ], + [ + "route tests that stub @/lib/api-handler", + "route-test-handler-stubs.json", + "handlerStubs", + "Call the real handler, so auth, scope and the envelope run.", + ], + [ + "as never casts in tests", + "test-as-never.json", + "asNever", + "Type the fixture (a typed builder or the Prisma payload type) instead of casting it away.", + ], + [ + "tests that read a source file by path", + "test-source-reads.json", + "sourceReads", + "Read through readSource(path, anchors) or readSourceFolder(dir, { floor }) from src/__tests__/helpers/source-files.ts.", + ], + ] as const)("%s only become rarer", (label, file, key, advice) => { + const r = runRatchet(file, m[key], { + comment: `${label}, per test file under src/. Maintained by ${GUARD}.`, + }); + expect(r.grew, `New or grown: ${label}. ${advice}`).toEqual([]); + expect(r.stale, writeBackHint(GUARD)).toEqual([]); + }); +}); + +const READ_CALL = ["read", "File", "Sync"].join(""); + +describe("the classifier", () => { + it("sees the shapes it counts", () => { + const c = classifyTest( + "app/api/x/__tests__/route.test.ts", + [ + 'vi.mock("@/lib/db", () => ({ prisma: {} }));', + 'vi.mock("@/lib/api-handler", () => ({}));', + "const a = {} as never; const b = [] as never;", + // Split so this file does not count itself as a source reader. + `const src = ${READ_CALL}(join(ROOT, "src/app/api/x/route.ts"), "utf8");`, + ].join("\n"), + ); + expect(c).toEqual({ + dbMock: true, + handlerStub: true, + asNever: 2, + sourceRead: true, + }); + }); + + it("ignores neighbours that only look alike", () => { + const c = classifyTest( + "app/api/x/__tests__/route.test.ts", + [ + 'vi.mock("@/lib/db-compat", () => ({}));', + "// cast with as never is banned", + 'const s = "as never";', + `const fixture = ${READ_CALL}(join(DIR, "sample.xml"), "utf8");`, + ].join("\n"), + ); + expect(c).toEqual({ + dbMock: false, + handlerStub: false, + asNever: 0, + sourceRead: false, + }); + }); + + it("counts a relative database mock and only under app/api", () => { + expect( + classifyTest( + "app/api/x/__tests__/a.test.ts", + 'vi.mock("../../../lib/db")', + ).dbMock, + ).toBe(true); + expect( + classifyTest("lib/x/__tests__/a.test.ts", 'vi.mock("@/lib/db")').dbMock, + ).toBe(false); + }); +}); diff --git a/src/__tests__/version-stamp-ratchet.test.ts b/src/__tests__/version-stamp-ratchet.test.ts new file mode 100644 index 000000000..3d7b22ef6 --- /dev/null +++ b/src/__tests__/version-stamp-ratchet.test.ts @@ -0,0 +1,85 @@ +/** + * No new comment that opens with a release version. + * + * `// v1.4.39.1 — wipe the rollup partition … Pre-fix the partition kept …` + * is a history note: it describes a state of the code the reader cannot see, + * and it repeats what CHANGELOG and `git log` already hold. About ten + * thousand comment lines in the tree open that way. They stay until their + * file is split or rewritten anyway; a mass edit would be churn. New ones do + * not get added: say what the code does and why, and leave when it changed + * to the history. + * + * A comment line counts when, after its marker (`//`, `/*`, or the `*` of a + * block comment), it opens with `vMAJOR.MINOR[.PATCH…]`. A version later in + * the sentence ("since v1.39 the gate …") is not an opener and is not + * counted. The count is per file, against `baselines/version-stamp-comments.json`: + * a file that gains one fails, a new file with one fails, and a file that + * sheds some fails until the lower count is written back with + * `RATCHET_WRITE=1 pnpm vitest run src/__tests__/version-stamp-ratchet.test.ts`. + * + * Limit, stated: it reads lines, not the parse tree, so a line inside a + * template string that happens to start with `* v1.2` would count. None does + * today. + */ +import { describe, expect, it } from "vitest"; + +import { requireFloor, walkSourceFiles } from "./helpers/source-files"; +import { SRC, readSrc } from "./helpers/source-metrics"; +import { runRatchet, writeBackHint } from "./helpers/ratchet"; + +const GUARD = "src/__tests__/version-stamp-ratchet.test.ts"; + +const STAMP_OPENER = /^\s*(?:\/\/+|\/\*+|\*+)\s*\(?v\d+\.\d+(?:\.\d+){0,3}\b/; + +function stampCount(text: string): number { + let n = 0; + for (const line of text.split("\n")) if (STAMP_OPENER.test(line)) n += 1; + return n; +} + +describe("version-stamp comments", () => { + const files = walkSourceFiles(SRC, { floor: 5000 }).filter( + (rel) => !rel.startsWith("generated/") && !rel.endsWith(".d.ts"), + ); + const counts: Record = {}; + for (const rel of files) { + const n = stampCount(readSrc(rel)); + if (n > 0) counts[rel] = n; + } + + it("finds the stamps that are there", () => { + requireFloor("files with a version-stamp opener", Object.keys(counts), 500); + }); + + it("gains none, and records every one that goes", () => { + const r = runRatchet("version-stamp-comments.json", counts, { + comment: `Comment lines that open with a release version, per file under src/. Maintained by ${GUARD}.`, + }); + expect( + r.grew, + "New comment lines opening with a release version. Say what the code does and why; when it changed belongs in CHANGELOG and git.", + ).toEqual([]); + expect(r.stale, writeBackHint(GUARD)).toEqual([]); + }); +}); + +describe("the opener matcher", () => { + it.each([ + "// v1.4.39.1 — wipe the partition", + " // v1.23: notes are encrypted", + " * v1.37.19 (#219 parity) — per-slot units", + "/* v1.5.0 — one-shot backfill */", + "/** v1.16.12 — stale window widened */", + ])("counts %s", (line) => { + expect(stampCount(line)).toBe(1); + }); + + it.each([ + "// Since v1.39 every AI decision is one resolution", + "const v = 'v1.2.3';", + "// version 1.2", + "// ve1.2", + ])("does not count %s", (line) => { + expect(stampCount(line)).toBe(0); + }); +}); From f64b49cf554a4d3cad3d80d12d015b912e7d30cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 04:29:57 +0200 Subject: [PATCH 4/9] test(guards): freeze runtime import cycles and re-export-only modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The runtime import graph (static value imports and re-exports, side-effect imports, dynamic import() with a literal; `import type` erased) is built with the TypeScript compiler's module resolution under the `@/*` mapping, over every production module. No new dependency. - Cycles: 13, counted one per back edge of a sorted depth-first walk (the number `madge --circular` reports), in 7 strongly connected components: the audit/geo/notifications/managed-profiles cluster (10 modules), the compliance facade cluster (9), command palette ↔ store ↔ shortcuts, modules ↔ cycle gate, boss-instance ↔ boss-ready, mood aggregates ↔ patterns, features ↔ signals-of-day. A new component or a component that draws in another module fails whatever the count says; a lower count or a shrunk component fails until written back. The target is zero. - Re-export-only modules (every statement an `export … from`): 16 listed; a new one fails. Proof: two planted modules importing each other failed with "lib/zz-probe/a.ts <-> lib/zz-probe/b.ts", a planted `export * from "./a"` module failed the re-export ratchet, and a baseline raised to 14 cycles failed with the write-back message until RATCHET_WRITE=1 restored it. --- src/__tests__/baselines/import-cycles.json | 50 ++++++ .../baselines/reexport-only-modules.json | 23 +++ src/__tests__/import-cycle-guard.test.ts | 151 ++++++++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 src/__tests__/baselines/import-cycles.json create mode 100644 src/__tests__/baselines/reexport-only-modules.json create mode 100644 src/__tests__/import-cycle-guard.test.ts diff --git a/src/__tests__/baselines/import-cycles.json b/src/__tests__/baselines/import-cycles.json new file mode 100644 index 000000000..d92e0b7a9 --- /dev/null +++ b/src/__tests__/baselines/import-cycles.json @@ -0,0 +1,50 @@ +{ + "$comment": "Runtime import cycles under src/: the back-edge count and the strongly connected components. Maintained by src/__tests__/import-cycle-guard.test.ts.", + "cycles": 13, + "components": [ + [ + "components/command-palette/command-palette.tsx", + "components/command-palette/palette-store.ts", + "components/layout/global-shortcuts.tsx" + ], + [ + "lib/analytics/compliance.ts", + "lib/analytics/compliance/display.ts", + "lib/analytics/compliance/ledger.ts", + "lib/analytics/compliance/occurrences.ts", + "lib/analytics/compliance/windows.ts", + "lib/medications/scheduling/band-minter.ts", + "lib/medications/scheduling/cadence.ts", + "lib/medications/scheduling/compliance.ts", + "lib/medications/scheduling/schedule-eras.ts" + ], + [ + "lib/auth/audit.ts", + "lib/geo.ts", + "lib/integrations/oauth-kit/connect-target.ts", + "lib/jobs/offhost-purge.ts", + "lib/managed-profiles/lifecycle.ts", + "lib/notifications/channel-state.ts", + "lib/notifications/delivery-identity.ts", + "lib/notifications/dispatch-localised.ts", + "lib/notifications/dispatcher.ts", + "lib/sharing/grants.ts" + ], + [ + "lib/cycle/gate.ts", + "lib/modules/gate.ts" + ], + [ + "lib/insights/features.ts", + "lib/insights/signals-of-day.ts" + ], + [ + "lib/insights/mood-aggregates.ts", + "lib/insights/mood-patterns.ts" + ], + [ + "lib/jobs/boss-instance.ts", + "lib/jobs/boss-ready.ts" + ] + ] +} diff --git a/src/__tests__/baselines/reexport-only-modules.json b/src/__tests__/baselines/reexport-only-modules.json new file mode 100644 index 000000000..2202753f0 --- /dev/null +++ b/src/__tests__/baselines/reexport-only-modules.json @@ -0,0 +1,23 @@ +{ + "$comment": "Modules under src/ whose every statement is an export … from. Maintained by src/__tests__/import-cycle-guard.test.ts.", + "count": 16, + "total": 16, + "entries": { + "components/data-list/index.ts": 1, + "components/insights/derived/index.ts": 1, + "components/insights/workout-detail/index.ts": 1, + "lib/ai/coach/tools/index.ts": 1, + "lib/analytics/compliance.ts": 1, + "lib/cycle/index.ts": 1, + "lib/doctor-report-data.ts": 1, + "lib/fhir/resources/index.ts": 1, + "lib/insights/derived/index.ts": 1, + "lib/links/index.ts": 1, + "lib/logging/index.ts": 1, + "lib/mcp/index.ts": 1, + "lib/openapi/routes/insights/index.ts": 1, + "lib/openapi/routes/medications/index.ts": 1, + "lib/record-settings/index.ts": 1, + "lib/validations/medication/index.ts": 1 + } +} diff --git a/src/__tests__/import-cycle-guard.test.ts b/src/__tests__/import-cycle-guard.test.ts new file mode 100644 index 000000000..b7fd1a6c8 --- /dev/null +++ b/src/__tests__/import-cycle-guard.test.ts @@ -0,0 +1,151 @@ +/** + * No new runtime import cycle, and no new re-export-only module. + * + * A value-import cycle means module evaluation order decides whether a + * binding is initialised when it is first read, and it ties the modules in + * it into one unit nobody can load or split alone. The graph here is the + * runtime one: static imports and re-exports that are not type-only, + * side-effect imports, and dynamic `import("…")` with a literal specifier + * (a lazy edge still loads the module into the same process). `import type` + * is erased and left out. Modules are resolved with the TypeScript compiler + * under the app's `@/*` mapping (`helpers/source-metrics.ts`). + * + * Two baselines in `baselines/import-cycles.json`: + * + * - the cycle count, as a depth-first walk in sorted order meets them (one + * per back edge, the number `madge --circular` reports); + * - the strongly connected components, member by member. + * + * A component that is new, or gained a member, fails: that is a new cycle, + * whatever the count says. A count above the baseline fails. A count below + * it, or a component that lost members, fails until the lower state is + * written back with + * `RATCHET_WRITE=1 pnpm vitest run src/__tests__/import-cycle-guard.test.ts`. + * The target is zero. + * + * A re-export-only module (every statement an `export … from`) is a facade: + * it is where most of the cycles above came from, because children of a + * split module import back through the old file. Existing ones are listed + * in `baselines/reexport-only-modules.json`; a new one fails. Import the + * module that defines the binding instead. + */ +import { describe, expect, it } from "vitest"; + +import { requireFloor } from "./helpers/source-files"; +import { + backEdgeCycles, + buildImportGraph, + productionSources, + stronglyConnected, +} from "./helpers/source-metrics"; +import { + readBaselineJson, + runRatchet, + writeBackHint, + writeBaselineJson, +} from "./helpers/ratchet"; + +const GUARD = "src/__tests__/import-cycle-guard.test.ts"; +const CYCLES_FILE = "import-cycles.json"; + +interface CycleBaseline { + $comment: string; + cycles: number; + components: string[][]; +} + +describe("runtime import cycles", () => { + const files = productionSources(); + const graph = buildImportGraph(files); + const components = stronglyConnected(graph.edges); + const cycles = backEdgeCycles(graph.edges); + + it("builds a graph of the whole tree", () => { + expect(graph.edges.size).toBeGreaterThan(3000); + expect(graph.resolved).toBeGreaterThan(15_000); + }); + + it("adds no cycle and leaves no improvement unrecorded", () => { + if (process.env.RATCHET_WRITE === "1") { + const old = readBaselineJson(CYCLES_FILE) ?? { + $comment: `Runtime import cycles under src/: the back-edge count and the strongly connected components. Maintained by ${GUARD}.`, + cycles: cycles.length, + components, + }; + const kept = old.components + .map((c) => c.filter((m) => components.some((now) => now.includes(m)))) + .filter((c) => c.length > 1); + writeBaselineJson(CYCLES_FILE, { + $comment: old.$comment, + cycles: Math.min(old.cycles, cycles.length), + components: kept, + }); + } + const baseline = readBaselineJson(CYCLES_FILE); + if (!baseline) throw new Error(`${CYCLES_FILE} is missing`); + + const grown = components.filter( + (c) => !baseline.components.some((b) => c.every((m) => b.includes(m))), + ); + expect( + grown.map((c) => c.join(" <-> ")), + "A new runtime import cycle (or a known one that drew in another " + + "module). Break it: move the shared piece into a leaf module, turn " + + "the edge into `import type` if only a type crosses, or import the " + + "defining module instead of a facade.", + ).toEqual([]); + expect( + cycles.length, + `Runtime import cycles: ${cycles.map((c) => c.join(" -> ")).join("; ")}`, + ).toBeLessThanOrEqual(baseline.cycles); + + const shrunk = baseline.components.filter( + (b) => !components.some((c) => b.every((m) => c.includes(m))), + ); + expect( + shrunk.map((c) => c.join(" <-> ")), + writeBackHint(GUARD), + ).toEqual([]); + expect(cycles.length, writeBackHint(GUARD)).toBe(baseline.cycles); + }); + + it("adds no re-export-only module", () => { + requireFloor("re-export-only modules", graph.reexportOnly, 1); + const r = runRatchet( + "reexport-only-modules.json", + Object.fromEntries(graph.reexportOnly.map((rel) => [rel, 1])), + { + comment: `Modules under src/ whose every statement is an export … from. Maintained by ${GUARD}.`, + }, + ); + expect( + r.grew, + "A new re-export-only module. Import the module that defines the binding instead of adding a facade.", + ).toEqual([]); + expect(r.stale, writeBackHint(GUARD)).toEqual([]); + }); +}); + +describe("the cycle finder", () => { + const graphOf = (edges: Record) => + new Map(Object.entries(edges)); + + it("finds a two-module and a three-module cycle, and nothing in a tree", () => { + const g = graphOf({ + a: ["b"], + b: ["a", "c"], + c: ["d"], + d: ["e"], + e: ["c"], + f: ["a"], + }); + expect(stronglyConnected(g)).toEqual([ + ["a", "b"], + ["c", "d", "e"], + ]); + expect(backEdgeCycles(g)).toHaveLength(2); + expect(stronglyConnected(graphOf({ a: ["b"], b: ["c"], c: [] }))).toEqual( + [], + ); + }); +}); From 45ea61261edf76c7d622f42c6d60ad650d4c60ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 04:36:21 +0200 Subject: [PATCH 5/9] build(bundle): gate every page's first load and the distinct client code The gate summed every emitted chunk once and watched five pages. The sum counts a module once per chunk group it lands in, so it rose whenever a page got lighter by moving code into on-demand chunks, and it needed a raise in most releases while no reader downloaded more; and a heavy import into a page outside the five went unseen. scripts/check-bundle-budget.mjs now measures: - the first load of every page: rootMainFiles plus the page's client-reference chunks, each file counted once by content hash; - p50, p95 and max of those first loads across pages; - the distinct client code: every chunk evaluated in a node:vm sandbox with a stub TURBOPACK array, each module factory counted once by its module id (not its text: each chunk is minified on its own, so copies of one module differ in mangled names), catalogs excluded, gzipped once. The summed aggregate is still printed but no longer gated. The catalog, per-catalog and one-Recharts checks are unchanged. `--write-baselines` rewrites the per-page baselines, `--dir` measures another build directory, `--json` writes the report. bundle-budget.json is re-based from the v1.44.0 build (local arm64): 110 pages, first load p50 336, p95 437, max 451 KB gz; distinct client code 1904 KB gz in 1224 modules; summed aggregate 3875 KB for comparison. Every page has a baseline with the same 20 KB allowance; caps are p95 447, max 461 and distinct code 1930 KB. A page without a baseline fails until one is written in the same change. Proof: importing zod into the weight insights page measured /insights/weight at 427.8 KB gz, 88.8 KB over its 339 KB baseline, and the gate failed; the old gate passed the same build, because the page was not watched and the summed aggregate did not move (3875 KB). The import was removed again. --- .github/workflows/e2e.yml | 11 +- bundle-budget.json | 127 ++++++++++- scripts/check-bundle-budget.mjs | 391 +++++++++++++++++++++----------- 3 files changed, 386 insertions(+), 143 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 4ac0fc739..5c1f748c8 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -93,11 +93,12 @@ jobs: - run: NODE_OPTIONS=--max-old-space-size=8192 pnpm build # Client-bundle budget gate — rides this job because it is the one - # place CI already has a production build on disk. Catches the - # regressions the dead webpack-era tooling (ANALYZE=1, the old - # bundle-report) let through: a statically imported message catalog - # (~100 KB gz on every route) or a chart import bypassing the shared - # chart-runtime boundary (a duplicated ~92 KB gz recharts chunk). + # place CI already has a production build on disk. Measures the first + # load of every page (files deduplicated by content hash) against its + # baseline, the p95 and max across pages, and the distinct client code + # (modules deduplicated by id); see the script header. Catches a + # statically imported message catalog, a duplicated recharts chunk, or + # a heavy module (Zod) pulled into a page that had none. - run: pnpm bundle-check - run: pnpm exec playwright test --shard=${{ matrix.shard }}/3 diff --git a/bundle-budget.json b/bundle-budget.json index 2fdc452d6..0553bb199 100644 --- a/bundle-budget.json +++ b/bundle-budget.json @@ -1,14 +1,121 @@ { - "$comment": "Client-bundle budgets, enforced by scripts/check-bundle-budget.mjs (--check) after `next build` in CI. Values are KB gzip and include every emitted lazy route and locale chunk, while route budgets include the shared rootMainFiles baseline. Re-measured at v1.37.2: / 455, /insights 443, /measurements 441, /insights/mood 453, total 3328, baseline 130, lazy catalogs 849. Every per-route ceiling is unchanged. The /insights overview crept a kilobyte over its 445 ceiling when the ECG teaser began importing its overview cap from the classification module, dragging that module's label map and helpers into the eager page; the cap now lives in its own value-only module, and the Health Score's single provenance entry is split out of the full metric-provenance map, so the overview imports the bare number and citation it renders rather than every metric's label map behind them. Both are back under 445 with room to spare. The 42 KB aggregate rise from 3286 (v1.35.0) is all locale-catalog strings: v1.36 and v1.37 added the encounter, mood-context, mood-prognosis, practitioner, workout-batch and account-sharing surfaces, and their copy lands across the six locale catalogs the whole-build sum counts independently (804 to 849 KB). The non-catalog client chunks are flat against v1.35.0, so nothing a person downloads on a given route grew beyond its own ceiling. The aggregate cap therefore moves from 3310 to 3352 (24 KB headroom, matching the previous margin); per-route ceilings, the static-catalog guard and the one-Recharts-chunk guard remain unchanged. v1.37.4's honest-error-state charge then added recoverable read-failure handling to thirteen surfaces (the shared QueryErrorCard and QueryErrorRow primitives plus an isError branch per surface) and rewrote the MCP scope note and the managed-record error copy across the six locales; the measured total rose to 3404 while every per-route ceiling stayed met (/page 457, /insights 445, /measurements 443, /insights/mood 455). The aggregate cap moves from 3352 to 3428, keeping the same margin; nothing a person downloads on a given route grew beyond its ceiling. v1.37.15's async-truth charge then added the refresh settling state with its two-locale-key copy, the index-outcome plain-words line (seven keys across the six catalogs) and the reindex-count wiring; the measured total rose to 3429 while every per-route ceiling stayed met (/page 456, /insights 445, /measurements 442, /insights/mood 455). The aggregate cap moves from 3428 to 3452, keeping the same margin; nothing a person downloads on a given route grew beyond its ceiling. v1.37.27 found /insights sitting a few hundred bytes under 445 on main and a few hundred over it on the release branch — the page had been at its ceiling for three releases and the release only had to breathe on it. The weight it was carrying was not its own: the authenticated shell imported one destination predicate from the `@/lib/record-settings` barrel, and the barrel also re-exports the managed settings-patch schemas, so Zod, the module registry and three validation modules rode into the eager bundle of every authenticated route for a function that reads a URL slug. The shell imports the `classification` leaf now. Measured across all 585 client-rendered routes: none grew by a byte, 100 fell, by 4.7 KB at the median and 8.1 KB at the most; /insights 444 to 437, /measurements 442 to 435, / 456 to 450, /insights/mood 455 to 450. The whole-build sum moved the other way, 3451 to 3476, and that direction is the metric rather than the change: the aggregate counts each emitted chunk once, so a graph that leaves the chunk group every route shares gets counted again wherever it lands — here inside the deferred settings chunks that actually use it. Bytes on disk rose 25 KB; bytes a reader downloads only ever fell. The aggregate cap moves from 3452 to 3500, the same 24 KB headroom as the four re-measurements before it. Per-route ceilings keep their old values rather than tightening to the new floor: the CI runner measures each route about 1.5 KB heavier than a local arm64 build does, and a ceiling set from a local number would fail on a build that had not regressed. The static-catalog guard and the one-Recharts-chunk guard remain unchanged. Raised to 3700 at v1.37.32 for the seventh interface language (Korean): the lazy message catalogs went 6->7 chunks (1026 KB gz measured), pushing the aggregate to 3631 against the old 3500. The raise is entirely the new catalog; no route budget moved, and the four eager route ceilings are unchanged and still passing. Most of the aggregate is the dynamic locale-switch and fallback path that a first paint never loads (see issue #253), so this ceiling guards bytes almost no visitor requests; 3700 leaves ~69 KB for ordinary drift until that double-count is cleaned up. v1.38.14 re-measures the four eager routes and changes what the number means. The ceilings had been re-stated but never re-based since v1.37.4, so /insights and /measurements sat within a kilobyte of theirs and / and /insights/mood within one of theirs; e2e run 34043772954 failed on `/insights/page: 445 KB gz exceeds the 445 KB budget` with the other two at 459 of 460, and none of it was a regression anyone had made. A ceiling a route is already touching does not measure the route, it measures whichever dependency bump landed that week. The file states the measured value per route now, plus one drift allowance over it: measured on a local arm64 build at d48bbff01 as / 447, /insights 434, /measurements 433, /insights/mood 447, with a 20 KB allowance, so the effective ceilings are 467 / 454 / 453 / 467 — a raise for all four. The allowance covers the runner delta (the CI runner measures each route a little heavier than a local arm64 build) and ordinary week-to-week drift, while the step change the gate exists for — a statically imported message catalog, a duplicated chart library — is several times larger and still fails. Every run now prints the delta against the baseline, so a route that has crept 15 KB is legible in the log long before it is red; the flat ceiling could only ever say pass or fail. Re-base by re-measuring on a build with nothing else in it and writing the new numbers here with the reason, exactly as a raised ceiling was written before. Raised to 3724 at v1.39.4 on a measured delta, not a feature. The release branch alone measured 3699 KB gz (3698.9) against the 3700 cap, so the ordinary drift room the v1.37.32 raise left had been used up by the releases since; the review fixes on top of it added 1.3 KB gz across the whole build (3700.2), almost all of it new copy in the seven lazy locale catalogs (the total and reused-table labels, the step and chip wording) plus the result-panel states. The four eager routes measured the same on both builds (/ 458, /insights/mood 456, /insights 445, /measurements 442), all inside their ceilings. The aggregate cap moves from 3700 to 3724, the same 24 KB headroom as the re-measurements before it. Raised to 3749 at v1.39.9 on a measured delta. Main measured 3718 KB gz after v1.39.7 and v1.39.8 (unit-aware copy, the phone layouts, the time-zone work and the account-settings restore labels, almost all of it new strings in the seven lazy locale catalogs), leaving 6 KB of the v1.39.4 headroom; the manual workout entry (#1085: the form, its sheet, the delete action and their copy in seven locales) adds about 7 KB and measured 3725. The four eager routes stay inside their drift allowances (/ +12.5, /insights +12.1, /insights/mood +10.2 KB of 20 on main). The aggregate cap moves from 3724 to 3749, the same 24 KB headroom as the re-measurements before it. Raised to 3790 at v1.40.0 on a measured delta. With the background document reads on main (#1103: the run polling hook, the still-reading states and their copy in seven locales), the key backup step and the plain-http sign-in notice (#1104) and the v1.40 fixes (#1106: the duration reader, the marker unit lock, the chart window) each branch measured 3752 KB gz on its own against the 3749 cap; together they add a few KB more. The four eager routes stay inside their drift allowances (/ +18.4, /insights +16.5, /measurements +14.9, /insights/mood +14.7 KB of 20). The aggregate cap moves from 3749 to 3790, about 24 KB above the combined build, the same headroom as the re-measurements before it. Re-based at v1.40.0 when the quick-entry forms began loading on demand. Custom symptoms added a fifth quick-entry form, and with all five forms bundled eagerly every one of the four eager routes measured over its ceiling (/ 472, /insights 457, /insights/mood 468, /measurements 454 KB gz, each 21 to 25 KB over the baseline). Loading the forms on demand brings them to / 429, /insights 368, /insights/mood 379, /measurements 411: 43 to 90 KB less on first load. The four route baselines move down to those measured values, so the gain is protected by the same 20 KB allowance rather than spent. The aggregate moves the other way, 3774 to 4077, for the reason described at v1.37.27: the aggregate counts each emitted chunk once per route group, and on-demand form chunks land in every route group that can open a quick-entry sheet. Bundling the five forms into one shared on-demand module measured the same (4078), so the split is not the cause. The aggregate cap moves from 3790 to 4100. Bytes on disk rose; bytes a reader downloads fell on every eager route. Raised to 4155 at v1.40.2 on a measured delta. The Coach redesign (the right conversation panel, its row menu and the quick-settings overlay, all loaded on demand), the answer action row, the dashboard's Today overview, the document procedure filter and their copy in seven locales took the whole-build sum from 4080 to 4129 KB gz; most of it is new strings in the seven lazy locale catalogs and the on-demand Coach chunks, which a first paint does not load. The four eager routes stay well inside their drift allowances (/ +1.9, /measurements +1.9, /insights/mood +1.4, /insights +1.0 KB of 20). The aggregate cap moves from 4100 to 4155, about 26 KB above the build, the same headroom as before; per-route baselines are unchanged. Raised to 4182 at v1.42.0 on a measured delta. Main measured 4146 KB gz (lazy catalogs 1177, other chunks 2969); the release branch's shared contract change measured 4158 (lazy catalogs 1188, other chunks 2969): the whole 12 KB is new strings in the seven lazy locale catalogs (the Health Connect import card, air quality and pollen, the managed-profile handover and claim, own vaccines, the lab conversion note, the per-type Apple Health list), and no eager route moved (/ +3.6, /measurements +1.5, /insights/mood +1.1, /insights +0.7 KB of 20). The aggregate cap moves from 4155 to 4182, about 24 KB above the build, the same headroom as before; per-route baselines are unchanged. Re-based at v1.42.0 when the settings sections and Zod left the shared bundles. The settings section route imported all twenty-one sections statically; each now loads on its own, and /settings/[section] measured 611 KB gz before and 403 after. The account-access parse that runs on every boot was the shell's only Zod import, about 63 KB gz on every route; it is plain TypeScript now, held to the published schema by an equivalence test, and the layout entry fell from 199 to 136 KB gz. The canonical unit table moved out of the measurement validation module, which took Zod off the routes whose only reason for it was the display transform: /insights 369 to 304, /medications 428 to 365, /labs 400 to 337. Across the 627 client routes 62 fell, none grew by more than 2 KB, and the per-route sum fell by 1844 KB gz. The /insights baseline moves down to 304 and /settings/[section] joins the watched routes at 403, so both gains are held by the same 20 KB allowance rather than spent. The aggregate moves the other way, 4167 to 4311, for the reason described at v1.37.27 and v1.40.0: the aggregate counts each emitted chunk once, and modules that used to sit in one shared chunk are now emitted into each route group that still needs them (the measurement validation module and the unit table into about twenty-five each, the settings sections into their own chunks). Bytes on disk rose; bytes a reader downloads fell on every route that moved. The aggregate cap moves from 4182 to 4335, about 24 KB above the build, the same headroom as before. Raised to 4400 at v1.42.0 on a measured delta. The release branch after its tickets merge measured 4365 KB gz (lazy catalogs 1206, other chunks 3159), already 30 KB over the 4335 cap; the day view and timeline contract adds 10 more (catalogs 1215, other 3160), almost all of it the base copy for the day, the timeline and life events in seven locales. The aggregate cap moves from 4335 to 4400, about 25 KB above the build, the same headroom as before; per-route baselines are unchanged. Raised to 4470 at v1.42.0 on a measured delta. The release branch with the day view, the timeline and their server measured 4386 KB gz (lazy catalogs 1228, other chunks 3158); giving every chart drawn in days the same doors to its day (one shared hook for the click, the touch tooltip's button, the open day's line, the row of day dots and the caption, a shared calendar adapter, a data table under each newly linked chart) measured 4446 (catalogs 1228, other chunks 3218). The 60 KB is the aggregate's double count again: the hook, the data table and the day link are emitted into each chart's own chunk group rather than once. The eager routes moved by a kilobyte or a few (/ +1.6, /measurements +1.7, /settings/[section] +0.8, /insights/mood +4.6, /insights +1.5 KB) and stay inside their allowances. The aggregate cap moves from 4400 to 4470, about 24 KB above the build, the same headroom as before; per-route baselines are unchanged. Raised to 4760 at v1.42.0 on a measured delta, the other way round from a feature: the day panel and the date field's calendar left the first load. The day view (its scores, sections and calendar) now loads when a day first opens, not with the shell, and the date field loads react-day-picker and the seven date-fns locales when its popover first opens. Every watched route fell: / 460 to 420, /measurements 439 to 398, /insights/mood 438 to 395, /settings/[section] 432 to 387, /insights 360 to 317 KB gz, all five back under their unchanged ceilings. The aggregate rose from 4426 to 4736 for the reason described at v1.37.27 and v1.40.0: while the day view sat in the shell, the small primitives it shares with the pages (card, table, popover, switch, input, badge, the chart day links) were emitted once into the shared chunk; with the day view on demand, each route group that uses them gets its own copy, and the aggregate counts every copy. Bytes on disk rose; bytes a reader downloads fell on every route. The aggregate cap moves from 4470 to 4760, about 24 KB above the build, the same headroom as before; per-route baselines are unchanged. Raised to 4790 at v1.42.3 on a measured delta. The release branch measured 4762 KB gz (lazy catalogs 1243, other chunks 3519) against the 4760 cap; the 15 KB catalog rise is new copy in the seven lazy locale catalogs (the Withings plan note and its type labels, the Oura reconnect hint, the lab conversion and printed-range notes, the issuer refusal), and the rest is the conversion badge and range note in the scan review's own chunk. The five watched routes stay inside their allowances (/ 422, /measurements 399, /insights/mood 396, /settings/[section] 389, /insights 318 KB gz). The aggregate cap moves from 4760 to 4790, about 28 KB above the build; per-route baselines are unchanged. Raised to 4860 at v1.43.0 on a measured delta. The release branch measured 4834 KB gz (lazy catalogs 1302, other chunks 3532) against the 4790 cap. 29 KB is new copy in the seven lazy locale catalogs (the AI usage levels, the life circumstances and AUDIT-C, the supplement composition, the marker merge, Dawarich, the related-records lists, the pulse day-mean card and the support report), and 13 KB is the on-demand chunks of those surfaces. The five watched routes stay inside their allowances and barely moved since v1.42.3 (/ 423, /measurements 400, /insights/mood 397, /settings/[section] 405, /insights 320 KB gz). The aggregate cap moves from 4790 to 4860, about 26 KB above the build; per-route baselines are unchanged. Re-based at v1.43.0 when Zod left four eager routes. Between the v1.43 budget raise and the merged release branch the four Zod-carrying eager routes grew by about 26 KB gz each (/ 423 to 450, /settings/[section] 405 to 431, /measurements 400 to 426, /insights/mood 397 to 423) while /insights, which loads no Zod, did not move. The whole step was the shared Zod chunk, 63.0 to 88.9 KB gz: zod 4.6 adds an ahead-of-time schema compiler and its memoizer to the core, and because every schema is written against the `z` namespace the bundler keeps the whole namespace (every locale, the JSON Schema converters and now the compiler) wherever Zod loads at all. None of the four routes needed Zod for its first paint. The dashboard and the measurement list read the source list, the notes limit and the screening check out of the two measurement validation modules, the mood page read the Coach source list out of the Coach request schemas, and every settings section rendered a gate that imported the managed settings form and the allergy and family-history forms for a branch only a shared or managed record reaches. The values now live in value-only modules (`@/lib/measurements/sources`, `@/lib/measurements/notes-limit`, `@/lib/measurement-reminders/screening`, `@/lib/ai/coach/scope-sources`) the schemas are built from, and the gate loads its two record-scoped sections on demand. Measured on a local arm64 build: / 450 to 355, /measurements 426 to 335, /insights/mood 423 to 334, /settings/[section] 431 to 307 KB gz; /insights stays at 320. The four route baselines move down to those values, so the gain is held by the same 20 KB allowance rather than spent; a Zod import back on any of them is about 89 KB and fails. The aggregate moves the other way, 4883 to 4949, for the reason described at v1.37.27, v1.40.0 and v1.42.0: the small primitives the removed forms shared with the lazy settings sections and quick-entry forms (popover, switch, the date and time fields) used to be emitted once into the routes' first load, and each on-demand chunk group now carries its own copy, which the aggregate counts every time. Bytes on disk rose; bytes a reader downloads fell by 89 to 124 KB gz on every route that moved. The aggregate cap moves from 4860 to 4975, about 26 KB above the build, the same headroom as before. Raised to 5000 at v1.43.3 on a measured delta. The release branch measured 4977 KB gz (lazy catalogs 1307, other chunks 3670) against the 4975 cap. Every fresh-factor refusal now opens the re-proof dialog, so the hook and the dialog behind it are imported by the managed-profile create and edit forms, the guardian and handover panels, the card, and the advanced and admin key-rotation sections as well, each of them an on-demand chunk the aggregate counts on its own. Loading the dialog on demand through next/dynamic was measured and not kept: 4979 KB gz, and every watched route grew by 0.1 to 0.7 KB for the loader. The five watched routes stay inside their allowances (/ 355, /measurements 335, /insights/mood 334, /settings/[section] 308, /insights 321 KB gz). The aggregate cap moves from 4975 to 5000, about 23 KB above the build; per-route baselines are unchanged. Split at v1.43.7 for the eighth interface language (Dutch). The aggregate counted every lazy message catalog, although a reader downloads only the one for their language, so each new language read as a regression of about 190 KB and the cap had to be raised for copy nobody else loads. The aggregate now covers the client chunks without the lazy catalogs (3670 KB gz measured at v1.43.3, cap 3700, the same headroom as before), and each catalog is held to its own ceiling (about 187 KB gz each at v1.43.3, cap 210). The static-catalog guard, the route baselines and the one-Recharts-chunk guard are unchanged. Raised to 3755 at v1.43.8 on a measured delta, and the /insights baseline re-based. The release branch measured 3729 KB gz without the lazy catalogs against the 3700 cap (v1.43.7: 3665). Bundling the client entry graph of v1.43.7 and of the release branch once each, deduplicated, measured 3462.6 and 3462.8 KB gz: no dependency, schema or catalog entered the client. The rise is the double count described at v1.37.27 and v1.40.0. The release replaces hand-written controls with shared primitives (the segmented toggle and its roving focus hook, the inline action, the score pill, the page header with its back link, the display-unit resolver), each well under a kilobyte, and the aggregate counts a copy in every chunk group that uses them, up to 88 of them. The /insights baseline still stated the 304 measured when Zod left the page at v1.42.0, although the page measured 320 at v1.43.0 and 321 at v1.43.3, so it had been running on a 4 KB allowance for three releases; it moves to the 323 measured on the runner now, protected by the same 20 KB allowance. The other four watched routes moved by 1.8 to 2.5 KB and keep their baselines. The aggregate cap moves from 3700 to 3755, about 26 KB above the build, the same headroom as before. Raised to 3900 at v1.44.0 on a measured delta, with two first-load reductions in the same release. Main measured 3737 KB gz without the lazy catalogs and the release branch 3863, the same figure the CI runner reported. Bundling every client module of each build once, deduplicated, measured 1494 and 1509 KB gz: the release adds about 15 KB of new client code (the CSV import card, the activity calendar and its goals sheet, the source provenance view, the efficacy summary, the training load and lab trend cards, the settings sheet on phones) and no dependency. The rest is the double count described at v1.37.27 and v1.40.0, attributed module by module from source maps: the shared destructive-control confirmation now used by fifteen more settings sections sits in 26 chunks instead of 11, and the two new routes (/settings/modules/[module] and /insights/sources) each carry their own copy of the record-settings gate's on-demand chunks, the date field and its popover, the settings shell and the type label map. Two reductions land beside the raise. The value domain read the plausibility band out of the measurement validation module, which put Zod into the first load of every metric sub-page; the band lives in a Zod-free module now and about fifty metric routes fell by 88.5 KB gz each (/insights/pulse 428 to 339). The arrangement pages imported their module link card from the file that holds the labs, mood and injection-site settings and shipped all three; the card has its own module (/settings/layout/[module] 391 to 380). Across the 108 pages the summed first load fell from 39998 to 35895 KB gz; 48 pages fell by more than 1 KB, 13 grew by more than 1 KB and none by more than 9 (/insights/workouts with the training load card, /enroll-mfa with the sessions and trusted devices that moved into the security section). The aggregate moved the other way, 3863 to 3875: the band is a small module now copied into each chunk group that reads it, where it used to ride in the one shared schema chunk. The five watched routes stay inside their allowances (/ 359, /measurements 339, /insights/mood 337, /insights 323, /settings/[section] 311 KB gz). The aggregate cap moves from 3755 to 3900, about 25 KB above the build, the same headroom as before; per-route baselines are unchanged.", - "routeBaselineKbGz": { - "/page": 355, - "/insights/page": 323, - "/measurements/page": 335, - "/insights/mood/page": 334, - "/settings/[section]/page": 307 - }, + "$comment": "Client-bundle budgets, enforced by scripts/check-bundle-budget.mjs --check after `pnpm build` in CI; the script header describes the method. Numbers are KB gzip. routeBaselineKbGz is the measured first load of every page (the shared rootMainFiles plus the page's client-reference chunks, each file counted once by content hash), written with --write-baselines; a page fails when it is more than routeDriftAllowanceKbGz over its baseline, and a page without a baseline fails until one is written in the same change. firstLoadP95KbGz and firstLoadMaxKbGz cap the distribution across pages. uniqueClientKbGz caps the distinct client code: every module counted once by its module id, the lazy message catalogs excluded. maxCatalogKbGz holds each catalog, maxRechartsChunks the one shared chart chunk. The summed-chunk aggregate the gate used up to v1.44.0 (3875 KB there, capped at 3900) is still printed but no longer gated: it counted a module once per chunk group it landed in, so it rose whenever a page moved code into on-demand chunks and needed a raise in most releases while no reader downloaded more. Re-based at v1.45.0 from the v1.44.0 build, measured on a local arm64 machine: 110 pages, first load p50 336, p95 437, max 451 KB; unique client code 1904 KB in 1224 modules. The five pages watched before keep their numbers within a kilobyte (/ 359, /measurements 339, /insights/mood 337, /insights 323, /settings/[section] 311), and the metric pages that no watched route covered before are gated now too (/insights/weight 339, /insights/pulse 339). Caps: p95 447 and max 461 KB (10 KB over the measurement), unique client code 1930 KB (about 25 KB, roughly two releases of new client code). A raise or a re-base is a reviewed change with the measured numbers and the reason written here; earlier re-bases are in the history of this file.", "routeDriftAllowanceKbGz": 20, - "totalClientKbGz": 3900, + "firstLoadP95KbGz": 447, + "firstLoadMaxKbGz": 461, + "uniqueClientKbGz": 1930, "maxRechartsChunks": 1, - "maxCatalogKbGz": 210 + "maxCatalogKbGz": 210, + "routeBaselineKbGz": { + "/_global-error/page": 133, + "/_not-found/page": 282, + "/about/page": 282, + "/achievements/page": 292, + "/admin/[section]/page": 451, + "/admin/page": 297, + "/auth/claim/page": 298, + "/auth/login/page": 290, + "/auth/register/page": 288, + "/c/[token]/page": 284, + "/checkups/page": 365, + "/checkups/practitioners/page": 289, + "/claim/[token]/page": 282, + "/coach/conversations/page": 294, + "/coach/page": 355, + "/coach/plans/page": 288, + "/confirm-access/page": 287, + "/custom-metrics/[id]/page": 303, + "/custom-metrics/[id]/values/page": 311, + "/cycle/page": 412, + "/documents/page": 378, + "/enroll-mfa/page": 305, + "/illness/[id]/page": 447, + "/illness/page": 421, + "/insights/active-energy/page": 336, + "/insights/audio-events/page": 337, + "/insights/blood-glucose/page": 340, + "/insights/blood-pressure/page": 336, + "/insights/bmi/page": 331, + "/insights/body-fat/page": 336, + "/insights/body-temperature/page": 336, + "/insights/body-water/page": 336, + "/insights/bone-mass/page": 336, + "/insights/breathing-disturbances/page": 337, + "/insights/cardio-fitness/page": 336, + "/insights/cardio-recovery/page": 337, + "/insights/catalog/page": 317, + "/insights/daylight/page": 336, + "/insights/double-support-time/page": 336, + "/insights/ecg/[id]/page": 316, + "/insights/ecg/page": 316, + "/insights/environmental-audio/page": 337, + "/insights/falls/page": 336, + "/insights/fat-free-mass/page": 336, + "/insights/fat-mass/page": 336, + "/insights/flights-climbed/page": 336, + "/insights/grip-strength/page": 337, + "/insights/headphone-audio/page": 336, + "/insights/health-score/page": 315, + "/insights/hrv/page": 337, + "/insights/lean-body-mass/page": 336, + "/insights/medications/page": 338, + "/insights/mood/page": 337, + "/insights/muscle-mass/page": 337, + "/insights/nutrients/page": 319, + "/insights/oxygen/page": 337, + "/insights/page": 323, + "/insights/pain/page": 336, + "/insights/pulse-wave-velocity/page": 336, + "/insights/pulse/page": 339, + "/insights/recovery/page": 330, + "/insights/respiratory-rate/page": 336, + "/insights/resting-pulse/page": 337, + "/insights/scores/[metric]/page": 328, + "/insights/six-minute-walk/page": 336, + "/insights/skin-temperature/page": 338, + "/insights/sleep/page": 337, + "/insights/sources/page": 321, + "/insights/stair-ascent-speed/page": 336, + "/insights/stair-descent-speed/page": 336, + "/insights/step-length/page": 336, + "/insights/steps/page": 337, + "/insights/values/[type]/page": 350, + "/insights/vascular-age/page": 337, + "/insights/visceral-fat/page": 336, + "/insights/waist-to-height/page": 336, + "/insights/waist/page": 336, + "/insights/walking-asymmetry/page": 336, + "/insights/walking-distance/page": 336, + "/insights/walking-heart-rate/page": 337, + "/insights/walking-speed/page": 336, + "/insights/walking-steadiness/page": 336, + "/insights/weight/page": 339, + "/insights/workouts/[id]/page": 437, + "/insights/workouts/page": 437, + "/insights/wrist-temperature/page": 336, + "/invite/[token]/page": 282, + "/labs/[biomarkerId]/page": 327, + "/labs/[biomarkerId]/values/page": 335, + "/labs/page": 423, + "/measurements/page": 339, + "/medications/[id]/history/page": 283, + "/medications/[id]/page": 402, + "/medications/new/page": 282, + "/medications/page": 355, + "/mental-wellbeing/page": 297, + "/mood/page": 438, + "/notifications/page": 289, + "/onboarding/[step]/page": 445, + "/onboarding/handover/page": 295, + "/onboarding/page": 286, + "/page": 359, + "/privacy/page": 282, + "/profile/page": 285, + "/settings/[section]/page": 311, + "/settings/layout/[module]/page": 380, + "/settings/modules/[module]/page": 374, + "/settings/page": 282, + "/timeline/page": 331, + "/vaccinations/page": 327 + } } diff --git a/scripts/check-bundle-budget.mjs b/scripts/check-bundle-budget.mjs index e92589d79..e88adbc56 100644 --- a/scripts/check-bundle-budget.mjs +++ b/scripts/check-bundle-budget.mjs @@ -2,55 +2,90 @@ /** * Client-bundle report + budget gate for the Turbopack build. * - * The webpack-era tooling died on Next 16: `ANALYZE=1` is a silent no-op - * under Turbopack and the old `.next/analyze/client.json` reader had - * nothing to read — so bundle regressions (a statically imported message - * catalog, a duplicated recharts chunk group) shipped invisibly. This - * script derives the signal straight from the build output instead: + * What a reader downloads is a set of chunk files: the shared + * `rootMainFiles` from `build-manifest.json` plus the chunks a page's + * `*_client-reference-manifest.js` names. This script measures that set per + * page and gates it, and separately measures how much distinct client code + * the build holds. * - * - per-route client-JS totals (gzip) from the Turbopack - * `*_client-reference-manifest.js` files plus the shared - * `rootMainFiles` baseline from `build-manifest.json`; - * - the total gzip weight of every emitted client chunk; - * - a recharts-duplication guard: the number of chunks carrying the - * recharts library fingerprint must stay exactly 1 (the shared - * chart-runtime chunk group); - * - a catalog guard: no message-catalog-fingerprinted chunk may be - * referenced by any route's client-reference manifest or by the - * shared baseline (catalogs load only lazily / via /i18n/). + * METHOD + * + * First load per page. The union of `rootMainFiles` and the page's + * client-reference chunks, deduplicated by content hash (sha-256 of the + * file), each file gzipped at level 9 and summed. A file that two + * manifests list under different names counts once. Every page in the + * build is measured, not a hand-picked few: a heavy import into a page + * nobody watched is how Zod sat in fifty metric pages for three months. + * + * Across pages. p50, p95 and max of the per-page first loads. + * + * Unique client code. Every emitted chunk is evaluated in a `node:vm` + * sandbox with a stub `TURBOPACK` array, which collects the module + * factories the chunk registers without running any of them. Each factory + * is keyed by its Turbopack module id, which is global to the build, so a + * module the bundler copied into forty chunk groups counts once. (Not by + * the factory's text: each chunk is minified on its own, so the copies of + * one module differ in their mangled local names.) One copy per module, + * from the first chunk in file-name order, without the lazy message + * catalogs, is concatenated in id order and gzipped once. A file that + * registers no factories (the runtime, the polyfills) counts as one unit + * by its content hash. + * + * The old aggregate (every emitted chunk gzipped and summed) is still + * printed, for continuity, but no longer gated: it counts a module once + * per chunk group it lands in, so it rose every time a page got lighter + * by moving code into on-demand chunks. + * + * GATES (bundle-budget.json) + * + * - routeBaselineKbGz: measured first load per page; a page fails when it + * is more than routeDriftAllowanceKbGz over its baseline, or when it is + * a page with no baseline (write one with --write-baselines). + * - firstLoadP95KbGz / firstLoadMaxKbGz: caps on the distribution. + * - uniqueClientKbGz: cap on the distinct client code. + * - maxCatalogKbGz: per lazy message catalog. + * - maxRechartsChunks: chunks carrying the Recharts fingerprint (one, the + * shared chart-runtime group). + * - no message catalog referenced by a page or the shared baseline. * * Usage: - * pnpm bundle-report # print the report table - * pnpm bundle-report --check # enforce bundle-budget.json (CI gate) + * pnpm bundle-report # print the report + * pnpm bundle-check # enforce bundle-budget.json + * node scripts/check-bundle-budget.mjs --write-baselines + * # rewrite routeBaselineKbGz from + * # this build (a reviewed change) + * node scripts/check-bundle-budget.mjs --dir [--json ] * - * Budgets live in `bundle-budget.json` (repo root). Numbers are KB gzip. - * - * Per-route the file states a MEASURED baseline and one drift allowance - * over it, and a route fails on the delta rather than on a ceiling it is - * already touching. Flat ceilings had been re-stated but never re-based, so - * three routes ended up sitting within a kilobyte of theirs and any - * unrelated dependency bump failed the e2e job on a regression nobody had - * made. The delta is printed on every run, so a route that has crept most of - * the way through its allowance is readable in the log before it is red. - * - * The gate still exists to catch step changes (a ~100 KB statically imported - * catalog, a ~90 KB duplicate library), which are several times the - * allowance. Re-base consciously, in the same PR that pays the cost, with - * the reason written into `$comment`. + * Numbers are KB gzip. A local arm64 build measures each page about 1.5 KB + * lighter than the CI runner; the drift allowance covers that and ordinary + * week-to-week movement, while the step changes the gate exists for (a + * statically imported catalog, a duplicated chart library, Zod on a page + * that had none: 60 to 100 KB each) are several times larger. */ -import { readFileSync, readdirSync, existsSync } from "node:fs"; +import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs"; +import { createHash } from "node:crypto"; import { join } from "node:path"; +import vm from "node:vm"; import { gzipSync } from "node:zlib"; -const CHECK = process.argv.includes("--check"); -const BUILD_DIR = join(process.cwd(), ".next"); +const argv = process.argv.slice(2); +const flag = (name) => argv.includes(name); +const option = (name) => { + const i = argv.indexOf(name); + return i >= 0 ? argv[i + 1] : undefined; +}; + +const CHECK = flag("--check"); +const WRITE_BASELINES = flag("--write-baselines"); +const BUILD_DIR = option("--dir") ?? join(process.cwd(), ".next"); +const JSON_OUT = option("--json"); const APP_DIR = join(BUILD_DIR, "server", "app"); const BUDGET_PATH = join(process.cwd(), "bundle-budget.json"); if (!existsSync(join(BUILD_DIR, "build-manifest.json"))) { console.error( - "check-bundle-budget: no .next/build-manifest.json — run `pnpm build` first.", + `check-bundle-budget: no ${join(BUILD_DIR, "build-manifest.json")} — run \`pnpm build\` first.`, ); process.exit(2); } @@ -58,30 +93,47 @@ if (!existsSync(join(BUILD_DIR, "build-manifest.json"))) { /** Recharts library fingerprint (present once in the shared runtime chunk). */ const RECHARTS_MARK = "CartesianGrid"; /** - * Message-catalog fingerprint. The key set is identical across all six - * locales, so a KEY-position match ("typeTimeInDaylight" directly followed - * by a colon, in either the plain or the JSON.parse-escaped chunk encoding) - * marks a bundled catalog. Source modules only ever carry the namespaced - * call-site form (`measurements.typeTimeInDaylight`), which matches neither. + * Message-catalog fingerprint. The key set is identical across all locales, + * so a KEY-position match ("typeTimeInDaylight" directly followed by a colon, + * in either the plain or the JSON.parse-escaped chunk encoding) marks a + * bundled catalog. Source modules only ever carry the namespaced call-site + * form (`measurements.typeTimeInDaylight`), which matches neither. */ const CATALOG_MARKS = ['"typeTimeInDaylight":', 'typeTimeInDaylight\\":']; -const sizeCache = new Map(); +const sha = (buf) => createHash("sha256").update(buf).digest("hex"); +const gz = (buf) => gzipSync(buf, { level: 9 }).length; +const kb = (bytes) => bytes / 1024; +const fmt = (bytes) => `${kb(bytes).toFixed(0)} KB`; +const round1 = (n) => Math.round(n * 10) / 10; + +const fileCache = new Map(); function chunkInfo(file) { - if (!sizeCache.has(file)) { + if (!fileCache.has(file)) { + let info = { + hash: `missing:${file}`, + raw: 0, + gz: 0, + recharts: false, + catalog: false, + }; try { const buf = readFileSync(join(BUILD_DIR, file)); - sizeCache.set(file, { + info = { + hash: sha(buf), raw: buf.length, - gz: gzipSync(buf, { level: 9 }).length, + gz: gz(buf), recharts: buf.includes(RECHARTS_MARK), catalog: CATALOG_MARKS.some((m) => buf.includes(m)), - }); + buf, + }; } catch { - sizeCache.set(file, { raw: 0, gz: 0, recharts: false, catalog: false }); + // A manifest naming a file the build did not emit: counted as zero and + // reported by its missing hash rather than crashing the report. } + fileCache.set(file, info); } - return sizeCache.get(file); + return fileCache.get(file); } function routeChunks(manifestFile) { @@ -97,24 +149,54 @@ function routeChunks(manifestFile) { return files; } -function findRouteManifests(dir, prefix = "") { +function findPageManifests(dir, prefix = "") { let out = []; for (const entry of readdirSync(dir, { withFileTypes: true })) { const p = join(dir, entry.name); if (entry.isDirectory()) { - out = out.concat(findRouteManifests(p, `${prefix}/${entry.name}`)); - } else if (entry.name.endsWith("_client-reference-manifest.js")) { - out.push({ - route: `${prefix}/${entry.name.replace("_client-reference-manifest.js", "")}`, - file: p, - }); + out = out.concat(findPageManifests(p, `${prefix}/${entry.name}`)); + } else if (entry.name === "page_client-reference-manifest.js") { + out.push({ route: `${prefix}/page`, file: p }); } } return out; } -const kb = (bytes) => bytes / 1024; -const fmt = (bytes) => `${kb(bytes).toFixed(0)} KB`; +function percentile(sorted, p) { + if (sorted.length === 0) return 0; + const i = Math.min( + sorted.length - 1, + Math.ceil((p / 100) * sorted.length) - 1, + ); + return sorted[Math.max(0, i)]; +} + +/** + * The `[module id, factory source]` pairs a Turbopack chunk registers, + * without running them: `TURBOPACK.push([script, id, factory, id, factory, + * …])`. Null when the file is not in that shape. + */ +function chunkFactories(buf) { + const sandbox = { TURBOPACK: [] }; + sandbox.globalThis = sandbox; + sandbox.self = sandbox; + try { + vm.runInNewContext(buf.toString("utf8"), sandbox, { timeout: 5000 }); + } catch { + return null; + } + const modules = []; + for (const entry of sandbox.TURBOPACK) { + if (!Array.isArray(entry)) continue; + for (let i = 1; i < entry.length; i++) { + if (typeof entry[i] !== "function") continue; + const id = entry[i - 1]; + if (typeof id !== "number" && typeof id !== "string") continue; + modules.push([String(id), entry[i].toString()]); + } + } + return modules.length > 0 ? modules : null; +} // ── Shared baseline ───────────────────────────────────────────────────────── const buildManifest = JSON.parse( @@ -123,88 +205,135 @@ const buildManifest = JSON.parse( const rootFiles = buildManifest.rootMainFiles ?? []; let rootGz = 0; for (const f of rootFiles) rootGz += chunkInfo(f).gz; +const rootCatalogRef = rootFiles.some((f) => chunkInfo(f).catalog); -// ── Per-route totals ──────────────────────────────────────────────────────── -const manifests = findRouteManifests(APP_DIR); -const routeTotals = new Map(); -for (const { route, file } of manifests) { - let gz = 0; +// ── Per-page first load, deduplicated by content hash ────────────────────── +const pages = new Map(); +for (const { route, file } of findPageManifests(APP_DIR)) { + const byHash = new Map(); let catalogRef = false; - for (const chunk of routeChunks(file)) { + for (const chunk of [...rootFiles, ...routeChunks(file)]) { const info = chunkInfo(chunk); - gz += info.gz; - if (info.catalog) catalogRef = true; + byHash.set(info.hash, info.gz); + if (info.catalog && !rootFiles.includes(chunk)) catalogRef = true; } - routeTotals.set(route, { gz: gz + rootGz, catalogRef }); + let total = 0; + for (const size of byHash.values()) total += size; + pages.set(route, { gz: total, catalogRef }); } +const firstLoads = [...pages.values()].map((p) => p.gz).sort((a, b) => a - b); +const p50 = percentile(firstLoads, 50); +const p95 = percentile(firstLoads, 95); +const max = firstLoads[firstLoads.length - 1] ?? 0; -// ── Whole-build chunk scan ────────────────────────────────────────────────── +// ── Whole build: old aggregate, catalogs, recharts, unique modules ───────── const chunkDir = join(BUILD_DIR, "static", "chunks"); -let totalGz = 0; +let summedGz = 0; let rechartsChunks = 0; let catalogChunks = 0; let catalogGz = 0; let largestCatalog = { file: "-", gz: 0 }; -let largest = { file: "-", gz: 0 }; -for (const f of readdirSync(chunkDir)) { +const uniqueUnits = new Map(); +for (const f of readdirSync(chunkDir).sort()) { if (!f.endsWith(".js")) continue; const info = chunkInfo(join("static", "chunks", f)); - totalGz += info.gz; + summedGz += info.gz; if (info.recharts) rechartsChunks += 1; if (info.catalog) { catalogChunks += 1; catalogGz += info.gz; if (info.gz > largestCatalog.gz) largestCatalog = { file: f, gz: info.gz }; + continue; + } + const modules = info.buf ? chunkFactories(info.buf) : null; + if (modules) { + for (const [id, source] of modules) { + if (!uniqueUnits.has(`m:${id}`)) uniqueUnits.set(`m:${id}`, source); + } + } else if (info.buf) { + uniqueUnits.set(`f:${info.hash}`, info.buf.toString("utf8")); } - if (info.gz > largest.gz) largest = { file: f, gz: info.gz }; } -const rootCatalogRef = rootFiles.some((f) => chunkInfo(f).catalog); +const uniqueKeys = [...uniqueUnits.keys()].sort(); +const uniqueGz = gz( + Buffer.from(uniqueKeys.map((k) => uniqueUnits.get(k)).join("\n"), "utf8"), +); // ── Report ────────────────────────────────────────────────────────────────── const budget = existsSync(BUDGET_PATH) ? JSON.parse(readFileSync(BUDGET_PATH, "utf8")) : null; +const baselines = budget?.routeBaselineKbGz ?? {}; +const allowanceKb = budget?.routeDriftAllowanceKbGz ?? 0; +console.log(`pages measured: ${pages.size}`); console.log(`shared baseline (rootMainFiles): ${fmt(rootGz)} gz`); -console.log(`all client chunks: ${fmt(totalGz)} gz`); console.log( - ` lazy message catalogs: ${fmt(catalogGz)} gz (${catalogChunks} chunks)`, -); -console.log(` other client chunks: ${fmt(totalGz - catalogGz)} gz`); -console.log( - ` largest message catalog: ${fmt(largestCatalog.gz)} gz (${largestCatalog.file})`, + `first load across pages: p50 ${fmt(p50)}, p95 ${fmt(p95)}, max ${fmt(max)} gz`, ); console.log( - `largest chunk: ${fmt(largest.gz)} gz ${largest.file}`, + `unique client code: ${fmt(uniqueGz)} gz (${uniqueKeys.length} distinct modules, catalogs excluded)`, +); +console.log( + `lazy message catalogs: ${fmt(catalogGz)} gz (${catalogChunks} chunks, largest ${fmt(largestCatalog.gz)} ${largestCatalog.file})`, +); +console.log( + `summed chunks, not gated: ${fmt(summedGz - catalogGz)} gz (every emitted chunk once, catalogs excluded)`, ); console.log(`recharts-fingerprint chunks: ${rechartsChunks}`); console.log(""); -const baselines = budget?.routeBaselineKbGz ?? {}; -const allowanceKb = budget?.routeDriftAllowanceKbGz ?? 0; -/** The ceiling a route's measured baseline plus the drift allowance buys it. */ -const capFor = (route) => - baselines[route] == null ? null : baselines[route] + allowanceKb; - -const watched = Object.keys(baselines); -const rows = [...routeTotals.entries()] - .filter(([route]) => !watched.length || watched.includes(route)) - .sort((a, b) => b[1].gz - a[1].gz); -for (const [route, { gz }] of rows.slice(0, watched.length || 15)) { - const baseline = baselines[route]; - if (baseline == null) { - console.log(`${fmt(gz).padStart(8)} gz ${route}`); - continue; - } - const delta = kb(gz) - baseline; - const sign = delta >= 0 ? "+" : "−"; +const rows = [...pages.entries()] + .map(([route, { gz: size }]) => { + const baseline = baselines[route]; + return { + route, + kb: round1(kb(size)), + baseline: baseline ?? null, + delta: baseline == null ? null : round1(kb(size) - baseline), + }; + }) + .sort((a, b) => (b.delta ?? Infinity) - (a.delta ?? Infinity) || b.kb - a.kb); +console.log("largest movers against their baseline:"); +for (const r of rows.slice(0, 15)) { + const delta = + r.delta == null + ? "no baseline" + : `${r.delta >= 0 ? "+" : "−"}${Math.abs(r.delta).toFixed(1)} KB of ${allowanceKb}`; console.log( - `${fmt(gz).padStart(8)} gz ${route} (baseline ${baseline} KB, ` + - `${sign}${Math.abs(delta).toFixed(1)} KB of ${allowanceKb} KB allowed, ` + - `cap ${capFor(route)} KB)`, + `${`${r.kb.toFixed(0)} KB`.padStart(8)} gz ${r.route} (${delta})`, ); } +const report = { + pages: Object.fromEntries(rows.map((r) => [r.route, r.kb])), + firstLoadKbGz: { + p50: round1(kb(p50)), + p95: round1(kb(p95)), + max: round1(kb(max)), + }, + uniqueClientKbGz: round1(kb(uniqueGz)), + summedChunksKbGz: round1(kb(summedGz - catalogGz)), + catalogKbGz: round1(kb(catalogGz)), + largestCatalogKbGz: round1(kb(largestCatalog.gz)), + rechartsChunks, +}; +if (JSON_OUT) writeFileSync(JSON_OUT, `${JSON.stringify(report, null, 2)}\n`); + +if (WRITE_BASELINES) { + if (!budget) { + console.error("check-bundle-budget: bundle-budget.json missing."); + process.exit(2); + } + budget.routeBaselineKbGz = Object.fromEntries( + rows + .map((r) => [r.route, Math.round(r.kb)]) + .sort(([a], [b]) => a.localeCompare(b)), + ); + writeFileSync(BUDGET_PATH, `${JSON.stringify(budget, null, 2)}\n`); + console.log(`\nwrote ${rows.length} page baselines to bundle-budget.json`); +} + if (!CHECK) process.exit(0); // ── Budget gate ───────────────────────────────────────────────────────────── @@ -212,42 +341,48 @@ if (!budget) { console.error("check-bundle-budget: bundle-budget.json missing."); process.exit(2); } - -// A budget file that states no baselines gates nothing, and would do it -// quietly — the loop below would simply have nothing to walk. Say so instead. -if (watched.length === 0) { +// A budget that states no page baselines gates nothing, and would do it +// quietly. A build with no pages is a broken build, not a light one. +if (Object.keys(baselines).length === 0 || pages.size === 0) { console.error( - "check-bundle-budget: bundle-budget.json states no routeBaselineKbGz " + - "entries, so no route is gated. Measure the eager routes and write " + - "them in, with a routeDriftAllowanceKbGz over them.", + "check-bundle-budget: no page baselines in bundle-budget.json, or no " + + "pages in the build. Measure them with --write-baselines.", ); process.exit(2); } const failures = []; -for (const [route, baselineKb] of Object.entries(baselines)) { - const actual = routeTotals.get(route); - if (!actual) { - failures.push(`route ${route} not found in the build output`); - continue; - } - const delta = kb(actual.gz) - baselineKb; - if (delta > allowanceKb) { +for (const r of rows) { + if (r.baseline == null) { failures.push( - `${route}: ${fmt(actual.gz)} gz is ${delta.toFixed(1)} KB over the ` + - `${baselineKb} KB baseline, past the ${allowanceKb} KB drift ` + - `allowance (cap ${capFor(route)} KB)`, + `${r.route}: ${r.kb} KB gz and no baseline. A new page gets one with --write-baselines in the same change.`, + ); + } else if (r.delta > allowanceKb) { + failures.push( + `${r.route}: ${r.kb} KB gz is ${r.delta.toFixed(1)} KB over its ${r.baseline} KB baseline, past the ${allowanceKb} KB drift allowance`, ); } } -// The aggregate leaves the lazy message catalogs out. A reader downloads one -// catalog, the one for their language, so counting all of them made every new -// interface language look like a regression. Each catalog is held to its own -// ceiling instead. -const nonCatalogGz = totalGz - catalogGz; -if (budget.totalClientKbGz && kb(nonCatalogGz) > budget.totalClientKbGz) { +for (const route of Object.keys(baselines)) { + if (!pages.has(route)) { + failures.push( + `${route} has a baseline but is not in the build; remove it with --write-baselines`, + ); + } +} +if (budget.firstLoadP95KbGz && kb(p95) > budget.firstLoadP95KbGz) { failures.push( - `client JS without the lazy catalogs ${fmt(nonCatalogGz)} gz exceeds the ${budget.totalClientKbGz} KB budget`, + `first-load p95 ${kb(p95).toFixed(1)} KB gz exceeds the ${budget.firstLoadP95KbGz} KB budget`, + ); +} +if (budget.firstLoadMaxKbGz && kb(max) > budget.firstLoadMaxKbGz) { + failures.push( + `largest first load ${kb(max).toFixed(1)} KB gz exceeds the ${budget.firstLoadMaxKbGz} KB budget`, + ); +} +if (budget.uniqueClientKbGz && kb(uniqueGz) > budget.uniqueClientKbGz) { + failures.push( + `unique client code ${kb(uniqueGz).toFixed(1)} KB gz exceeds the ${budget.uniqueClientKbGz} KB budget`, ); } if (budget.maxCatalogKbGz && kb(largestCatalog.gz) > budget.maxCatalogKbGz) { @@ -263,12 +398,12 @@ if ( `${rechartsChunks} recharts-fingerprint chunks (budget ${budget.maxRechartsChunks}) — a chart import bypassed the shared chart-runtime boundary`, ); } -const catalogRefRoutes = [...routeTotals.entries()] +const catalogRefPages = [...pages.entries()] .filter(([, v]) => v.catalogRef) .map(([r]) => r); -if (catalogRefRoutes.length > 0 || rootCatalogRef) { +if (catalogRefPages.length > 0 || rootCatalogRef) { failures.push( - `message catalog statically referenced by ${rootCatalogRef ? "the shared baseline" : catalogRefRoutes.join(", ")} — catalogs must stay lazy (/i18n/ + dynamic import)`, + `message catalog statically referenced by ${rootCatalogRef ? "the shared baseline" : catalogRefPages.join(", ")} — catalogs must stay lazy (/i18n/ + dynamic import)`, ); } From 375ca12b058009765596574a2805e3eeebddd3fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 22 Oct 2026 05:38:36 +0200 Subject: [PATCH 6/9] perf(bench): add a large-account seed and a request benchmark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit scripts/bench/seed-large-account.ts (moved from scripts/, extended) writes one synthetic account of ten years: 1,265,561 measurement rows across the main types and five sources (Apple Health dense spot rows, Withings blood pressure and scale, a weekly manual weight, three years of Oura nightly values, 90 days of Nightscout CGM), `stats:` and per-sample cumulative rows, nightly sleep stages, 3,020 workouts, 1,095 mood entries and three medications with 3,285 intake events. Everything goes in through set-based INSERT … SELECT generate_series; values and timestamps are functions of the row index and `--anchor`, so a run is reproducible. The rollup tier is folded with recomputeUserRollups, as the boot backfill does, and the tables are analysed. About 80 s on the Mac mini. scripts/bench/run.ts boots the production build once with every role (so the queues exist), then measures against a web-only process under the production heap ceiling: p50, p95 and max latency and SQL statements per request (pg_stat_statements, read once nothing is in flight and the count holds still) for /api/auth/me, the dashboard summary and snapshot, the pulse series over 30, 365 and 3650 days, the day view, insights health status and correlations, each cold (caches evicted by a one-row manual-sync ingest before every request) and warm, plus a 5,000-row batch ingest as ten requests of 500. It writes JSON and a markdown table. docs/contributing/benchmarks.md describes the setup against a throwaway Postgres in Docker with the production pg_stat_statements settings. --- docs/README.md | 3 + docs/contributing/benchmarks.md | 83 +++ scripts/README.md | 1 + scripts/bench/run.ts | 478 ++++++++++++++ scripts/bench/seed-large-account.ts | 659 +++++++++++++++++++ scripts/seed-large-account.ts | 296 --------- tests/integration/large-account-jobs.test.ts | 2 +- 7 files changed, 1225 insertions(+), 297 deletions(-) create mode 100644 docs/contributing/benchmarks.md create mode 100644 scripts/bench/run.ts create mode 100644 scripts/bench/seed-large-account.ts delete mode 100644 scripts/seed-large-account.ts diff --git a/docs/README.md b/docs/README.md index aabff21cc..5ade54148 100644 --- a/docs/README.md +++ b/docs/README.md @@ -59,6 +59,9 @@ notes, and machine-readable specs that ship alongside the source: - [`contributing/ui-standards.md`](./contributing/ui-standards.md) — the binding UI standards: card anatomy, spacing, text colour, tokens and which primitive to use where. Read before touching any surface. +- [`contributing/benchmarks.md`](./contributing/benchmarks.md) — the + large-account benchmark (latency and statements per request) and the + statement budgets on the hot routes. - [`ui-guidelines.md`](./ui-guidelines.md) — UI / a11y house style; where it disagrees with the UI standards, the standards win. diff --git a/docs/contributing/benchmarks.md b/docs/contributing/benchmarks.md new file mode 100644 index 000000000..62b16f33f --- /dev/null +++ b/docs/contributing/benchmarks.md @@ -0,0 +1,83 @@ +# Benchmarks + +Two tools measure the server's read and write paths. Use them before and after any change to a hot path, and put both numbers in the PR. + +- **`scripts/bench/run.ts`** measures latency (p50, p95, max) and SQL statements per request against a seeded account of about 1.26 million measurement rows, through the production build. It is a local tool, not a CI gate: latency depends on the machine. +- **`tests/integration/query-budget-hot-routes.test.ts`** holds a statement budget per hot route on a small fixture. Statement counts are exact on a fixed fixture, so this one is a gate: a change that adds a query per row, or a second read of something already loaded, fails it. + +## The large-account benchmark + +### 1. A throwaway Postgres with pg_stat_statements + +Never point the seed or the benchmark at a database that holds real data. They write under a synthetic user, and the seed's `--reset` deletes that user with everything it owns. + +```sh +docker run -d --name hl-bench-db --shm-size=256m \ + -e POSTGRES_USER=bench -e POSTGRES_PASSWORD=bench -e POSTGRES_DB=healthlog_bench \ + -p 55432:5432 postgres:16.15-alpine \ + postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=top +``` + +The image and the two settings are the ones `docker-compose.yml` runs in production. + +### 2. Environment, migrations, seed + +```sh +export DATABASE_URL="postgresql://bench:bench@127.0.0.1:55432/healthlog_bench?schema=public" +export ENCRYPTION_KEYS='{"v1":"<64 hex>"}' ENCRYPTION_ACTIVE_KEY_ID=v1 +export API_TOKEN_HMAC_KEY="<64 hex>" + +pnpm db:migrate:deploy +pnpm dlx tsx scripts/bench/seed-large-account.ts --reset --anchor 2026-10-22 +``` + +Any throwaway key values work, as long as the seed, the benchmark and the server see the same ones. + +The seed writes ten years for one user: Apple Health dense rows (heart rate about every ten minutes, HRV, SpO2, respiratory rate, gait, audio), Withings blood pressure, pulse and scale, a weekly manual weight, three years of Oura resting heart rate and RMSSD, 90 days of Nightscout CGM, `stats:` and per-sample cumulative rows, nightly sleep stages, about 3,000 workouts, three years of mood and of three medications with their intake events. It then folds the rollup tier the way the boot backfill does and runs `ANALYZE`. It takes about 80 seconds on the Mac mini. + +The seed is deterministic for a given `--anchor`: values and timestamps are functions of the row index and that day. Without `--anchor` it anchors on today, which is what you want when the benchmark should see recent data. + +### 3. A production build + +```sh +pnpm build +``` + +### 4. Run + +```sh +pnpm dlx tsx scripts/bench/run.ts --out scripts/bench/results/