Skip to content

Save Playgrounds by default#3655

Draft
adamziel wants to merge 17 commits into
trunkfrom
codex/default-saved-playgrounds
Draft

Save Playgrounds by default#3655
adamziel wants to merge 17 commits into
trunkfrom
codex/default-saved-playgrounds

Conversation

@adamziel
Copy link
Copy Markdown
Collaborator

@adamziel adamziel commented May 17, 2026

What it does

  • Makes the default Playground route create a browser-backed Playground in OPFS instead of an unsaved temporary one.
  • Treats default OPFS sites as Autosaved Playgrounds: recovery copies created to prevent data loss, not user-preserved saved sites.
  • Adds an explicit Save / Save Playground promotion path. Browser-saved Playgrounds and local filesystem Playgrounds are marked explicit and kept by Playground unless the user deletes them or browser/platform storage is cleared.
  • Keeps only the latest 5 autosaved Playgrounds and excludes explicit, local filesystem, legacy, temporary, and currently selected autosaves from pruning.
  • Replaces the initial Saved Playground label for default-created sites with Autosaved Playground, Autosaving..., or Finalizing autosave... as appropriate.
  • Shows Save even while an autosaved Playground is still syncing, so users can preserve the recovery copy immediately.
  • Uses retention copy that states autosaves are removed after 5 newer autosaves instead of saying they are "kept", which collided with the promotion action.
  • Uses a recovery/backup icon for autosaved sites, keeps the checkmark for explicitly saved sites, and exposes a visible Your Playgrounds toolbar label on desktop so stored sites are easier to find.
  • Fixes the confusing Saving n/n... state by adding OPFS sync phases: copy progress remains counted, while final journal flush reports as finalizing.
  • Updates the Your Playgrounds overlay and Site Manager details to show whether a site is autosaved or explicitly saved.
  • Keeps temporary behavior available with ?storage=temp, ?can-save=no, browser/storage fallbacks, and the Your Playgrounds overlay Unsaved Playground opt-out.
  • Starts brand-new OPFS Playgrounds without waiting for the full initial file copy, journals writes during the background sync, and queues the final journal flush in the background so Finalizing autosave... cannot block forever while WordPress keeps a request or filesystem write active.
  • Routes import, blueprint, and overlay-created Playgrounds through the same saved-by-default flow.
  • Adds plan.md with the UX/research plan for autosaved default Playgrounds.

UX model

The PR now separates recovery from intent:

  • Autosaved Playground: created automatically in browser storage to avoid data loss. It is removed after 5 newer autosaves unless the user saves it.
  • Saved Playground: explicitly saved by the user via Save, Save Playground, rename/save APIs, or local filesystem save. It is kept by Playground unless the user deletes it or browser/platform storage is cleared.
  • Unsaved Playground: explicit temporary storage. It keeps the existing warning/save affordance.

This follows patterns from other products that separate automatic recovery from intentional preservation:

  • Google Drive keeps only recent past versions unless the user chooses Keep forever.
  • Figma records autosave checkpoints but lets users create/name versions for intentional milestones.
  • Slack auto-saves unfinished messages as drafts rather than treating them as sent/saved messages.
  • StackBlitz and CodePen both keep a user-facing Save action distinct from automatic sync/autosave behavior.

Implementation

  • Added persistence?: 'autosave' | 'explicit' and whenLastUsed metadata. Missing persistence is treated as explicit for backwards compatibility with existing saved Playgrounds.
  • Added pure lifecycle helpers in site-lifecycle.ts and focused tests for legacy handling, retention, whenLastUsed, and pruning exclusions.
  • Added preserveSite() and playgroundSites.keep() to promote autosaves to explicit saved sites.
  • Default createNewSavedSite() now creates autosaves and prunes old autosaves after the new site is selected.
  • persistTemporarySite() and rename/save APIs promote sites to explicit persistence.
  • bootSiteClient() updates whenLastUsed for non-temporary sites so autosave retention is based on recency, not only creation time.
  • OPFS initial sync now reports phase: 'copying' | 'flushing', sends a final copy progress event before the flush phase, and lets the mount resolve while the final journal flush continues in the background.
  • Save status UI now distinguishes autosaved/saved/unsaved/error states, uses Save as the autosave promotion action, and avoids leaving users stuck on Finalizing autosave... when the final OPFS journal flush is waiting on ongoing WordPress activity.
  • The Playwright persistence smoke waits for the initial autosave mount to finish before mutating and explicitly flushes OPFS before reload, matching the UI contract that Autosaving... means recovery is still in progress.

Testing

Local passing:

  • npx nx test playground-website --testFile=packages/playground/website/src/lib/state/redux/slice-sites.spec.ts
  • npx nx test php-wasm-web --testFile=packages/php-wasm/web/src/lib/directory-handle-mount.spec.ts
  • npx nx run playground-website:typecheck
  • npx nx run php-wasm-web:typecheck
  • git diff --check

Latest local passing after merge conflict resolution and finalizing-fix:

  • npx nx test php-wasm-web --testFile=packages/php-wasm/web/src/lib/directory-handle-mount.spec.ts
  • npx nx run playground-website:typecheck
  • npx nx run php-wasm-web:typecheck
  • git diff --check

Local blocked:

  • npx playwright test --config=packages/playground/website/playwright/playwright.config.ts --project=chromium -g "Default Playground storage"
  • The Playwright runner reached the test phase but Chromium could not launch in this container because host libraries are missing (libglib-2.0, libnss3, libX11, libgbm, libasound, and related dependencies). CI provides browser coverage.

CI:

  • Previous head 02d94ade6 exposed a race in test-e2e-playwright (chromium, 3/3, 3-of-3): the persistence smoke reloaded while the UI still showed Autosaving..., before the initial OPFS mount was ready.
  • Head e0a7517c545afc2de0355f1b0146072f3366eac5 passed GitHub Actions run 26066078037: 51 passed, 2 intentionally skipped.
  • Current head ed331afc4f4fac0c3864f852119c56e91de4de74 merges current origin/trunk, resolves the boot-site-client.ts conflict by combining wordpressInstallMode with the saved-by-default background OPFS mount behavior, and fixes the stuck Finalizing autosave... state. GitHub Actions run 26087850389 is in progress.

@adamziel adamziel force-pushed the codex/default-saved-playgrounds branch from bb825ab to 1861daa Compare May 19, 2026 00:08
…playgrounds

# Conflicts:
#	packages/playground/website/src/lib/state/redux/boot-site-client.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant