Platform
Web editor on current dev (c2e266870172312f461df75da3e7f6fbe9d2a1fc); browser-independent storage initialization failure.
Browser
All supported browsers (deterministic with a migration adapter that rejects once).
Current Behavior
A transient storage migration rejection is cached permanently at two layers, so later project loads cannot retry successfully.
On current dev:
ProjectManager.ensureStorageMigrations() stores storageMigrationPromise and never clears it when runStorageMigrations() rejects.
StorageService.ensureMigrations() likewise retains a rejected migrationsPromise.
ProjectManager.loadProject() calls editor.save.pause() and awaits ensureStorageMigrations() before entering its try/finally. If migration rejects, the finally that resets isLoading, notifies, and calls editor.save.resume() is skipped.
- Subsequent
loadProject()/loadAllProjects() calls await the same rejected promise rather than rerunning the migration.
After one transient IndexedDB/OPFS migration failure, project loading can remain poisoned for the manager lifetime and autosave can remain paused.
Expected Behavior
Concurrent migration callers should share one in-flight promise, successful completion should remain cached, and a rejected current promise should be cleared so the next explicit load retries. Every path after pausing save should restore loading/migration flags, notify, and resume saving exactly once. Failed initialization should not clear the currently loaded media/scenes or partially publish project state.
Recurrence Probability
Always
Steps To Reproduce
- Configure
runStorageMigrations() to reject its first call and resolve its second.
- Call
ProjectManager.loadProject({ id }) and observe the first rejection.
- Restore the migration dependency and call
loadProject({ id }) or loadAllProjects() again.
- Observe that the original rejected
storageMigrationPromise is awaited again, so migration is not retried.
- Observe that the first
loadProject() rejected before its try/finally, leaving editor.save paused and initial loading state uncleared.
- Repeat at the
StorageService.ensureMigrations() layer; its rejected cache behaves the same way.
Anything else?
Impact: A recoverable local-storage hiccup can make all projects unusable until the application/manager is recreated, while silently disabling further autosaves in the affected session. This is a critical availability and data-durability failure.
Bounded proposed fix: At both migration-cache layers, assign one local promise, share it across concurrent callers, cache fulfillment, and clear rejection only when the cache still points to that promise. In loadProject(), place all work after save.pause() under one try/finally; restore flags/notification and resume exactly once on every outcome. Preserve existing media/scenes and active state until migration and load succeed.
Focused acceptance tests: For both caches, rejection followed by resolution, concurrent calls invoking migration once, fulfilled-cache reuse, and identity-safe rejection cleanup. For loadProject()/loadAllProjects(), assert state preservation, loading/migration flag reset, notifications, and save pause/resume balance on every failure path.
Per the contribution policy, this report is requesting explicit maintainer approval for this candidate before any implementation or PR work begins.
Platform
Web editor on current
dev(c2e266870172312f461df75da3e7f6fbe9d2a1fc); browser-independent storage initialization failure.Browser
All supported browsers (deterministic with a migration adapter that rejects once).
Current Behavior
A transient storage migration rejection is cached permanently at two layers, so later project loads cannot retry successfully.
On current
dev:ProjectManager.ensureStorageMigrations()storesstorageMigrationPromiseand never clears it whenrunStorageMigrations()rejects.StorageService.ensureMigrations()likewise retains a rejectedmigrationsPromise.ProjectManager.loadProject()callseditor.save.pause()and awaitsensureStorageMigrations()before entering itstry/finally. If migration rejects, thefinallythat resetsisLoading, notifies, and callseditor.save.resume()is skipped.loadProject()/loadAllProjects()calls await the same rejected promise rather than rerunning the migration.After one transient IndexedDB/OPFS migration failure, project loading can remain poisoned for the manager lifetime and autosave can remain paused.
Expected Behavior
Concurrent migration callers should share one in-flight promise, successful completion should remain cached, and a rejected current promise should be cleared so the next explicit load retries. Every path after pausing save should restore loading/migration flags, notify, and resume saving exactly once. Failed initialization should not clear the currently loaded media/scenes or partially publish project state.
Recurrence Probability
Always
Steps To Reproduce
runStorageMigrations()to reject its first call and resolve its second.ProjectManager.loadProject({ id })and observe the first rejection.loadProject({ id })orloadAllProjects()again.storageMigrationPromiseis awaited again, so migration is not retried.loadProject()rejected before itstry/finally, leavingeditor.savepaused and initial loading state uncleared.StorageService.ensureMigrations()layer; its rejected cache behaves the same way.Anything else?
Impact: A recoverable local-storage hiccup can make all projects unusable until the application/manager is recreated, while silently disabling further autosaves in the affected session. This is a critical availability and data-durability failure.
Bounded proposed fix: At both migration-cache layers, assign one local promise, share it across concurrent callers, cache fulfillment, and clear rejection only when the cache still points to that promise. In
loadProject(), place all work aftersave.pause()under onetry/finally; restore flags/notification and resume exactly once on every outcome. Preserve existing media/scenes and active state until migration and load succeed.Focused acceptance tests: For both caches, rejection followed by resolution, concurrent calls invoking migration once, fulfilled-cache reuse, and identity-safe rejection cleanup. For
loadProject()/loadAllProjects(), assert state preservation, loading/migration flag reset, notifications, and save pause/resume balance on every failure path.Per the contribution policy, this report is requesting explicit maintainer approval for this candidate before any implementation or PR work begins.