Skip to content

[Storage] Add embedded site OPFS locator - #4184

Closed
ashfame wants to merge 6 commits into
trunkfrom
agent/add-embedded-site-opfs-path
Closed

[Storage] Add embedded site OPFS locator#4184
ashfame wants to merge 6 commits into
trunkfrom
agent/add-embedded-site-opfs-path

Conversation

@ashfame

@ashfame ashfame commented Jul 28, 2026

Copy link
Copy Markdown
Member

What

Adds a constrained OPFS locator for sites managed by embedding applications:

  • getExistingEmbeddedSiteOpfsDirectoryHandle(storageKey) is public and only opens an existing directory.
  • getOrCreateEmbeddedSiteOpfsDirectoryHandle(storageKey) is private to browser-fs.ts and is used by mount handling.
  • opfs-paths.ts owns both the website-managed /sites root and embedded-site /embedded-sites root, plus the internal embedded-site path derivation.

Adds an opfs-embedded-site mount device that accepts the same opaque storage key. remote.html already resolves mounts through directoryHandleFromMountDevice(), which now uses the private get-or-create operation.

Why

An embedding that persists a Playground through remote.html and later reads it through a lightweight api.html needs both endpoints to locate the same directory by storage key. Creation policy should not be a caller-controlled boolean: mount handling must create the directory, while a consumer must never turn an unknown key into an empty site.

This is intentionally a small precursor to #4095. That PR can call getExistingEmbeddedSiteOpfsDirectoryHandle(storageKey) and pass the returned handle to its ZIP implementation.

The website-specific opfsSiteStorage.exportSavedSiteAsZip(slug) remains unchanged. It is coupled to the website's /sites namespace, slug lookup, and wp-runtime.json completeness metadata, so it is not the exporter for embedded sites.

Impact

Embedders can persist a site through startPlaygroundWeb() with:

mounts: [
	{
		device: {
			type: 'opfs-embedded-site',
			storageKey,
		},
		mountpoint: '/wordpress',
		initialSyncDirection: 'memfs-to-opfs',
	},
]

Before removing the Playground iframe, callers should use await playground.flushOpfs('/wordpress') or await playground.unmountOpfs('/wordpress') to ensure pending changes have reached OPFS.

The later lightweight API can locate the persisted directory with:

const directory =
	await getExistingEmbeddedSiteOpfsDirectoryHandle(storageKey);

Existing raw opfs path mounts are unchanged. Website and Personal WP site storage now share the canonical /sites root exported by the storage package.

Testing

  • npm exec nx test playground-storage
  • npm exec nx lint playground-storage
  • npm exec nx build playground-storage
  • npm exec nx build playground-client
  • npm exec nx typecheck playground-personal-wp
  • npm exec nx lint playground-personal-wp
  • npm exec nx typecheck playground-website
  • npm exec nx lint playground-website
  • npm exec nx test playground-website
@ashfame
ashfame force-pushed the agent/add-embedded-site-opfs-path branch from 2da7fdc to 326ab85 Compare July 28, 2026 23:53
adamziel pushed a commit that referenced this pull request Jul 29, 2026
## What

Updates the concurrent SQLite publish regression fixture so its
`varchar` columns accept the 500-character values the test intentionally
inserts.

## Root cause

The test created every stress column as `varchar(191)` and then inserted
a 500-character value into each column. Current WordPress field
validation rejects those values before the test reaches the SQLite
concurrency behavior it is meant to exercise, returning HTTP 500 with an
error that the supplied values may be too long or invalid.

The mismatch has existed since the test was introduced in #3920. It was
exposed while working on #4184 because changes to
`@wp-playground/storage` mark Playground CLI as affected and therefore
execute the entire CLI suite. Running the focused test directly on the
current `trunk` commit reproduces the same failure, so this is
independent of the OPFS work.

Changing the schema to `varchar(500)` preserves the intended payload and
concurrency pressure while making the fixture internally valid.

## Impact

This changes test setup only. Production CLI and SQLite behavior are
unchanged.

This is the first of two stacked PRs. #4190 is stacked on this branch
and corrects the CI base/head calculation used by `nx affected`, which
currently compares `trunk` to itself on post-merge push runs and skips
affected Nx tasks.

## Testing

- `npm exec nx run playground-cli:test-playground-cli --
--testFile=sqlite-concurrent-publish.spec.ts`
- `npm exec nx run playground-cli:typecheck`
- `npm exec nx lint playground-cli`
- `npx nx format:check --base=origin/trunk --head=HEAD`
@ashfame ashfame closed this Jul 31, 2026
@ashfame
ashfame deleted the agent/add-embedded-site-opfs-path branch July 31, 2026 22:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment