import assert from "node:assert/strict"; import { readdirSync, readFileSync, statSync } from "node:fs"; import { extname, join } from "node:path"; import { fileURLToPath } from "node:url"; import test from "node:test"; import { rectificationOpenIdentity } from "../src/lib/rectification-session-open.ts"; const surface = readFileSync( new URL("../src/hooks/use-rectification-surface.ts", import.meta.url), "utf8", ); const srcRoot = fileURLToPath(new URL("../src", import.meta.url)); function owningObjectLiteral(source: string, index: number): string { let depth = 0; for (let i = index; i >= 0; i -= 1) { const char = source[i]; if (char === "}") depth += 1; else if (char === "{") { if (depth === 0) { let open = 0; for (let j = i; j < source.length; j += 1) { if (source[j] === "{") open += 1; else if (source[j] === "}") { open -= 1; if (open === 0) return source.slice(i, j + 1); } } return source.slice(i); } depth -= 1; } } return ""; } function walkTypeScript(directory: string): string[] { const files: string[] = []; for (const entry of readdirSync(directory)) { const path = join(directory, entry); const stat = statSync(path); if (stat.isDirectory()) files.push(...walkTypeScript(path)); else if (stat.isFile() && (extname(path) === ".ts" || extname(path) === ".tsx")) files.push(path); } return files; } function unconditionalExistingSessionClocks(source: string): string[] { const hits: string[] = []; const needle = /updatedAt:\s*timestamp\(\)/g; for (const match of source.matchAll(needle)) { const index = match.index ?? 0; const lineStart = source.lastIndexOf("\n", index) + 1; const line = source.slice(lineStart, source.indexOf("\n", index)); if (/\bexisting\?\.updatedAt\s*\?\?/.test(line)) continue; const literal = owningObjectLiteral(source, index); if (!/\bid\s*:/.test(literal)) continue; if (!/\bsessionType\s*:/.test(literal) && !/\bmessages\s*:/.test(literal)) continue; if (/randomUUID\s*\(/.test(literal)) continue; if (/session-clock-exempt/.test(literal)) continue; hits.push(literal.slice(0, 180)); } return hits; } test("opening a stored rectification session keeps its title and updatedAt", () => { const existing = { title: "9月12日 · 生时校正", updatedAt: 1_757_635_445_000 }; const minted = { title: "9月15日 · 生时校正", updatedAt: 1_757_894_400_000 }; assert.deepEqual(rectificationOpenIdentity(existing, minted), existing); }); test("minting a new rectification session still uses the wall-clock title", () => { const minted = { title: "9月15日 · 生时校正", updatedAt: 1_757_894_400_000 }; assert.deepEqual(rectificationOpenIdentity(undefined, minted), minted); }); test("openRectificationCase inherits identity from existing before minting", () => { const open = surface.slice( surface.indexOf("async function openRectificationCase"), surface.indexOf("async function openRectificationFromHomepage"), ); const merged = open.slice(open.indexOf("const merged: ChatSession = {"), open.indexOf("setSessions((current)")); assert.match(open, /rectificationOpenIdentity\(\s*existing,/); assert.match(merged, /title: openIdentity\.title/); assert.match(merged, /updatedAt: openIdentity\.updatedAt/); assert.doesNotMatch(merged, /resolveSessionTitle/); assert.doesNotMatch(merged, /updatedAt: timestamp\(\)/); assert.match(open, /pinned: existing\?\.pinned \?\? false/); assert.match(open, /void persistSession\(merged\)/); }); test("ChatSession literals for an existing id cannot stamp updatedAt with the wall clock", () => { const offenders: string[] = []; for (const directory of ["hooks", "lib"]) { for (const file of walkTypeScript(join(srcRoot, directory))) { const source = readFileSync(file, "utf8"); for (const hit of unconditionalExistingSessionClocks(source)) { offenders.push(`${file}: ${hit.replace(/\s+/g, " ")}`); } } } assert.deepEqual(offenders, []); });