import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import test from "node:test"; const pageSource = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8"); // Measured 2026-09-16 after home-state-lowering batch 2 on origin/staging @ 37e6c519. // Line count: (pageSource.match(/\n/g) ?? []).length, same as `wc -l` = 1846. // Hook counts use \buseState[<(] / \buseRef[<(] so `useState(` is not missed. // Former caps were useState 52 / useRef 39 / lines 1931 after batch 1 @ f8e607c2, // and useState 66 / useRef 41 / lines 1951 on freeze-metric @ 51a65d92. // Batch 2 moved 4 synastry useStates into useSynastry, 6 profile useStates into // useProfileOnboarding and 6 session useStates into useSessionManagement, and // dropped the two guided-birth-time callback refs that the profile hook no longer // needs now that it runs before useBirthTimeGuidedJourney. const PAGE_LINE_COUNT_BASELINE = 1846; const PAGE_LINE_COUNT_CAP = PAGE_LINE_COUNT_BASELINE + 150; const HOME_USE_STATE_CAP = 36; const HOME_USE_REF_CAP = 37; const USE_STATE_RE = /\buseState[<(]/g; const USE_REF_RE = /\buseRef[<(]/g; function homeSource(source: string): string { const marker = "export default function Home("; const start = source.indexOf(marker); assert.notEqual(start, -1, "page.tsx must export default function Home("); return source.slice(start); } function countMatches(source: string, pattern: RegExp): number { return source.match(new RegExp(pattern.source, "g"))?.length ?? 0; } test("Home() useState count must not grow", () => { const n = countMatches(homeSource(pageSource), USE_STATE_RE); assert.ok( n <= HOME_USE_STATE_CAP, `Home() has ${n} useState calls; cap is ${HOME_USE_STATE_CAP}. Extracted hooks and child components should own their own state.`, ); }); test("Home() useRef count must not grow", () => { const n = countMatches(homeSource(pageSource), USE_REF_RE); assert.ok( n <= HOME_USE_REF_CAP, `Home() has ${n} useRef calls; cap is ${HOME_USE_REF_CAP}. Do not rewrite state as refs to dodge the useState freeze.`, ); }); test("page.tsx line count stays within the coarse guardrail", () => { const n = (pageSource.match(/\n/g) ?? []).length; assert.ok( n <= PAGE_LINE_COUNT_CAP, `page.tsx has ${n} lines; cap is ${PAGE_LINE_COUNT_CAP} (${PAGE_LINE_COUNT_BASELINE} baseline + 150).`, ); }); test("useSessionManagement still runs before useRectificationSurface", () => { // Batch 1 established the order: useSessionManagement needs an opener for // history rectification sessions, and useRectificationSurface is what supplies // it, so the opener is handed over through rectificationSessionOpenerRef // instead of by calling the surface hook first. Batch 2 moved hook calls // around; this pins the constraint so a later reorder cannot silently break // opening a rectification session from the sidebar. const home = homeSource(pageSource); const sessionCall = home.indexOf("= useSessionManagement({"); const surfaceCall = home.indexOf("= useRectificationSurface({"); const openerRef = home.indexOf("const rectificationSessionOpenerRef = useRef("); assert.notEqual(sessionCall, -1, "Home must call useSessionManagement"); assert.notEqual(surfaceCall, -1, "Home must call useRectificationSurface"); assert.notEqual(openerRef, -1, "Home must declare rectificationSessionOpenerRef"); assert.ok(openerRef < sessionCall, "the opener ref must exist before useSessionManagement reads it"); assert.ok( sessionCall < surfaceCall, "useSessionManagement must run before useRectificationSurface; it receives the opener through rectificationSessionOpenerRef", ); assert.match( home.slice(sessionCall, surfaceCall), /openRectificationSession: \(exactSessionId\) => rectificationSessionOpenerRef\.current\(exactSessionId\)/, ); assert.match(home.slice(surfaceCall), /rectificationSessionOpenerRef,/); });