Tracking issue for three genuinely-valid, out-of-scope findings surfaced during PR #596's review (the #532 E2E-determinism / IDB reset-quiescence work), deliberately NOT fixed in that PR per explicit scoping decision. This repo's branch protection requires all review conversation threads to be resolved before merge (required_conversation_resolution: true), so the five threads below were resolved on #596 with a reply citing this tracking issue — resolving is a GitHub merge-mechanics step, not a claim that the underlying issue is fixed. Full original disposition remains permanently visible in each thread's comment history.
1. Local-first Safari/old-browser fallback omits dynamic worldscript-localfirst-* databases
Threads: CodeAnt, CodeRabbit, and cubic each independently flagged services/factoryResetService.ts around the native-indexedDB.databases()-unavailable fallback path.
Disposition (confirmed valid): When native database enumeration is unavailable (Safari) or throws, deleteAllIndexedDBDatabases() falls back to the static KNOWN_DB_NAMES list, which cannot include per-project worldscript-localfirst-<projectId> databases (dynamically named, only discoverable via indexedDB.databases()). A factory reset on an affected browser can therefore reload as a "fresh install" while local-first project shadow data survives.
Why not fixed in #596: local-first sync is behind enableLocalFirstSync, a user opt-in flag defaulting OFF — narrowing the real-world blast radius to Safari/old-browser users who have explicitly enabled an experimental flag. A real fix requires enumerating known project IDs (e.g. via dbService.listProjects()) before beginIdbReset() closes that connection, then deriving candidate worldscript-localfirst-* names for the fallback -- a call-ordering restructure of wipeAllAppData(), not a local one-line fix, and risks reintroducing a connection-timing race the whole PR exists to close.
Suggested approach: capture the project-ID list up front in wipeAllAppData(), before beginIdbReset() runs, and thread it into deleteAllIndexedDBDatabases()'s fallback branch.
2. Encryption-transition clearData() failure is swallowed before the caller can react
Thread: CodeRabbit on app/listenerMiddleware.ts's reconcileLocalFirstHandle().
Disposition (confirmed valid): When at-rest encryption becomes active after a plaintext-persisting local-first handle was created, reconcileLocalFirstHandle() calls persistence.clearData() to wipe the plaintext before discarding the handle. DocPersistence.clearData() (in services/localFirst/docPersistence.ts) always resolves successfully by design (so its many defensive callers never need to branch on failure), meaning a genuine wipe failure is invisible to the caller, which proceeds to discard the handle as if the wipe succeeded.
Why not fixed in #596: this is the same root architectural gap as an earlier pre-existing thread on this feature (encryption-transition remanence in the "dead reference" branch) -- fixing it properly requires the SAME kind of raw-vs-swallowed split already applied to destroy()/beginDestroy() in this PR (see services/localFirst/docPersistence.ts), but for clearData(), plus a design decision on what reconcileLocalFirstHandle() should actually DO on a proven wipe failure (retry? surface a user-facing warning? refuse to discard the handle?) -- that's a deliberate lifecycle-design call, not a one-line fix, and this PR's scope is reset-gate quiescence, not local-first encryption lifecycle redesign.
Suggested approach: mirror the beginDestroy()/destroy() split for clearData(), then decide (separately) what reconcileLocalFirstHandle() does with a genuine wipe failure.
3. IdbConnectionManager discards its unregister function, leaking closers in tests
Thread: cubic on services/storage/idbCore.ts's IdbConnectionManager constructor.
Disposition (confirmed valid): registerIdbConnectionCloser()'s returned unregister function is discarded in IdbConnectionManager's constructor. In production every subclass is a long-lived module-level singleton (constructed once), so this doesn't leak there -- but multiple test files instantiate new IdbProjectStore() (or similar) repeatedly within the same file, each accumulating a new closer in the shared idbResetGate module registry with no way to remove the stale one from a previous instance.
Why not fixed in #596: each leaked closer is a harmless no-op (this.stateDb?.close() etc. on an already-closed/null reference), so this is a test-hygiene issue, not a production correctness bug. A real fix means exposing a disposal API and updating roughly ten existing test files (idbStoreEncryption.test.ts and others) that aren't in this PR's already-large scope.
Suggested approach: store unregister on the instance, expose a dispose()/similar method, and update affected test files to call it between instantiations.
Tracking issue for three genuinely-valid, out-of-scope findings surfaced during PR #596's review (the #532 E2E-determinism / IDB reset-quiescence work), deliberately NOT fixed in that PR per explicit scoping decision. This repo's branch protection requires all review conversation threads to be resolved before merge (
required_conversation_resolution: true), so the five threads below were resolved on #596 with a reply citing this tracking issue — resolving is a GitHub merge-mechanics step, not a claim that the underlying issue is fixed. Full original disposition remains permanently visible in each thread's comment history.1. Local-first Safari/old-browser fallback omits dynamic
worldscript-localfirst-*databasesThreads: CodeAnt, CodeRabbit, and cubic each independently flagged
services/factoryResetService.tsaround the native-indexedDB.databases()-unavailable fallback path.Disposition (confirmed valid): When native database enumeration is unavailable (Safari) or throws,
deleteAllIndexedDBDatabases()falls back to the staticKNOWN_DB_NAMESlist, which cannot include per-projectworldscript-localfirst-<projectId>databases (dynamically named, only discoverable viaindexedDB.databases()). A factory reset on an affected browser can therefore reload as a "fresh install" while local-first project shadow data survives.Why not fixed in #596: local-first sync is behind
enableLocalFirstSync, a user opt-in flag defaulting OFF — narrowing the real-world blast radius to Safari/old-browser users who have explicitly enabled an experimental flag. A real fix requires enumerating known project IDs (e.g. viadbService.listProjects()) beforebeginIdbReset()closes that connection, then deriving candidateworldscript-localfirst-*names for the fallback -- a call-ordering restructure ofwipeAllAppData(), not a local one-line fix, and risks reintroducing a connection-timing race the whole PR exists to close.Suggested approach: capture the project-ID list up front in
wipeAllAppData(), beforebeginIdbReset()runs, and thread it intodeleteAllIndexedDBDatabases()'s fallback branch.2. Encryption-transition
clearData()failure is swallowed before the caller can reactThread: CodeRabbit on
app/listenerMiddleware.ts'sreconcileLocalFirstHandle().Disposition (confirmed valid): When at-rest encryption becomes active after a plaintext-persisting local-first handle was created,
reconcileLocalFirstHandle()callspersistence.clearData()to wipe the plaintext before discarding the handle.DocPersistence.clearData()(inservices/localFirst/docPersistence.ts) always resolves successfully by design (so its many defensive callers never need to branch on failure), meaning a genuine wipe failure is invisible to the caller, which proceeds to discard the handle as if the wipe succeeded.Why not fixed in #596: this is the same root architectural gap as an earlier pre-existing thread on this feature (encryption-transition remanence in the "dead reference" branch) -- fixing it properly requires the SAME kind of raw-vs-swallowed split already applied to
destroy()/beginDestroy()in this PR (seeservices/localFirst/docPersistence.ts), but forclearData(), plus a design decision on whatreconcileLocalFirstHandle()should actually DO on a proven wipe failure (retry? surface a user-facing warning? refuse to discard the handle?) -- that's a deliberate lifecycle-design call, not a one-line fix, and this PR's scope is reset-gate quiescence, not local-first encryption lifecycle redesign.Suggested approach: mirror the
beginDestroy()/destroy()split forclearData(), then decide (separately) whatreconcileLocalFirstHandle()does with a genuine wipe failure.3.
IdbConnectionManagerdiscards itsunregisterfunction, leaking closers in testsThread: cubic on
services/storage/idbCore.ts'sIdbConnectionManagerconstructor.Disposition (confirmed valid):
registerIdbConnectionCloser()'s returnedunregisterfunction is discarded inIdbConnectionManager's constructor. In production every subclass is a long-lived module-level singleton (constructed once), so this doesn't leak there -- but multiple test files instantiatenew IdbProjectStore()(or similar) repeatedly within the same file, each accumulating a new closer in the sharedidbResetGatemodule registry with no way to remove the stale one from a previous instance.Why not fixed in #596: each leaked closer is a harmless no-op (
this.stateDb?.close()etc. on an already-closed/null reference), so this is a test-hygiene issue, not a production correctness bug. A real fix means exposing a disposal API and updating roughly ten existing test files (idbStoreEncryption.test.tsand others) that aren't in this PR's already-large scope.Suggested approach: store
unregisteron the instance, expose adispose()/similar method, and update affected test files to call it between instantiations.