From eb9484e781948ac805331488074b75718746e36e Mon Sep 17 00:00:00 2001 From: Cole Leavitt Date: Wed, 19 Aug 2026 03:12:28 -0400 Subject: [PATCH 1/3] test(plugin): isolate and await storage work --- packages/plugin/package.json | 2 +- .../magic-context/message-index-async.test.ts | 15 ++-- .../magic-context/message-index-async.ts | 71 +++++++++++-------- .../storage-embedding-measurements.test.ts | 50 ++++++------- 4 files changed, 77 insertions(+), 61 deletions(-) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 8cc039944..9c3d4b0b2 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -39,7 +39,7 @@ "build:tui": "bun scripts/build-tui.ts", "check:tui-compiled": "bun run build:tui && git diff --exit-code -- src/tui-compiled && test -z \"$(git status --porcelain -- src/tui-compiled)\"", "typecheck": "tsc -p ../retina-local-fs/tsconfig.build.json && tsc --noEmit && tsc -p tsconfig.scripts.json", - "test": "bun test", + "test": "bun test --isolate", "lint": "biome check .", "lint:fix": "biome check --write .", "format": "biome format --write .", diff --git a/packages/plugin/src/features/magic-context/message-index-async.test.ts b/packages/plugin/src/features/magic-context/message-index-async.test.ts index 9f9c82203..467f53a9d 100644 --- a/packages/plugin/src/features/magic-context/message-index-async.test.ts +++ b/packages/plugin/src/features/magic-context/message-index-async.test.ts @@ -1,6 +1,6 @@ /// -import { afterEach, beforeEach, describe, expect, it } from "bun:test"; +import { afterEach, beforeEach, describe, expect, it, jest } from "bun:test"; import type { RawMessage } from "../../hooks/magic-context/read-session-raw"; import { BOOT_QUIET_MS, setBootQuietPeriodForTests } from "../../plugin/boot-quiet"; @@ -114,6 +114,7 @@ describe("message-index-async", () => { afterEach(() => { setBootQuietPeriodForTests(null); + jest.useRealTimers(); closeQuietly(db); __resetMessageIndexAsyncForTests(); }); @@ -359,6 +360,8 @@ describe("message-index-async", () => { }); it("rebuilds when removal overtakes a boot-quiet reconciliation", async () => { + jest.useFakeTimers(); + jest.setSystemTime(0); const sessionId = "ses-boot-clear"; const surviving = [message("m-survivor", 1, "surviving searchable bytes")]; let reads = 0; @@ -366,13 +369,15 @@ describe("message-index-async", () => { reads++; return surviving; }; - setBootQuietPeriodForTests(Date.now() - BOOT_QUIET_MS + 20); + setBootQuietPeriodForTests(0); - scheduleReconciliation(db, sessionId, readSurviving); - scheduleClearAndReindex(db, sessionId, readSurviving); + const reconciliationFinished = scheduleReconciliation(db, sessionId, readSurviving); + const rebuildFinished = scheduleClearAndReindex(db, sessionId, readSurviving); expect(isSessionReconciled(sessionId)).toBe(false); - await wait(80); + jest.advanceTimersByTime(BOOT_QUIET_MS); + jest.runAllTimers(); + await Promise.all([reconciliationFinished, rebuildFinished]); expect(reads).toBe(2); expect(countMessageRows(db, sessionId, "m-survivor")).toBe(1); diff --git a/packages/plugin/src/features/magic-context/message-index-async.ts b/packages/plugin/src/features/magic-context/message-index-async.ts index 7b5b6a2fc..a33c50942 100644 --- a/packages/plugin/src/features/magic-context/message-index-async.ts +++ b/packages/plugin/src/features/magic-context/message-index-async.ts @@ -73,7 +73,7 @@ const INCREMENTAL_DEBOUNCE_MS = 100; const RECONCILIATION_BATCH_SIZE = 100; const reconciledSessions = new Set(); -const reconciliationScheduledSessions = new Set(); +const reconciliationScheduledSessions = new Map>(); const sessionLocks = new Map>(); const incrementalTimers = new Map>(); const pendingIncrementalKeys = new Set(); @@ -199,23 +199,29 @@ export function scheduleReconciliation( db: Database, sessionId: string, readMessages: ReadMessages, -): void { - if (reconciledSessions.has(sessionId) || reconciliationScheduledSessions.has(sessionId)) { - return; - } - reconciliationScheduledSessions.add(sessionId); - - scheduleAfterBootQuiet(() => { - defer(() => { - void reconcileSessionIndex(db, sessionId, readMessages) - .catch((error) => { - logIndexingError(sessionId, "reconciliation", error); - }) - .finally(() => { - reconciliationScheduledSessions.delete(sessionId); - }); +): Promise { + if (reconciledSessions.has(sessionId)) return Promise.resolve(); + const scheduled = reconciliationScheduledSessions.get(sessionId); + if (scheduled) return scheduled; + + const completion = new Promise((resolve) => { + scheduleAfterBootQuiet(() => { + defer(() => { + void reconcileSessionIndex(db, sessionId, readMessages) + .catch((error) => { + logIndexingError(sessionId, "reconciliation", error); + }) + .finally(() => { + if (reconciliationScheduledSessions.get(sessionId) === completion) { + reconciliationScheduledSessions.delete(sessionId); + } + resolve(); + }); + }); }); }); + reconciliationScheduledSessions.set(sessionId, completion); + return completion; } export function scheduleIncrementalIndex( @@ -275,26 +281,29 @@ export function scheduleClearAndReindex( db: Database, sessionId: string, readMessages: ReadMessages, -): void { +): Promise { reconciledSessions.delete(sessionId); reconciliationScheduledSessions.delete(sessionId); clearCompletedIncrementalKeys(sessionId); - scheduleAfterBootQuiet(() => { - defer(() => { - void runWithSessionLock(sessionId, () => { - // An older boot-quiet reconciliation can finish after this clear was - // scheduled, so invalidate process state under the same session lock - // that clears the durable index. - reconciledSessions.delete(sessionId); - clearCompletedIncrementalKeys(sessionId); - clearIndexedMessages(db, sessionId); - }) - .then(() => reconcileSessionIndex(db, sessionId, readMessages)) - .catch((error) => { + return new Promise((resolve) => { + scheduleAfterBootQuiet(() => { + defer(() => { + void runWithSessionLock(sessionId, () => { + // An older boot-quiet reconciliation can finish after this clear was + // scheduled, so invalidate process state under the same session lock + // that clears the durable index. reconciledSessions.delete(sessionId); - logIndexingError(sessionId, "clear and reindex", error); - }); + clearCompletedIncrementalKeys(sessionId); + clearIndexedMessages(db, sessionId); + }) + .then(() => reconcileSessionIndex(db, sessionId, readMessages)) + .catch((error) => { + reconciledSessions.delete(sessionId); + logIndexingError(sessionId, "clear and reindex", error); + }) + .finally(resolve); + }); }); }); } diff --git a/packages/plugin/src/features/magic-context/storage-embedding-measurements.test.ts b/packages/plugin/src/features/magic-context/storage-embedding-measurements.test.ts index 8b4a64ff3..759d07221 100644 --- a/packages/plugin/src/features/magic-context/storage-embedding-measurements.test.ts +++ b/packages/plugin/src/features/magic-context/storage-embedding-measurements.test.ts @@ -62,30 +62,32 @@ describe("embedding measurement corpus", () => { const db = openDatabase(); const overflow = 5; const total = MEASUREMENT_CORPUS_SESSION_ROW_CAP + overflow; - for (let i = 0; i < total; i++) { - recordEmbeddingMeasurement(db, { - sessionId: "ses-cap", - projectPath: "/repo", - // Unique query text per row: dedup is on (query hash, cohort), so - // distinct queries simulate the cohort-transition growth. - queryText: `query ${i}`, - cohortKey: "fp-a:0|fp-b:0", - primaryResultIds: [], - shadowResultIds: [], - primaryLatencyMs: 1, - shadowLatencyMs: 1, - primaryFailed: false, - shadowFailed: false, - primaryModelId: "local-id", - shadowModelId: "synapse-id", - primaryFingerprint: "", - shadowFingerprint: "fp-b", - primaryEpoch: 0, - shadowEpoch: 0, - corpusHash: `corpus-${i}`, - coverage: {}, - }); - } + db.transaction(() => { + for (let i = 0; i < total; i++) { + recordEmbeddingMeasurement(db, { + sessionId: "ses-cap", + projectPath: "/repo", + // Unique query text per row: dedup is on (query hash, cohort), so + // distinct queries simulate the cohort-transition growth. + queryText: `query ${i}`, + cohortKey: "fp-a:0|fp-b:0", + primaryResultIds: [], + shadowResultIds: [], + primaryLatencyMs: 1, + shadowLatencyMs: 1, + primaryFailed: false, + shadowFailed: false, + primaryModelId: "local-id", + shadowModelId: "synapse-id", + primaryFingerprint: "", + shadowFingerprint: "fp-b", + primaryEpoch: 0, + shadowEpoch: 0, + corpusHash: `corpus-${i}`, + coverage: {}, + }); + } + })(); const rows = listEmbeddingMeasurements(db, "ses-cap"); expect(rows).toHaveLength(MEASUREMENT_CORPUS_SESSION_ROW_CAP); From 9f53f32aa0c4bb493c15c6dd0185741c7270f6f3 Mon Sep 17 00:00:00 2001 From: Cole Leavitt Date: Wed, 19 Aug 2026 04:30:51 -0400 Subject: [PATCH 2/3] test(plugin): keep storage timeout errors observable --- packages/plugin/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/plugin/package.json b/packages/plugin/package.json index 9c3d4b0b2..adffcf858 100644 --- a/packages/plugin/package.json +++ b/packages/plugin/package.json @@ -39,7 +39,7 @@ "build:tui": "bun scripts/build-tui.ts", "check:tui-compiled": "bun run build:tui && git diff --exit-code -- src/tui-compiled && test -z \"$(git status --porcelain -- src/tui-compiled)\"", "typecheck": "tsc -p ../retina-local-fs/tsconfig.build.json && tsc --noEmit && tsc -p tsconfig.scripts.json", - "test": "bun test --isolate", + "test": "bun test --timeout 30000", "lint": "biome check .", "lint:fix": "biome check --write .", "format": "biome format --write .", From d166e56b1016b7cddaa50618a30639970fa536a6 Mon Sep 17 00:00:00 2001 From: Cole Leavitt Date: Wed, 19 Aug 2026 04:43:35 -0400 Subject: [PATCH 3/3] test(tui): stabilize runtime import order --- packages/plugin/src/tui/tui-compiled-runtime-imports.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/plugin/src/tui/tui-compiled-runtime-imports.test.ts b/packages/plugin/src/tui/tui-compiled-runtime-imports.test.ts index e763d2ac5..2a81f672b 100644 --- a/packages/plugin/src/tui/tui-compiled-runtime-imports.test.ts +++ b/packages/plugin/src/tui/tui-compiled-runtime-imports.test.ts @@ -49,6 +49,9 @@ describe("compiled TUI runtime imports", () => { * Built from the shared list so the test cannot cover fewer modules than the * build rewrites. */ async function loadExportSets(): Promise>> { + // @opentui/core/testing subclasses a core export during module initialization; + // warm core before the parallel imports so Bun cannot expose its TDZ. + await import("@opentui/core"); const entries = await Promise.all( TUI_RUNTIME_SPECIFIERS.map( async (specifier) =>