Switching projects discards any edit made within the last AUTOSAVE_DEBOUNCE_MS (2000 ms), and the UI then tells the user everything is saved.
The path
src/core/project/projectService.ts — loadProject tears the queue down before hydrating:
async function loadProject(id: string): Promise<void> {
teardownAutosave();
teardownAutosave calls queue.dispose(), and dispose() in src/core/project/autosave.ts is:
dispose(): void {
this.disposed = true;
this.disarm();
this.dirty.clear();
}
It disarms the pending timer and clears the dirty set without flushing it. There is no flushNow() anywhere on this path.
Why it is a defect
Spec §4.4 requires the write-behind queue to be flushed:
immediately on visibilitychange → hidden and before project switch/export
Export complies — exportMpcweb awaits saveNow(). The switch does not. newProject routes through loadProject too, so "New Project" loses the same window.
Why it is worse than plain data loss
dispose() clears the dirty set, so onIdle never fires to report the loss — and the subsequent hydration calls setModified(false). The unsaved-dot is cleared. The user is shown a clean project having just lost work, with no error and no toast.
Per §4.4 the dot exists precisely to represent unflushed state, so this makes the one indicator that could have warned them assert the opposite.
Suggested fix
Await a flush before tearing down:
await queue?.flushNow();
teardownAutosave();
Worth a regression test: mutate, switch project inside the debounce window, and assert the mutation is present in the database afterwards.
Switching projects discards any edit made within the last
AUTOSAVE_DEBOUNCE_MS(2000 ms), and the UI then tells the user everything is saved.The path
src/core/project/projectService.ts—loadProjecttears the queue down before hydrating:teardownAutosavecallsqueue.dispose(), anddispose()insrc/core/project/autosave.tsis:It disarms the pending timer and clears the dirty set without flushing it. There is no
flushNow()anywhere on this path.Why it is a defect
Spec §4.4 requires the write-behind queue to be flushed:
Export complies —
exportMpcwebawaitssaveNow(). The switch does not.newProjectroutes throughloadProjecttoo, so "New Project" loses the same window.Why it is worse than plain data loss
dispose()clears the dirty set, soonIdlenever fires to report the loss — and the subsequent hydration callssetModified(false). The unsaved-dot is cleared. The user is shown a clean project having just lost work, with no error and no toast.Per §4.4 the dot exists precisely to represent unflushed state, so this makes the one indicator that could have warned them assert the opposite.
Suggested fix
Await a flush before tearing down:
Worth a regression test: mutate, switch project inside the debounce window, and assert the mutation is present in the database afterwards.