Skip to content

feat(embed): the SuperWXR embed bridge — FastShaders inside another app's iframe - #2

Open
Alvis1 wants to merge 2 commits into
mainfrom
superwxr-embed
Open

Alvis1 wants to merge 2 commits into
mainfrom
superwxr-embed

Conversation

@Alvis1

@Alvis1 Alvis1 commented Oct 30, 2026

Copy link
Copy Markdown
Owner

What

FastShaders can now run as the shader editor of another app: opened at ?embed=1 inside an iframe, it mounts a small postMessage bridge (src/embed/EmbedBridge.tsx) that talks to the parent window at the page's own origin only. The host is SuperWXR, a WebXR game engine + editor whose Material component edits its shader in an embedded FastShaders.

Messages (all type: 'superwxr:*'):

direction message meaning
in superwxr:open { module, name? } open a shader .js export — a FASTSHADERS_PROJECT_V1 block restores the graph, a bare module is parsed back — through importShaderText, the "Load Script…" path (no Open/Add dialog)
out superwxr:ready the bridge listens
out superwxr:module { module, name } the settled EXPORT module (images inlined, project block appended), whenever the graph, the module settings or the name changed — trailing-debounced like the preview, deduped on the text
out superwxr:export { kind, fileName, mime, bytes } what downloadShader would have downloaded: when embedded, exportShader.ts posts the bytes to the host instead of saving them

How it stays out of the way

  • src/embed/embedMode.ts is the switch: a LEAF (imports nothing), sampled once at module init like eval/evalArm.ts, never true at the top level — only an embedding page can answer the bridge.
  • The bridge is a lazyOr chunk mounted from App.tsx on EvalGate's terms: an ordinary visitor's boot never creates the element and never fetches the chunk (it pulls the import path and the export assembly). bootWave.test.ts still passes.
  • Never in a study (isEmbedMode() && !isEvalMode()): it is a second way to change the graph.
  • Messages are accepted from window.parent at location.origin only; the module is type-checked before anything runs; the name is bounded (80 chars, extension stripped) and still goes through the import path's own sanitizeDroppedName.
  • The store subscription compares three references and the Output node's settings object by reference, so a drag frame costs nothing (the checklist's "selector on every notify" rule).

Tests and docs

  • src/embed/embedBridge.test.ts pins the shape: the switch is a leaf and off under node, App.tsx mounts the bridge lazily behind the switch and outside a study, the bridge answers the parent at its own origin only, EXPORT posts instead of downloading only when embedded.
  • npm run build (tsc -b + vite) and npm test green locally: 502 files, 11231 tests passed, 3 skipped.
  • Tree entries added to CLAUDE.md and docs/dev/project-structure.md.

The host side (the panel, the superwxr:* consumer, the browser suite that drives the real embed end-to-end) lives in SuperWXR; this is the whole FastShaders half.


Generated by Claude Code

Alvis1 and others added 2 commits October 30, 2026 10:34
…pp's iframe

`?embed=1` inside an iframe mounts src/embed/EmbedBridge.tsx (a lazyOr chunk
on EvalGate's terms — an ordinary boot never fetches it — and never in a
study): postMessage to and from the parent window at the page's own origin
only. `superwxr:open` restores a shader .js export through importShaderText
(the "Load Script…" path, no Open/Add dialog); `superwxr:module` broadcasts
the settled export module (images inlined, project block appended,
trailing-debounced like the preview, deduped on the text); `superwxr:export`
hands EXPORT's bytes to the host instead of the download folder
(exportShader.ts). src/embed/embedMode.ts is the switch: a LEAF sampled once
at module init, never true at the top level. The bridge's store subscription
compares three references and the Output node's settings object by reference,
so a drag frame costs nothing. Pinned by src/embed/embedBridge.test.ts; the
tree entries in CLAUDE.md and docs/dev/project-structure.md describe it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JrG3bmbgVjHDfnAHEaJEAE
…p after the first settle

As a lazyOr chunk the bridge mounts after the demo graph has settled, so a host
that only shows what the editor holds never saw a `superwxr:module`: nothing
changed the store again. The bridge now broadcasts once at mount, at the same
trailing delay, unless a `superwxr:open` arrives first — the import's own
settle broadcasts then. Found by SuperWXR's 30-shader browser suite against
the rebuilt vendored copy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JrG3bmbgVjHDfnAHEaJEAE

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant