Skip to content

[Website] Serialize Blueprint snapshots and OPFS site lifecycle mutations - #4004

Closed
adamziel wants to merge 4 commits into
opfs-final-flush-contractfrom
blueprint-site-lifecycle-persistence
Closed

[Website] Serialize Blueprint snapshots and OPFS site lifecycle mutations#4004
adamziel wants to merge 4 commits into
opfs-final-flush-contractfrom
blueprint-site-lifecycle-persistence

Conversation

@adamziel

@adamziel adamziel commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #4003.

What it does

Serializes Blueprint editor buffers, versioned bundle snapshots, saved-site metadata, and destructive OPFS lifecycle operations so one setup cannot persist into another setup's storage.

Before a bundle snapshot starts, the active editor flushes its in-memory buffer and drains older filesystem operations. The snapshot then occupies the same queue, so later edits wait until traversal finishes. Rapid file switches, panel unmounts, renames, exports, recreation, and persistence all use that boundary.

New Blueprint bundles are copied into inactive blueprint-bundle-<uuid> directories. Site metadata continues selecting the previous complete version until the copy finishes and the site transaction commits the new directory. Failed and stale copies are deleted only after authoritative metadata proves they are not selected.

Saved-site mutations now use per-site queues and browser locks. They re-read OPFS metadata while locked and verify the site ID and setup revision before writing. Slug allocation and autosave retention decisions are serialized separately because they operate on the whole collection.

A mounted OPFS runtime holds a shared browser lock. Reset, delete, and prune request the exclusive form before touching durable files. This tab can suspend and later restore its mount if another tab prevents exclusive access. Once destructive work starts, the old runtime is discarded rather than allowed to write stale MEMFS state back into the directory.

The save path now covers the active client check, storage selection, optional bundle copy, mount transition, metadata commit, and client-state commit in one site transaction. Failure cleanup retains a mount whose final flush failed, restores the previous mount when safe, removes inactive bundle versions, and forces a clean reboot when the old runtime can no longer be trusted.

Boot reconciles durable metadata after waiting for runtime access, finishes crash-interrupted resets, rejects incomplete initial saves, and ignores callbacks from replaced iframe boots.

This does not move any UI into the dock.

Rationale

A debounced Blueprint edit can exist only in CodeMirror when persistence starts walking the backend. Copying at that point saves an older Blueprint.

A reset or delete can also race an active OPFS journal in this tab or another tab. Deleting the directory is not enough if the old runtime is still able to recreate files afterward.

Redux is not authoritative across tabs. A stale tab can otherwise overwrite metadata for a reused slug or select a bundle copied for a setup that has already been replaced.

The code now has one order:

  1. flush editor-only state;
  2. serialize filesystem traversal;
  3. copy a complete inactive bundle version;
  4. acquire the required site/runtime ownership;
  5. re-read and verify durable metadata; and
  6. commit the selected version or remove the inactive copy.

Compatibility

Existing sites whose opfs-site source has no directory continue to load the legacy blueprint-bundle directory. New writes use versioned directories.

No existing public function is removed. Destructive operations can now reject when another tab keeps the same site mounted or when durable ownership changed. That rejection is intentional; proceeding would corrupt or delete another runtime's files.

Testing instructions

npm exec nx -- run playground-website:test
npm exec nx -- run playground-website:typecheck
npm exec nx -- run playground-website:lint
npm exec nx -- run playground-website:build:standalone

npm exec nx -- run playground-website:e2e:playwright -- \
  --project chromium \
  --grep 'should preserve Blueprint edits across rapid file switches|should not recreate a dirty Blueprint file after renaming it|should restore an edited file|should edit a blueprint in the blueprint editor|should edit a Blueprint for an autosaved Playground'
@adamziel

Copy link
Copy Markdown
Collaborator Author

Closing this draft instead of rebasing it onto the simplified stack.

It depends on drainFilesystemOperations, serializeFilesystemOperation, pathContainsPath, FileOpenRequestGuard, and the editor ownership machinery deliberately removed from #3973. Replaying it would bring back the same parallel path and queue model we just discarded, and its own diff is still roughly 7,600 changed lines.

The branch is preserved. Useful lifecycle invariants can be extracted later as small standalone PRs with focused tests; this is no longer a valid next PR in the stack.

@adamziel adamziel closed this Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment