diff --git a/e2e/a11y.spec.ts b/e2e/a11y.spec.ts index 048c116fd..fa433f35a 100644 --- a/e2e/a11y.spec.ts +++ b/e2e/a11y.spec.ts @@ -116,7 +116,7 @@ test.describe("axe-core authenticated surfaces", () => { } // The keyboard-only "skip to content" link is fixed at the top-left - // corner of every authenticated page. Marc reported it intercepting + // corner of every authenticated page. The maintainer reported it intercepting // logo clicks in v1.4.14 — the fix combines `pointer-events-none` // with `-translate-y-full` so the element sits offscreen until // focused. Regression guard: with no keyboard focus, the logo's own @@ -127,7 +127,7 @@ test.describe("axe-core authenticated surfaces", () => { }) => { // The desktop sidebar (and its logo) is `hidden md:flex` — the // mobile profile (Pixel 5) hides it by design. The skip-link - // regression Marc originally reported in v1.4.14 was a desktop- + // regression the maintainer originally reported in v1.4.14 was a desktop- // only artifact (mobile uses the topbar logo, which is a // different DOM node). Skip the mobile project so we don't // flake on a contract that doesn't apply there. diff --git a/e2e/admin-api-tokens-mobile.spec.ts b/e2e/admin-api-tokens-mobile.spec.ts index 12fa90d04..08bef6dd2 100644 --- a/e2e/admin-api-tokens-mobile.spec.ts +++ b/e2e/admin-api-tokens-mobile.spec.ts @@ -5,7 +5,7 @@ import { STORAGE_STATE_PATH } from "./setup/global-setup"; /** * v1.4.16 A3 — `/admin/api-tokens` must not produce horizontal page * overflow on a Pixel-5 viewport (393 CSS px). v1.4.15 added column- - * hide breakpoints + tighter card padding, but Marc still saw a + * hide breakpoints + tighter card padding, but the maintainer still saw a * scrollbar on prod. The v1.4.16 fix swaps the desktop `` for a * mobile card-list at `. * @@ -23,7 +23,7 @@ test.describe("/admin/api-tokens mobile no-overflow", () => { }) => { // Mock the admin tokens endpoint with a moderately wide payload — // a token with multiple permissions, a long name, and timestamps. - // This is the realistic worst case Marc hit on prod. + // This is the realistic worst case the maintainer hit on prod. await page.route("**/api/admin/tokens", (route) => route.fulfill({ status: 200, @@ -38,7 +38,7 @@ test.describe("/admin/api-tokens mobile no-overflow", () => { expiresAt: null, createdAt: "2026-05-01T08:00:00Z", revoked: false, - user: { id: "u1", username: "marc-the-very-long-username" }, + user: { id: "u1", username: "user-the-very-long-username" }, }, { id: "tok2", @@ -48,7 +48,7 @@ test.describe("/admin/api-tokens mobile no-overflow", () => { expiresAt: "2026-08-01T00:00:00Z", createdAt: "2026-04-15T08:00:00Z", revoked: false, - user: { id: "u1", username: "marc-the-very-long-username" }, + user: { id: "u1", username: "user-the-very-long-username" }, }, ], error: null, @@ -76,7 +76,7 @@ test.describe("/admin/api-tokens mobile no-overflow", () => { }); // v1.4.18 phase A2 — production probe at Pixel-5 viewport pinned the - // visible "scrollbar" Marc reported for the third time as the + // visible "scrollbar" the maintainer reported for the third time as the // `` mobile section strip, NOT the api-tokens table. The // strip carries 13 admin sections so its scrollWidth (~1700 CSS px) // permanently exceeds clientWidth (~360 CSS px). Adding the @@ -120,7 +120,7 @@ test.describe("/admin/api-tokens mobile no-overflow", () => { }); // v1.4.19 phase A7 (4th attempt) — even with the mobile-strip - // `no-scrollbar` fix from v1.4.18, Marc still reported a painted + // `no-scrollbar` fix from v1.4.18, the maintainer still reported a painted // scrollbar. The card body itself must not contribute any // horizontal-overflow culprit: every element inside the api-tokens // section must satisfy `scrollWidth <= clientWidth + 1`. Pads with @@ -149,7 +149,7 @@ test.describe("/admin/api-tokens mobile no-overflow", () => { revoked: false, user: { id: "u1", - username: "marc-the-very-long-username-that-overflows", + username: "user-the-very-long-username-that-overflows", }, }, ], @@ -166,7 +166,7 @@ test.describe("/admin/api-tokens mobile no-overflow", () => { // Walk every element inside the mobile card-list and assert no // child has a wider scrollWidth than its container. This is the - // Pixel-5 worst-case Marc kept hitting. + // Pixel-5 worst-case the maintainer kept hitting. // // Elements clamped via `truncate` (Tailwind's `overflow:hidden + // text-overflow:ellipsis + white-space:nowrap` shorthand) are by diff --git a/e2e/chart-overlay-controls.spec.ts b/e2e/chart-overlay-controls.spec.ts index ed7fa367a..2c4d5f8ef 100644 --- a/e2e/chart-overlay-controls.spec.ts +++ b/e2e/chart-overlay-controls.spec.ts @@ -9,7 +9,7 @@ import { /** * v1.4.18 — per-chart overlay-controls popover. * - * Marc reverted v1.4.16's always-on chart overlays (gradient fill, + * The maintainer reverted v1.4.16's always-on chart overlays (gradient fill, * personal-baseline line, target-zone shading) and asked for per-chart * switches that the user can flip on or off. This spec exercises the * end-to-end behaviour against the seeded test user: diff --git a/e2e/medications-wizard-daily.spec.ts b/e2e/medications-wizard-daily.spec.ts index 578ce5d89..65b23df62 100644 --- a/e2e/medications-wizard-daily.spec.ts +++ b/e2e/medications-wizard-daily.spec.ts @@ -22,7 +22,7 @@ import { STORAGE_STATE_PATH } from "./setup/global-setup"; * * Walks the 7-step daily path through the modal dialog. The dialog * mounts when `/medications/new` redirects to `/medications?new=1`. - * The DE label surface (Marc's primary locale) is the assertion + * The DE label surface (the maintainer's primary locale) is the assertion * surface — "Schritt N von 7" tracks the visible counter. */ test.describe("medication wizard — daily", () => { diff --git a/e2e/medications-wizard-rolling.spec.ts b/e2e/medications-wizard-rolling.spec.ts index a720476d3..d98fad92b 100644 --- a/e2e/medications-wizard-rolling.spec.ts +++ b/e2e/medications-wizard-rolling.spec.ts @@ -23,7 +23,7 @@ import { STORAGE_STATE_PATH } from "./setup/global-setup"; * Walks the 8-step recurring path with "Flexibel ab letzter * Einnahme" picked on Step 5 and rollingDays = 7 set on Step 6. * Emits `rollingIntervalDays: 7` (no rrule). The cadence row also - * surfaces Marc's literal "Counter startet neu" copy directly below + * surfaces the maintainer's literal "Counter startet neu" copy directly below * the row label — the assertion below pins the phrase so a future * copy edit can't silently drop the rolling explainer. */ diff --git a/e2e/sidebar-admin-no-expand.spec.ts b/e2e/sidebar-admin-no-expand.spec.ts index bc11d5e23..97051932c 100644 --- a/e2e/sidebar-admin-no-expand.spec.ts +++ b/e2e/sidebar-admin-no-expand.spec.ts @@ -5,7 +5,7 @@ import { STORAGE_STATE_PATH } from "./setup/global-setup"; /** * v1.4.16 A1 — Sidebar Admin entry must mirror the Settings entry * exactly: a single link with no sub-item expansion in the global - * sidebar. Marc reported in v1.4.15 that the sidebar was auto-expanding + * sidebar. The maintainer reported in v1.4.15 that the sidebar was auto-expanding * admin sub-items on `/admin/*` and the gravatar dropdown felt linked to * the sidebar — both unwanted UX. The in-shell `` already * renders its own per-section nav inside the page itself. diff --git a/eslint.config.mjs b/eslint.config.mjs index a1d07e8eb..560d86560 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -38,6 +38,12 @@ const eslintConfig = defineConfig([ // requirePublicHost connect-time pin). The wrapper internals and // test files are exempt; see the rule file for the allowlist. "healthlog/safe-fetch-required": "error", + // v1.16.4 — same-origin `/api/...` calls in the client surface + // (src/components + src/app) must route through the typed apiFetch + // wrapper (`src/lib/api/api-fetch.ts`): one `.ok` check, one + // envelope unwrap, one ApiError shape. Wrapper internals and test + // files are exempt; see the rule file for the allowlist. + "healthlog/api-fetch-required": "error", }, }, ]); diff --git a/src/__tests__/api/analytics/since-cap.test.ts b/src/__tests__/api/analytics/since-cap.test.ts index 86259e984..e089b8aab 100644 --- a/src/__tests__/api/analytics/since-cap.test.ts +++ b/src/__tests__/api/analytics/since-cap.test.ts @@ -4,7 +4,7 @@ * * The v1.4.38.7 perf audit (`.planning/round-v1438-perf-analysis.md` * §1 A2) flagged the per-type `fetchMeasurementSeriesChunked` loop - * inside `/api/analytics` as the dominant cost of Marc's 74.6 s cold + * inside `/api/analytics` as the dominant cost of the maintainer's 74.6 s cold * full-slice mount. v1.4.38.8 (commit 8a8150d2) relaxed the * `isFullyCovered` gate inside the three downstream fast-paths so the * walk became unreachable in the common case — but the A2 loop itself @@ -129,7 +129,7 @@ const SESSION_OK = { session: { id: "sess-1", expiresAt: new Date(Date.now() + 3_600_000) }, user: { id: "user-since-1", - username: "marc", + username: "testuser", role: "USER" as const, locale: "en", timezone: "Europe/Berlin", diff --git a/src/__tests__/api/auth/check-user/route.test.ts b/src/__tests__/api/auth/check-user/route.test.ts index b62d18636..7cdd433d0 100644 --- a/src/__tests__/api/auth/check-user/route.test.ts +++ b/src/__tests__/api/auth/check-user/route.test.ts @@ -119,7 +119,7 @@ describe("POST /api/auth/check-user", () => { passwordHash: null, _count: { passkeys: 2 }, } as never); - const res = await POST(makeRequest({ identifier: "marc" })); + const res = await POST(makeRequest({ identifier: "testuser" })); const body = (await res.json()) as CheckUserBody; expect(body.data.branch).toBe("passkey_only"); expect(body.data.hasPasskey).toBe(true); @@ -132,7 +132,7 @@ describe("POST /api/auth/check-user", () => { passwordHash: "argon2id$…", _count: { passkeys: 1 }, } as never); - const res = await POST(makeRequest({ identifier: "marc" })); + const res = await POST(makeRequest({ identifier: "testuser" })); const body = (await res.json()) as CheckUserBody; expect(body.data.branch).toBe("email_fallback"); expect(body.data.hasPasskey).toBe(true); @@ -145,7 +145,7 @@ describe("POST /api/auth/check-user", () => { passwordHash: null, _count: { passkeys: 0 }, } as never); - const res = await POST(makeRequest({ identifier: "marc" })); + const res = await POST(makeRequest({ identifier: "testuser" })); const body = (await res.json()) as CheckUserBody; expect(body.data.branch).toBe("exists"); expect(body.data.hasPasskey).toBe(false); diff --git a/src/__tests__/i18n-call-site-coverage.test.ts b/src/__tests__/i18n-call-site-coverage.test.ts index 3ef042678..8e461b4ae 100644 --- a/src/__tests__/i18n-call-site-coverage.test.ts +++ b/src/__tests__/i18n-call-site-coverage.test.ts @@ -1,7 +1,7 @@ /** * v1.4.49.3 — call-site i18n coverage guard. * - * Marc surfaced raw `insights.relativeHoursAgo`, `notifications.event + * The maintainer surfaced raw `insights.relativeHoursAgo`, `notifications.event * MoodReminder`, and `notifications.eventMoodReminderDesc` strings * leaking into the UI in v1.4.49. Audit found 28 keys called from real * code that never existed in any locale bundle — silent regressions diff --git a/tests/integration/dashboard-widgets-save.test.ts b/tests/integration/dashboard-widgets-save.test.ts index 1c774b107..12122f694 100644 --- a/tests/integration/dashboard-widgets-save.test.ts +++ b/tests/integration/dashboard-widgets-save.test.ts @@ -339,7 +339,7 @@ describe("PUT /api/dashboard/widgets — Save persists full layouts", () => { const { userId } = await seedUser(); const { PUT, GET } = await import("@/app/api/dashboard/widgets/route"); - // Marc flips the sleep widget's chart visibility on. + // The maintainer flips the sleep widget's chart visibility on. const body = buildClientPayload({ chartOverlayPrefs: { bp: { diff --git a/tests/integration/doctor-report-sections.test.ts b/tests/integration/doctor-report-sections.test.ts index a905ce4f6..f702eabd8 100644 --- a/tests/integration/doctor-report-sections.test.ts +++ b/tests/integration/doctor-report-sections.test.ts @@ -230,7 +230,7 @@ describe("doctor-report — per-section toggles", () => { const data = await collectDoctorReportData(userId, RANGE); - // Privacy default per Marc — mood is opt-in, not opt-out. + // Privacy default per the maintainer — mood is opt-in, not opt-out. expect(data.mood).toBeNull(); // Every other section keeps its data. expect(data.stats.WEIGHT).toBeDefined(); diff --git a/tests/integration/intake-scheduled-for-targeting.test.ts b/tests/integration/intake-scheduled-for-targeting.test.ts index 959747eeb..aa27a306e 100644 --- a/tests/integration/intake-scheduled-for-targeting.test.ts +++ b/tests/integration/intake-scheduled-for-targeting.test.ts @@ -24,7 +24,7 @@ import { applyCanonicalSlotWrite, resolveSlotInstantForWrite, } from "@/lib/medications/scheduling/slot-upsert"; -import { localHmAsUtc } from "@/lib/timezone"; +import { localHmAsUtc } from "@/lib/tz/local-day"; const TEST_USER_ID = "user-intake-scheduledfor-targeting"; diff --git a/tests/integration/intake-slot-dedup.test.ts b/tests/integration/intake-slot-dedup.test.ts index f04478697..c3f69246a 100644 --- a/tests/integration/intake-slot-dedup.test.ts +++ b/tests/integration/intake-slot-dedup.test.ts @@ -29,7 +29,7 @@ import { applyCanonicalSlotWrite, resolveSlotInstantForWrite, } from "@/lib/medications/scheduling/slot-upsert"; -import { localHmAsUtc } from "@/lib/timezone"; +import { localHmAsUtc } from "@/lib/tz/local-day"; const TEST_USER_ID = "user-intake-slot-dedup"; const OTHER_USER_ID = "user-intake-slot-dedup-clean"; diff --git a/tests/integration/medication-cadence-worker.test.ts b/tests/integration/medication-cadence-worker.test.ts index 3b9090e26..95fe6a847 100644 --- a/tests/integration/medication-cadence-worker.test.ts +++ b/tests/integration/medication-cadence-worker.test.ts @@ -41,7 +41,7 @@ import { buildRecurrenceContext, scheduleEmitsInWindow, } from "@/lib/medications/scheduling/worker-helpers"; -import { localHmAsUtc } from "@/lib/timezone"; +import { localHmAsUtc } from "@/lib/tz/local-day"; const TEST_USER_ID = "user-medication-cadence-worker"; @@ -131,7 +131,7 @@ async function loadCanonical(medicationId: string): Promise<{ /** * Compute the user-tz today bounds for an arbitrary "now" instant, - * matching `getUserTodayBounds` in `src/lib/timezone.ts` so the test + * matching `getUserTodayBounds` in `src/lib/tz/local-day.ts` so the test * window matches what the worker would build on that tick. */ // UTC midnight on the local calendar day that `now` falls within for `tz`. diff --git a/tests/integration/medications-intake-bulk.test.ts b/tests/integration/medications-intake-bulk.test.ts index 2986d521e..df18caf3f 100644 --- a/tests/integration/medications-intake-bulk.test.ts +++ b/tests/integration/medications-intake-bulk.test.ts @@ -12,7 +12,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { cookieJar } from "./mock-next-headers"; import { getPrismaClient, truncateAllTables } from "./setup"; -import { localHmAsUtc } from "@/lib/timezone"; +import { localHmAsUtc } from "@/lib/tz/local-day"; const TEST_USER_ID = "user-medications-intake-bulk"; const OTHER_USER_ID = "user-medications-intake-other"; diff --git a/vitest.config.mts b/vitest.config.mts index c0f5cfb44..6972701c9 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -9,6 +9,10 @@ export default defineConfig({ test: { globals: true, environment: "node", + // Primes the i18n locale cache so provider mounts resolve every + // locale synchronously (production gets the active bundle as an RSC + // prop instead — see src/lib/i18n/load-locale.ts). + setupFiles: ["./vitest.setup.ts"], // Default `pnpm test` keeps unit tests only — integration suite uses // testcontainers and runs separately via `pnpm test:integration`. exclude: [