fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync - #34

Merged
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild
Jun 12, 2026
Merged

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync#34
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Why this PR exists

PR #33 was merged correctly (thanks @zico15!) but its branch (copilot/fix-6666f3a8-e9fd-449a-9eb0-49c50f6f3c2d) was the Copilot branch reused from PR #15 — GitHub considered it already partially merged/dirty, so fix commits pushed after the merge could not be included. This is a clean replacement branch (fix/browser-playground-esbuild) based directly from the current TypeComposer/docs:main (dedd68a) with only the runtime fixes applied on top.


What was wrong (post-merge audit of #33)

SeverityBugImpact
🔴 CRITICALwasmURL: '/node_modules/esbuild-wasm/esbuild.wasm' — filesystem path, not a served URLVite doesn't serve node_modules/ via fetch; playground could never compile anything
🔴 HIGHtypeComposerVersion = "0.1.53" hardcodedSilent API mismatch vs package.json's ^0.1.56
🟡 MEDIUMNo sandbox attribute on preview iframeUser-compiled code had full access to window.parent and docs page DOM
🟡 MEDIUMBlob URL revoked via setTimeout(5000) — race + leakModule may not finish loading in 5s; URLs leaked between compile runs
🟡 MEDIUMcreateCodeBlobUrl() text-replaced from "typecomposer"Redundant (import map handles this), fragile (missed import(), subpaths, single quotes)
🟢 LOWDemo /package.json still had @codesandbox/sandpack-client + typecomposer@0.0.98Misleading sandpack migration artefact

Changes (2 commits)

Commit 1 — fix(playground): correct esbuild-wasm WASM URL — use Vite ?url import

src/utils/browserCompiler.ts

// Before (broken — 404 in dev and production):awaitesbuild.initialize({wasmURL: '/node_modules/esbuild-wasm/esbuild.wasm'});// After (correct — Vite copies to dist/assets/ with content hash):importwasmUrlfrom'esbuild-wasm/esbuild.wasm?url';awaitesbuild.initialize({wasmURL: wasmUrl});

src/vite-env.d.ts — added declare module '*.wasm?url' so TypeScript accepts the import.

Commit 2 — fix(playground): CDN version sync, iframe sandbox, blob URL cleanup, remove brittle import rewrite

src/views/playground/PlaygroundView.ts

  • "0.1.53" → module-level TYPECOMPOSER_VERSION = "0.1.56" constant with sync comment
  • this.iframe.setAttribute("sandbox", "allow-scripts") — iframe isolation (allow-same-origin intentionally omitted)
  • Removed createCodeBlobUrl() — blob URLs now tracked in pendingBlobUrls[], revoked at the start of each compile run and in new disconnectedCallback()
  • Removed fragile text-replace of from "typecomposer" — import map is the correct mechanism
  • Updated demo /package.json to typecomposer: "^0.1.56", removed sandpack artefacts

Build result

✓ 3486 modules transformed.
dist/assets/esbuild-BHljloGq.wasm 12,332.68 kB ← WASM correctly emitted to dist/assets/
dist/assets/index-C2_sis7Q.css 48.96 kB
dist/assets/index-CAvLbuBC.js 1,754.60 kB
✓ built in 14.86s

The esbuild.wasm asset is now present in dist/assets/ — confirming the ?url import works correctly and the playground will be able to compile code at runtime.


Does this fix the "branch was merged before" problem?

Yes. This branch (fix/browser-playground-esbuild) is a fresh branch from TypeComposer/docs:main at dedd68a. It has no shared history with the old Copilot branch — GitHub will treat it as a clean, unmerged branch with a clear diff.


Files changed

FileChange
src/utils/browserCompiler.ts?url WASM import, minor cleanup
src/vite-env.d.tsAdd *.wasm?url type declaration
src/views/playground/PlaygroundView.tsVersion constant, sandbox attr, blob URL tracking, remove brittle rewrite, fix demo package.json

cc @zico15@joaodibba

The previous code used a filesystem path '/node_modules/esbuild-wasm/esbuild.wasm'
as the wasmURL. Vite does NOT serve node_modules as static files via fetch —
the path 404s in dev and node_modules/ doesn't exist in production at all.
The playground could never compile anything as shipped.
Fix: replace with a Vite `?url` import so the WASM binary is copied to
dist/assets/ with a content-hash filename and served correctly in both
dev and prod.
Also add `declare module '*.wasm?url'` to src/vite-env.d.ts to satisfy
TypeScript for the new import.
FixesTypeComposer#14 post-merge regression.
…remove brittle import rewrite
Five runtime quality fixes for PlaygroundView:
1. TYPECOMPOSER_VERSION constant (0.1.53 → 0.1.56)
- Hardcoded '0.1.53' silently mismatched the package.json ^0.1.56 dep.
- Renamed to module-level TYPECOMPOSER_VERSION constant with a sync comment.
2. sandbox="allow-scripts" on preview iframe
- User-compiled code previously had full access to window.parent and could
mutate the docs page DOM. sandbox="allow-scripts" isolates the iframe.
- allow-same-origin intentionally omitted — blob: URLs are opaque-origin,
so omitting it makes the sandbox stricter, not weaker.
3. Blob URL memory leak / revocation race removed
- createCodeBlobUrl() used setTimeout(revoke, 5000) — a race condition
(module may not have finished loading) that also leaked URLs between runs.
- Fix: track blob URLs in pendingBlobUrls[], revoke at the start of the
next compile run and in disconnectedCallback(). disconnectedCallback()
also cancels the debounce timer.
4. Brittle import text-replace removed
- createCodeBlobUrl() did a regex replace of `from "typecomposer"` with the
CDN URL. This was redundant (import map handles it), fragile (missed
dynamic import(), subpath imports, single-quote variants), and now gone.
- The import map in the iframe HTML is the correct, complete mechanism.
5. Demo /package.json updated
- In-editor demo config still referenced @codesandbox/sandpack-client and
typecomposer@0.0.98 — sandpack migration artifacts left from before PR TypeComposer#15.
- Updated to reference only typecomposer at the current TYPECOMPOSER_VERSION.
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync - #34

Merged
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild
Jun 12, 2026
Merged

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync#34
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Why this PR exists

PR #33 was merged correctly (thanks @zico15!) but its branch (copilot/fix-6666f3a8-e9fd-449a-9eb0-49c50f6f3c2d) was the Copilot branch reused from PR #15 — GitHub considered it already partially merged/dirty, so fix commits pushed after the merge could not be included. This is a clean replacement branch (fix/browser-playground-esbuild) based directly from the current TypeComposer/docs:main (dedd68a) with only the runtime fixes applied on top.


What was wrong (post-merge audit of #33)

SeverityBugImpact
🔴 CRITICALwasmURL: '/node_modules/esbuild-wasm/esbuild.wasm' — filesystem path, not a served URLVite doesn't serve node_modules/ via fetch; playground could never compile anything
🔴 HIGHtypeComposerVersion = "0.1.53" hardcodedSilent API mismatch vs package.json's ^0.1.56
🟡 MEDIUMNo sandbox attribute on preview iframeUser-compiled code had full access to window.parent and docs page DOM
🟡 MEDIUMBlob URL revoked via setTimeout(5000) — race + leakModule may not finish loading in 5s; URLs leaked between compile runs
🟡 MEDIUMcreateCodeBlobUrl() text-replaced from "typecomposer"Redundant (import map handles this), fragile (missed import(), subpaths, single quotes)
🟢 LOWDemo /package.json still had @codesandbox/sandpack-client + typecomposer@0.0.98Misleading sandpack migration artefact

Changes (2 commits)

Commit 1 — fix(playground): correct esbuild-wasm WASM URL — use Vite ?url import

src/utils/browserCompiler.ts

// Before (broken — 404 in dev and production):awaitesbuild.initialize({wasmURL: '/node_modules/esbuild-wasm/esbuild.wasm'});// After (correct — Vite copies to dist/assets/ with content hash):importwasmUrlfrom'esbuild-wasm/esbuild.wasm?url';awaitesbuild.initialize({wasmURL: wasmUrl});

src/vite-env.d.ts — added declare module '*.wasm?url' so TypeScript accepts the import.

Commit 2 — fix(playground): CDN version sync, iframe sandbox, blob URL cleanup, remove brittle import rewrite

src/views/playground/PlaygroundView.ts

  • "0.1.53" → module-level TYPECOMPOSER_VERSION = "0.1.56" constant with sync comment
  • this.iframe.setAttribute("sandbox", "allow-scripts") — iframe isolation (allow-same-origin intentionally omitted)
  • Removed createCodeBlobUrl() — blob URLs now tracked in pendingBlobUrls[], revoked at the start of each compile run and in new disconnectedCallback()
  • Removed fragile text-replace of from "typecomposer" — import map is the correct mechanism
  • Updated demo /package.json to typecomposer: "^0.1.56", removed sandpack artefacts

Build result

✓ 3486 modules transformed.
dist/assets/esbuild-BHljloGq.wasm 12,332.68 kB ← WASM correctly emitted to dist/assets/
dist/assets/index-C2_sis7Q.css 48.96 kB
dist/assets/index-CAvLbuBC.js 1,754.60 kB
✓ built in 14.86s

The esbuild.wasm asset is now present in dist/assets/ — confirming the ?url import works correctly and the playground will be able to compile code at runtime.


Does this fix the "branch was merged before" problem?

Yes. This branch (fix/browser-playground-esbuild) is a fresh branch from TypeComposer/docs:main at dedd68a. It has no shared history with the old Copilot branch — GitHub will treat it as a clean, unmerged branch with a clear diff.


Files changed

FileChange
src/utils/browserCompiler.ts?url WASM import, minor cleanup
src/vite-env.d.tsAdd *.wasm?url type declaration
src/views/playground/PlaygroundView.tsVersion constant, sandbox attr, blob URL tracking, remove brittle rewrite, fix demo package.json

cc @zico15@joaodibba

The previous code used a filesystem path '/node_modules/esbuild-wasm/esbuild.wasm'
as the wasmURL. Vite does NOT serve node_modules as static files via fetch —
the path 404s in dev and node_modules/ doesn't exist in production at all.
The playground could never compile anything as shipped.
Fix: replace with a Vite `?url` import so the WASM binary is copied to
dist/assets/ with a content-hash filename and served correctly in both
dev and prod.
Also add `declare module '*.wasm?url'` to src/vite-env.d.ts to satisfy
TypeScript for the new import.
FixesTypeComposer#14 post-merge regression.
…remove brittle import rewrite
Five runtime quality fixes for PlaygroundView:
1. TYPECOMPOSER_VERSION constant (0.1.53 → 0.1.56)
- Hardcoded '0.1.53' silently mismatched the package.json ^0.1.56 dep.
- Renamed to module-level TYPECOMPOSER_VERSION constant with a sync comment.
2. sandbox="allow-scripts" on preview iframe
- User-compiled code previously had full access to window.parent and could
mutate the docs page DOM. sandbox="allow-scripts" isolates the iframe.
- allow-same-origin intentionally omitted — blob: URLs are opaque-origin,
so omitting it makes the sandbox stricter, not weaker.
3. Blob URL memory leak / revocation race removed
- createCodeBlobUrl() used setTimeout(revoke, 5000) — a race condition
(module may not have finished loading) that also leaked URLs between runs.
- Fix: track blob URLs in pendingBlobUrls[], revoke at the start of the
next compile run and in disconnectedCallback(). disconnectedCallback()
also cancels the debounce timer.
4. Brittle import text-replace removed
- createCodeBlobUrl() did a regex replace of `from "typecomposer"` with the
CDN URL. This was redundant (import map handles it), fragile (missed
dynamic import(), subpath imports, single-quote variants), and now gone.
- The import map in the iframe HTML is the correct, complete mechanism.
5. Demo /package.json updated
- In-editor demo config still referenced @codesandbox/sandpack-client and
typecomposer@0.0.98 — sandpack migration artifacts left from before PR TypeComposer#15.
- Updated to reference only typecomposer at the current TYPECOMPOSER_VERSION.
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync - #34

Merged
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild
Jun 12, 2026
Merged

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync#34
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Why this PR exists

PR #33 was merged correctly (thanks @zico15!) but its branch (copilot/fix-6666f3a8-e9fd-449a-9eb0-49c50f6f3c2d) was the Copilot branch reused from PR #15 — GitHub considered it already partially merged/dirty, so fix commits pushed after the merge could not be included. This is a clean replacement branch (fix/browser-playground-esbuild) based directly from the current TypeComposer/docs:main (dedd68a) with only the runtime fixes applied on top.


What was wrong (post-merge audit of #33)

SeverityBugImpact
🔴 CRITICALwasmURL: '/node_modules/esbuild-wasm/esbuild.wasm' — filesystem path, not a served URLVite doesn't serve node_modules/ via fetch; playground could never compile anything
🔴 HIGHtypeComposerVersion = "0.1.53" hardcodedSilent API mismatch vs package.json's ^0.1.56
🟡 MEDIUMNo sandbox attribute on preview iframeUser-compiled code had full access to window.parent and docs page DOM
🟡 MEDIUMBlob URL revoked via setTimeout(5000) — race + leakModule may not finish loading in 5s; URLs leaked between compile runs
🟡 MEDIUMcreateCodeBlobUrl() text-replaced from "typecomposer"Redundant (import map handles this), fragile (missed import(), subpaths, single quotes)
🟢 LOWDemo /package.json still had @codesandbox/sandpack-client + typecomposer@0.0.98Misleading sandpack migration artefact

Changes (2 commits)

Commit 1 — fix(playground): correct esbuild-wasm WASM URL — use Vite ?url import

src/utils/browserCompiler.ts

// Before (broken — 404 in dev and production):awaitesbuild.initialize({wasmURL: '/node_modules/esbuild-wasm/esbuild.wasm'});// After (correct — Vite copies to dist/assets/ with content hash):importwasmUrlfrom'esbuild-wasm/esbuild.wasm?url';awaitesbuild.initialize({wasmURL: wasmUrl});

src/vite-env.d.ts — added declare module '*.wasm?url' so TypeScript accepts the import.

Commit 2 — fix(playground): CDN version sync, iframe sandbox, blob URL cleanup, remove brittle import rewrite

src/views/playground/PlaygroundView.ts

  • "0.1.53" → module-level TYPECOMPOSER_VERSION = "0.1.56" constant with sync comment
  • this.iframe.setAttribute("sandbox", "allow-scripts") — iframe isolation (allow-same-origin intentionally omitted)
  • Removed createCodeBlobUrl() — blob URLs now tracked in pendingBlobUrls[], revoked at the start of each compile run and in new disconnectedCallback()
  • Removed fragile text-replace of from "typecomposer" — import map is the correct mechanism
  • Updated demo /package.json to typecomposer: "^0.1.56", removed sandpack artefacts

Build result

✓ 3486 modules transformed.
dist/assets/esbuild-BHljloGq.wasm 12,332.68 kB ← WASM correctly emitted to dist/assets/
dist/assets/index-C2_sis7Q.css 48.96 kB
dist/assets/index-CAvLbuBC.js 1,754.60 kB
✓ built in 14.86s

The esbuild.wasm asset is now present in dist/assets/ — confirming the ?url import works correctly and the playground will be able to compile code at runtime.


Does this fix the "branch was merged before" problem?

Yes. This branch (fix/browser-playground-esbuild) is a fresh branch from TypeComposer/docs:main at dedd68a. It has no shared history with the old Copilot branch — GitHub will treat it as a clean, unmerged branch with a clear diff.


Files changed

FileChange
src/utils/browserCompiler.ts?url WASM import, minor cleanup
src/vite-env.d.tsAdd *.wasm?url type declaration
src/views/playground/PlaygroundView.tsVersion constant, sandbox attr, blob URL tracking, remove brittle rewrite, fix demo package.json

cc @zico15@joaodibba

The previous code used a filesystem path '/node_modules/esbuild-wasm/esbuild.wasm'
as the wasmURL. Vite does NOT serve node_modules as static files via fetch —
the path 404s in dev and node_modules/ doesn't exist in production at all.
The playground could never compile anything as shipped.
Fix: replace with a Vite `?url` import so the WASM binary is copied to
dist/assets/ with a content-hash filename and served correctly in both
dev and prod.
Also add `declare module '*.wasm?url'` to src/vite-env.d.ts to satisfy
TypeScript for the new import.
FixesTypeComposer#14 post-merge regression.
…remove brittle import rewrite
Five runtime quality fixes for PlaygroundView:
1. TYPECOMPOSER_VERSION constant (0.1.53 → 0.1.56)
- Hardcoded '0.1.53' silently mismatched the package.json ^0.1.56 dep.
- Renamed to module-level TYPECOMPOSER_VERSION constant with a sync comment.
2. sandbox="allow-scripts" on preview iframe
- User-compiled code previously had full access to window.parent and could
mutate the docs page DOM. sandbox="allow-scripts" isolates the iframe.
- allow-same-origin intentionally omitted — blob: URLs are opaque-origin,
so omitting it makes the sandbox stricter, not weaker.
3. Blob URL memory leak / revocation race removed
- createCodeBlobUrl() used setTimeout(revoke, 5000) — a race condition
(module may not have finished loading) that also leaked URLs between runs.
- Fix: track blob URLs in pendingBlobUrls[], revoke at the start of the
next compile run and in disconnectedCallback(). disconnectedCallback()
also cancels the debounce timer.
4. Brittle import text-replace removed
- createCodeBlobUrl() did a regex replace of `from "typecomposer"` with the
CDN URL. This was redundant (import map handles it), fragile (missed
dynamic import(), subpath imports, single-quote variants), and now gone.
- The import map in the iframe HTML is the correct, complete mechanism.
5. Demo /package.json updated
- In-editor demo config still referenced @codesandbox/sandpack-client and
typecomposer@0.0.98 — sandpack migration artifacts left from before PR TypeComposer#15.
- Updated to reference only typecomposer at the current TYPECOMPOSER_VERSION.
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync - #34

Merged
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild
Jun 12, 2026
Merged

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync#34
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Why this PR exists

PR #33 was merged correctly (thanks @zico15!) but its branch (copilot/fix-6666f3a8-e9fd-449a-9eb0-49c50f6f3c2d) was the Copilot branch reused from PR #15 — GitHub considered it already partially merged/dirty, so fix commits pushed after the merge could not be included. This is a clean replacement branch (fix/browser-playground-esbuild) based directly from the current TypeComposer/docs:main (dedd68a) with only the runtime fixes applied on top.


What was wrong (post-merge audit of #33)

SeverityBugImpact
🔴 CRITICALwasmURL: '/node_modules/esbuild-wasm/esbuild.wasm' — filesystem path, not a served URLVite doesn't serve node_modules/ via fetch; playground could never compile anything
🔴 HIGHtypeComposerVersion = "0.1.53" hardcodedSilent API mismatch vs package.json's ^0.1.56
🟡 MEDIUMNo sandbox attribute on preview iframeUser-compiled code had full access to window.parent and docs page DOM
🟡 MEDIUMBlob URL revoked via setTimeout(5000) — race + leakModule may not finish loading in 5s; URLs leaked between compile runs
🟡 MEDIUMcreateCodeBlobUrl() text-replaced from "typecomposer"Redundant (import map handles this), fragile (missed import(), subpaths, single quotes)
🟢 LOWDemo /package.json still had @codesandbox/sandpack-client + typecomposer@0.0.98Misleading sandpack migration artefact

Changes (2 commits)

Commit 1 — fix(playground): correct esbuild-wasm WASM URL — use Vite ?url import

src/utils/browserCompiler.ts

// Before (broken — 404 in dev and production):awaitesbuild.initialize({wasmURL: '/node_modules/esbuild-wasm/esbuild.wasm'});// After (correct — Vite copies to dist/assets/ with content hash):importwasmUrlfrom'esbuild-wasm/esbuild.wasm?url';awaitesbuild.initialize({wasmURL: wasmUrl});

src/vite-env.d.ts — added declare module '*.wasm?url' so TypeScript accepts the import.

Commit 2 — fix(playground): CDN version sync, iframe sandbox, blob URL cleanup, remove brittle import rewrite

src/views/playground/PlaygroundView.ts

  • "0.1.53" → module-level TYPECOMPOSER_VERSION = "0.1.56" constant with sync comment
  • this.iframe.setAttribute("sandbox", "allow-scripts") — iframe isolation (allow-same-origin intentionally omitted)
  • Removed createCodeBlobUrl() — blob URLs now tracked in pendingBlobUrls[], revoked at the start of each compile run and in new disconnectedCallback()
  • Removed fragile text-replace of from "typecomposer" — import map is the correct mechanism
  • Updated demo /package.json to typecomposer: "^0.1.56", removed sandpack artefacts

Build result

✓ 3486 modules transformed.
dist/assets/esbuild-BHljloGq.wasm 12,332.68 kB ← WASM correctly emitted to dist/assets/
dist/assets/index-C2_sis7Q.css 48.96 kB
dist/assets/index-CAvLbuBC.js 1,754.60 kB
✓ built in 14.86s

The esbuild.wasm asset is now present in dist/assets/ — confirming the ?url import works correctly and the playground will be able to compile code at runtime.


Does this fix the "branch was merged before" problem?

Yes. This branch (fix/browser-playground-esbuild) is a fresh branch from TypeComposer/docs:main at dedd68a. It has no shared history with the old Copilot branch — GitHub will treat it as a clean, unmerged branch with a clear diff.


Files changed

FileChange
src/utils/browserCompiler.ts?url WASM import, minor cleanup
src/vite-env.d.tsAdd *.wasm?url type declaration
src/views/playground/PlaygroundView.tsVersion constant, sandbox attr, blob URL tracking, remove brittle rewrite, fix demo package.json

cc @zico15@joaodibba

The previous code used a filesystem path '/node_modules/esbuild-wasm/esbuild.wasm'
as the wasmURL. Vite does NOT serve node_modules as static files via fetch —
the path 404s in dev and node_modules/ doesn't exist in production at all.
The playground could never compile anything as shipped.
Fix: replace with a Vite `?url` import so the WASM binary is copied to
dist/assets/ with a content-hash filename and served correctly in both
dev and prod.
Also add `declare module '*.wasm?url'` to src/vite-env.d.ts to satisfy
TypeScript for the new import.
FixesTypeComposer#14 post-merge regression.
…remove brittle import rewrite
Five runtime quality fixes for PlaygroundView:
1. TYPECOMPOSER_VERSION constant (0.1.53 → 0.1.56)
- Hardcoded '0.1.53' silently mismatched the package.json ^0.1.56 dep.
- Renamed to module-level TYPECOMPOSER_VERSION constant with a sync comment.
2. sandbox="allow-scripts" on preview iframe
- User-compiled code previously had full access to window.parent and could
mutate the docs page DOM. sandbox="allow-scripts" isolates the iframe.
- allow-same-origin intentionally omitted — blob: URLs are opaque-origin,
so omitting it makes the sandbox stricter, not weaker.
3. Blob URL memory leak / revocation race removed
- createCodeBlobUrl() used setTimeout(revoke, 5000) — a race condition
(module may not have finished loading) that also leaked URLs between runs.
- Fix: track blob URLs in pendingBlobUrls[], revoke at the start of the
next compile run and in disconnectedCallback(). disconnectedCallback()
also cancels the debounce timer.
4. Brittle import text-replace removed
- createCodeBlobUrl() did a regex replace of `from "typecomposer"` with the
CDN URL. This was redundant (import map handles it), fragile (missed
dynamic import(), subpath imports, single-quote variants), and now gone.
- The import map in the iframe HTML is the correct, complete mechanism.
5. Demo /package.json updated
- In-editor demo config still referenced @codesandbox/sandpack-client and
typecomposer@0.0.98 — sandpack migration artifacts left from before PR TypeComposer#15.
- Updated to reference only typecomposer at the current TYPECOMPOSER_VERSION.
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync - #34

Merged
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild
Jun 12, 2026
Merged

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync#34
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Why this PR exists

PR #33 was merged correctly (thanks @zico15!) but its branch (copilot/fix-6666f3a8-e9fd-449a-9eb0-49c50f6f3c2d) was the Copilot branch reused from PR #15 — GitHub considered it already partially merged/dirty, so fix commits pushed after the merge could not be included. This is a clean replacement branch (fix/browser-playground-esbuild) based directly from the current TypeComposer/docs:main (dedd68a) with only the runtime fixes applied on top.


What was wrong (post-merge audit of #33)

SeverityBugImpact
🔴 CRITICALwasmURL: '/node_modules/esbuild-wasm/esbuild.wasm' — filesystem path, not a served URLVite doesn't serve node_modules/ via fetch; playground could never compile anything
🔴 HIGHtypeComposerVersion = "0.1.53" hardcodedSilent API mismatch vs package.json's ^0.1.56
🟡 MEDIUMNo sandbox attribute on preview iframeUser-compiled code had full access to window.parent and docs page DOM
🟡 MEDIUMBlob URL revoked via setTimeout(5000) — race + leakModule may not finish loading in 5s; URLs leaked between compile runs
🟡 MEDIUMcreateCodeBlobUrl() text-replaced from "typecomposer"Redundant (import map handles this), fragile (missed import(), subpaths, single quotes)
🟢 LOWDemo /package.json still had @codesandbox/sandpack-client + typecomposer@0.0.98Misleading sandpack migration artefact

Changes (2 commits)

Commit 1 — fix(playground): correct esbuild-wasm WASM URL — use Vite ?url import

src/utils/browserCompiler.ts

// Before (broken — 404 in dev and production):awaitesbuild.initialize({wasmURL: '/node_modules/esbuild-wasm/esbuild.wasm'});// After (correct — Vite copies to dist/assets/ with content hash):importwasmUrlfrom'esbuild-wasm/esbuild.wasm?url';awaitesbuild.initialize({wasmURL: wasmUrl});

src/vite-env.d.ts — added declare module '*.wasm?url' so TypeScript accepts the import.

Commit 2 — fix(playground): CDN version sync, iframe sandbox, blob URL cleanup, remove brittle import rewrite

src/views/playground/PlaygroundView.ts

  • "0.1.53" → module-level TYPECOMPOSER_VERSION = "0.1.56" constant with sync comment
  • this.iframe.setAttribute("sandbox", "allow-scripts") — iframe isolation (allow-same-origin intentionally omitted)
  • Removed createCodeBlobUrl() — blob URLs now tracked in pendingBlobUrls[], revoked at the start of each compile run and in new disconnectedCallback()
  • Removed fragile text-replace of from "typecomposer" — import map is the correct mechanism
  • Updated demo /package.json to typecomposer: "^0.1.56", removed sandpack artefacts

Build result

✓ 3486 modules transformed.
dist/assets/esbuild-BHljloGq.wasm 12,332.68 kB ← WASM correctly emitted to dist/assets/
dist/assets/index-C2_sis7Q.css 48.96 kB
dist/assets/index-CAvLbuBC.js 1,754.60 kB
✓ built in 14.86s

The esbuild.wasm asset is now present in dist/assets/ — confirming the ?url import works correctly and the playground will be able to compile code at runtime.


Does this fix the "branch was merged before" problem?

Yes. This branch (fix/browser-playground-esbuild) is a fresh branch from TypeComposer/docs:main at dedd68a. It has no shared history with the old Copilot branch — GitHub will treat it as a clean, unmerged branch with a clear diff.


Files changed

FileChange
src/utils/browserCompiler.ts?url WASM import, minor cleanup
src/vite-env.d.tsAdd *.wasm?url type declaration
src/views/playground/PlaygroundView.tsVersion constant, sandbox attr, blob URL tracking, remove brittle rewrite, fix demo package.json

cc @zico15@joaodibba

The previous code used a filesystem path '/node_modules/esbuild-wasm/esbuild.wasm'
as the wasmURL. Vite does NOT serve node_modules as static files via fetch —
the path 404s in dev and node_modules/ doesn't exist in production at all.
The playground could never compile anything as shipped.
Fix: replace with a Vite `?url` import so the WASM binary is copied to
dist/assets/ with a content-hash filename and served correctly in both
dev and prod.
Also add `declare module '*.wasm?url'` to src/vite-env.d.ts to satisfy
TypeScript for the new import.
FixesTypeComposer#14 post-merge regression.
…remove brittle import rewrite
Five runtime quality fixes for PlaygroundView:
1. TYPECOMPOSER_VERSION constant (0.1.53 → 0.1.56)
- Hardcoded '0.1.53' silently mismatched the package.json ^0.1.56 dep.
- Renamed to module-level TYPECOMPOSER_VERSION constant with a sync comment.
2. sandbox="allow-scripts" on preview iframe
- User-compiled code previously had full access to window.parent and could
mutate the docs page DOM. sandbox="allow-scripts" isolates the iframe.
- allow-same-origin intentionally omitted — blob: URLs are opaque-origin,
so omitting it makes the sandbox stricter, not weaker.
3. Blob URL memory leak / revocation race removed
- createCodeBlobUrl() used setTimeout(revoke, 5000) — a race condition
(module may not have finished loading) that also leaked URLs between runs.
- Fix: track blob URLs in pendingBlobUrls[], revoke at the start of the
next compile run and in disconnectedCallback(). disconnectedCallback()
also cancels the debounce timer.
4. Brittle import text-replace removed
- createCodeBlobUrl() did a regex replace of `from "typecomposer"` with the
CDN URL. This was redundant (import map handles it), fragile (missed
dynamic import(), subpath imports, single-quote variants), and now gone.
- The import map in the iframe HTML is the correct, complete mechanism.
5. Demo /package.json updated
- In-editor demo config still referenced @codesandbox/sandpack-client and
typecomposer@0.0.98 — sandpack migration artifacts left from before PR TypeComposer#15.
- Updated to reference only typecomposer at the current TYPECOMPOSER_VERSION.
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync - #34

Merged
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild
Jun 12, 2026
Merged

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync#34
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Why this PR exists

PR #33 was merged correctly (thanks @zico15!) but its branch (copilot/fix-6666f3a8-e9fd-449a-9eb0-49c50f6f3c2d) was the Copilot branch reused from PR #15 — GitHub considered it already partially merged/dirty, so fix commits pushed after the merge could not be included. This is a clean replacement branch (fix/browser-playground-esbuild) based directly from the current TypeComposer/docs:main (dedd68a) with only the runtime fixes applied on top.


What was wrong (post-merge audit of #33)

SeverityBugImpact
🔴 CRITICALwasmURL: '/node_modules/esbuild-wasm/esbuild.wasm' — filesystem path, not a served URLVite doesn't serve node_modules/ via fetch; playground could never compile anything
🔴 HIGHtypeComposerVersion = "0.1.53" hardcodedSilent API mismatch vs package.json's ^0.1.56
🟡 MEDIUMNo sandbox attribute on preview iframeUser-compiled code had full access to window.parent and docs page DOM
🟡 MEDIUMBlob URL revoked via setTimeout(5000) — race + leakModule may not finish loading in 5s; URLs leaked between compile runs
🟡 MEDIUMcreateCodeBlobUrl() text-replaced from "typecomposer"Redundant (import map handles this), fragile (missed import(), subpaths, single quotes)
🟢 LOWDemo /package.json still had @codesandbox/sandpack-client + typecomposer@0.0.98Misleading sandpack migration artefact

Changes (2 commits)

Commit 1 — fix(playground): correct esbuild-wasm WASM URL — use Vite ?url import

src/utils/browserCompiler.ts

// Before (broken — 404 in dev and production):awaitesbuild.initialize({wasmURL: '/node_modules/esbuild-wasm/esbuild.wasm'});// After (correct — Vite copies to dist/assets/ with content hash):importwasmUrlfrom'esbuild-wasm/esbuild.wasm?url';awaitesbuild.initialize({wasmURL: wasmUrl});

src/vite-env.d.ts — added declare module '*.wasm?url' so TypeScript accepts the import.

Commit 2 — fix(playground): CDN version sync, iframe sandbox, blob URL cleanup, remove brittle import rewrite

src/views/playground/PlaygroundView.ts

  • "0.1.53" → module-level TYPECOMPOSER_VERSION = "0.1.56" constant with sync comment
  • this.iframe.setAttribute("sandbox", "allow-scripts") — iframe isolation (allow-same-origin intentionally omitted)
  • Removed createCodeBlobUrl() — blob URLs now tracked in pendingBlobUrls[], revoked at the start of each compile run and in new disconnectedCallback()
  • Removed fragile text-replace of from "typecomposer" — import map is the correct mechanism
  • Updated demo /package.json to typecomposer: "^0.1.56", removed sandpack artefacts

Build result

✓ 3486 modules transformed.
dist/assets/esbuild-BHljloGq.wasm 12,332.68 kB ← WASM correctly emitted to dist/assets/
dist/assets/index-C2_sis7Q.css 48.96 kB
dist/assets/index-CAvLbuBC.js 1,754.60 kB
✓ built in 14.86s

The esbuild.wasm asset is now present in dist/assets/ — confirming the ?url import works correctly and the playground will be able to compile code at runtime.


Does this fix the "branch was merged before" problem?

Yes. This branch (fix/browser-playground-esbuild) is a fresh branch from TypeComposer/docs:main at dedd68a. It has no shared history with the old Copilot branch — GitHub will treat it as a clean, unmerged branch with a clear diff.


Files changed

FileChange
src/utils/browserCompiler.ts?url WASM import, minor cleanup
src/vite-env.d.tsAdd *.wasm?url type declaration
src/views/playground/PlaygroundView.tsVersion constant, sandbox attr, blob URL tracking, remove brittle rewrite, fix demo package.json

cc @zico15@joaodibba

The previous code used a filesystem path '/node_modules/esbuild-wasm/esbuild.wasm'
as the wasmURL. Vite does NOT serve node_modules as static files via fetch —
the path 404s in dev and node_modules/ doesn't exist in production at all.
The playground could never compile anything as shipped.
Fix: replace with a Vite `?url` import so the WASM binary is copied to
dist/assets/ with a content-hash filename and served correctly in both
dev and prod.
Also add `declare module '*.wasm?url'` to src/vite-env.d.ts to satisfy
TypeScript for the new import.
FixesTypeComposer#14 post-merge regression.
…remove brittle import rewrite
Five runtime quality fixes for PlaygroundView:
1. TYPECOMPOSER_VERSION constant (0.1.53 → 0.1.56)
- Hardcoded '0.1.53' silently mismatched the package.json ^0.1.56 dep.
- Renamed to module-level TYPECOMPOSER_VERSION constant with a sync comment.
2. sandbox="allow-scripts" on preview iframe
- User-compiled code previously had full access to window.parent and could
mutate the docs page DOM. sandbox="allow-scripts" isolates the iframe.
- allow-same-origin intentionally omitted — blob: URLs are opaque-origin,
so omitting it makes the sandbox stricter, not weaker.
3. Blob URL memory leak / revocation race removed
- createCodeBlobUrl() used setTimeout(revoke, 5000) — a race condition
(module may not have finished loading) that also leaked URLs between runs.
- Fix: track blob URLs in pendingBlobUrls[], revoke at the start of the
next compile run and in disconnectedCallback(). disconnectedCallback()
also cancels the debounce timer.
4. Brittle import text-replace removed
- createCodeBlobUrl() did a regex replace of `from "typecomposer"` with the
CDN URL. This was redundant (import map handles it), fragile (missed
dynamic import(), subpath imports, single-quote variants), and now gone.
- The import map in the iframe HTML is the correct, complete mechanism.
5. Demo /package.json updated
- In-editor demo config still referenced @codesandbox/sandpack-client and
typecomposer@0.0.98 — sandpack migration artifacts left from before PR TypeComposer#15.
- Updated to reference only typecomposer at the current TYPECOMPOSER_VERSION.
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync - #34

Merged
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild
Jun 12, 2026
Merged

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync#34
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Why this PR exists

PR #33 was merged correctly (thanks @zico15!) but its branch (copilot/fix-6666f3a8-e9fd-449a-9eb0-49c50f6f3c2d) was the Copilot branch reused from PR #15 — GitHub considered it already partially merged/dirty, so fix commits pushed after the merge could not be included. This is a clean replacement branch (fix/browser-playground-esbuild) based directly from the current TypeComposer/docs:main (dedd68a) with only the runtime fixes applied on top.


What was wrong (post-merge audit of #33)

SeverityBugImpact
🔴 CRITICALwasmURL: '/node_modules/esbuild-wasm/esbuild.wasm' — filesystem path, not a served URLVite doesn't serve node_modules/ via fetch; playground could never compile anything
🔴 HIGHtypeComposerVersion = "0.1.53" hardcodedSilent API mismatch vs package.json's ^0.1.56
🟡 MEDIUMNo sandbox attribute on preview iframeUser-compiled code had full access to window.parent and docs page DOM
🟡 MEDIUMBlob URL revoked via setTimeout(5000) — race + leakModule may not finish loading in 5s; URLs leaked between compile runs
🟡 MEDIUMcreateCodeBlobUrl() text-replaced from "typecomposer"Redundant (import map handles this), fragile (missed import(), subpaths, single quotes)
🟢 LOWDemo /package.json still had @codesandbox/sandpack-client + typecomposer@0.0.98Misleading sandpack migration artefact

Changes (2 commits)

Commit 1 — fix(playground): correct esbuild-wasm WASM URL — use Vite ?url import

src/utils/browserCompiler.ts

// Before (broken — 404 in dev and production):awaitesbuild.initialize({wasmURL: '/node_modules/esbuild-wasm/esbuild.wasm'});// After (correct — Vite copies to dist/assets/ with content hash):importwasmUrlfrom'esbuild-wasm/esbuild.wasm?url';awaitesbuild.initialize({wasmURL: wasmUrl});

src/vite-env.d.ts — added declare module '*.wasm?url' so TypeScript accepts the import.

Commit 2 — fix(playground): CDN version sync, iframe sandbox, blob URL cleanup, remove brittle import rewrite

src/views/playground/PlaygroundView.ts

  • "0.1.53" → module-level TYPECOMPOSER_VERSION = "0.1.56" constant with sync comment
  • this.iframe.setAttribute("sandbox", "allow-scripts") — iframe isolation (allow-same-origin intentionally omitted)
  • Removed createCodeBlobUrl() — blob URLs now tracked in pendingBlobUrls[], revoked at the start of each compile run and in new disconnectedCallback()
  • Removed fragile text-replace of from "typecomposer" — import map is the correct mechanism
  • Updated demo /package.json to typecomposer: "^0.1.56", removed sandpack artefacts

Build result

✓ 3486 modules transformed.
dist/assets/esbuild-BHljloGq.wasm 12,332.68 kB ← WASM correctly emitted to dist/assets/
dist/assets/index-C2_sis7Q.css 48.96 kB
dist/assets/index-CAvLbuBC.js 1,754.60 kB
✓ built in 14.86s

The esbuild.wasm asset is now present in dist/assets/ — confirming the ?url import works correctly and the playground will be able to compile code at runtime.


Does this fix the "branch was merged before" problem?

Yes. This branch (fix/browser-playground-esbuild) is a fresh branch from TypeComposer/docs:main at dedd68a. It has no shared history with the old Copilot branch — GitHub will treat it as a clean, unmerged branch with a clear diff.


Files changed

FileChange
src/utils/browserCompiler.ts?url WASM import, minor cleanup
src/vite-env.d.tsAdd *.wasm?url type declaration
src/views/playground/PlaygroundView.tsVersion constant, sandbox attr, blob URL tracking, remove brittle rewrite, fix demo package.json

cc @zico15@joaodibba

The previous code used a filesystem path '/node_modules/esbuild-wasm/esbuild.wasm'
as the wasmURL. Vite does NOT serve node_modules as static files via fetch —
the path 404s in dev and node_modules/ doesn't exist in production at all.
The playground could never compile anything as shipped.
Fix: replace with a Vite `?url` import so the WASM binary is copied to
dist/assets/ with a content-hash filename and served correctly in both
dev and prod.
Also add `declare module '*.wasm?url'` to src/vite-env.d.ts to satisfy
TypeScript for the new import.
FixesTypeComposer#14 post-merge regression.
…remove brittle import rewrite
Five runtime quality fixes for PlaygroundView:
1. TYPECOMPOSER_VERSION constant (0.1.53 → 0.1.56)
- Hardcoded '0.1.53' silently mismatched the package.json ^0.1.56 dep.
- Renamed to module-level TYPECOMPOSER_VERSION constant with a sync comment.
2. sandbox="allow-scripts" on preview iframe
- User-compiled code previously had full access to window.parent and could
mutate the docs page DOM. sandbox="allow-scripts" isolates the iframe.
- allow-same-origin intentionally omitted — blob: URLs are opaque-origin,
so omitting it makes the sandbox stricter, not weaker.
3. Blob URL memory leak / revocation race removed
- createCodeBlobUrl() used setTimeout(revoke, 5000) — a race condition
(module may not have finished loading) that also leaked URLs between runs.
- Fix: track blob URLs in pendingBlobUrls[], revoke at the start of the
next compile run and in disconnectedCallback(). disconnectedCallback()
also cancels the debounce timer.
4. Brittle import text-replace removed
- createCodeBlobUrl() did a regex replace of `from "typecomposer"` with the
CDN URL. This was redundant (import map handles it), fragile (missed
dynamic import(), subpath imports, single-quote variants), and now gone.
- The import map in the iframe HTML is the correct, complete mechanism.
5. Demo /package.json updated
- In-editor demo config still referenced @codesandbox/sandpack-client and
typecomposer@0.0.98 — sandpack migration artifacts left from before PR TypeComposer#15.
- Updated to reference only typecomposer at the current TYPECOMPOSER_VERSION.
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync - #34

Merged
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild
Jun 12, 2026
Merged

fix(playground): runtime fixes — esbuild WASM URL, iframe sandbox, blob cleanup, CDN version sync#34
joaodibba merged 2 commits into
TypeComposer:mainfrom
lucas-spin:fix/browser-playground-esbuild

Conversation

@lucas-spin

Copy link
Copy Markdown
Contributor

Why this PR exists

PR #33 was merged correctly (thanks @zico15!) but its branch (copilot/fix-6666f3a8-e9fd-449a-9eb0-49c50f6f3c2d) was the Copilot branch reused from PR #15 — GitHub considered it already partially merged/dirty, so fix commits pushed after the merge could not be included. This is a clean replacement branch (fix/browser-playground-esbuild) based directly from the current TypeComposer/docs:main (dedd68a) with only the runtime fixes applied on top.


What was wrong (post-merge audit of #33)

SeverityBugImpact
🔴 CRITICALwasmURL: '/node_modules/esbuild-wasm/esbuild.wasm' — filesystem path, not a served URLVite doesn't serve node_modules/ via fetch; playground could never compile anything
🔴 HIGHtypeComposerVersion = "0.1.53" hardcodedSilent API mismatch vs package.json's ^0.1.56
🟡 MEDIUMNo sandbox attribute on preview iframeUser-compiled code had full access to window.parent and docs page DOM
🟡 MEDIUMBlob URL revoked via setTimeout(5000) — race + leakModule may not finish loading in 5s; URLs leaked between compile runs
🟡 MEDIUMcreateCodeBlobUrl() text-replaced from "typecomposer"Redundant (import map handles this), fragile (missed import(), subpaths, single quotes)
🟢 LOWDemo /package.json still had @codesandbox/sandpack-client + typecomposer@0.0.98Misleading sandpack migration artefact

Changes (2 commits)

Commit 1 — fix(playground): correct esbuild-wasm WASM URL — use Vite ?url import

src/utils/browserCompiler.ts

// Before (broken — 404 in dev and production):awaitesbuild.initialize({wasmURL: '/node_modules/esbuild-wasm/esbuild.wasm'});// After (correct — Vite copies to dist/assets/ with content hash):importwasmUrlfrom'esbuild-wasm/esbuild.wasm?url';awaitesbuild.initialize({wasmURL: wasmUrl});

src/vite-env.d.ts — added declare module '*.wasm?url' so TypeScript accepts the import.

Commit 2 — fix(playground): CDN version sync, iframe sandbox, blob URL cleanup, remove brittle import rewrite

src/views/playground/PlaygroundView.ts

  • "0.1.53" → module-level TYPECOMPOSER_VERSION = "0.1.56" constant with sync comment
  • this.iframe.setAttribute("sandbox", "allow-scripts") — iframe isolation (allow-same-origin intentionally omitted)
  • Removed createCodeBlobUrl() — blob URLs now tracked in pendingBlobUrls[], revoked at the start of each compile run and in new disconnectedCallback()
  • Removed fragile text-replace of from "typecomposer" — import map is the correct mechanism
  • Updated demo /package.json to typecomposer: "^0.1.56", removed sandpack artefacts

Build result

✓ 3486 modules transformed.
dist/assets/esbuild-BHljloGq.wasm 12,332.68 kB ← WASM correctly emitted to dist/assets/
dist/assets/index-C2_sis7Q.css 48.96 kB
dist/assets/index-CAvLbuBC.js 1,754.60 kB
✓ built in 14.86s

The esbuild.wasm asset is now present in dist/assets/ — confirming the ?url import works correctly and the playground will be able to compile code at runtime.


Does this fix the "branch was merged before" problem?

Yes. This branch (fix/browser-playground-esbuild) is a fresh branch from TypeComposer/docs:main at dedd68a. It has no shared history with the old Copilot branch — GitHub will treat it as a clean, unmerged branch with a clear diff.


Files changed

FileChange
src/utils/browserCompiler.ts?url WASM import, minor cleanup
src/vite-env.d.tsAdd *.wasm?url type declaration
src/views/playground/PlaygroundView.tsVersion constant, sandbox attr, blob URL tracking, remove brittle rewrite, fix demo package.json

cc @zico15@joaodibba

The previous code used a filesystem path '/node_modules/esbuild-wasm/esbuild.wasm'
as the wasmURL. Vite does NOT serve node_modules as static files via fetch —
the path 404s in dev and node_modules/ doesn't exist in production at all.
The playground could never compile anything as shipped.
Fix: replace with a Vite `?url` import so the WASM binary is copied to
dist/assets/ with a content-hash filename and served correctly in both
dev and prod.
Also add `declare module '*.wasm?url'` to src/vite-env.d.ts to satisfy
TypeScript for the new import.
FixesTypeComposer#14 post-merge regression.
…remove brittle import rewrite
Five runtime quality fixes for PlaygroundView:
1. TYPECOMPOSER_VERSION constant (0.1.53 → 0.1.56)
- Hardcoded '0.1.53' silently mismatched the package.json ^0.1.56 dep.
- Renamed to module-level TYPECOMPOSER_VERSION constant with a sync comment.
2. sandbox="allow-scripts" on preview iframe
- User-compiled code previously had full access to window.parent and could
mutate the docs page DOM. sandbox="allow-scripts" isolates the iframe.
- allow-same-origin intentionally omitted — blob: URLs are opaque-origin,
so omitting it makes the sandbox stricter, not weaker.
3. Blob URL memory leak / revocation race removed
- createCodeBlobUrl() used setTimeout(revoke, 5000) — a race condition
(module may not have finished loading) that also leaked URLs between runs.
- Fix: track blob URLs in pendingBlobUrls[], revoke at the start of the
next compile run and in disconnectedCallback(). disconnectedCallback()
also cancels the debounce timer.
4. Brittle import text-replace removed
- createCodeBlobUrl() did a regex replace of `from "typecomposer"` with the
CDN URL. This was redundant (import map handles it), fragile (missed
dynamic import(), subpath imports, single-quote variants), and now gone.
- The import map in the iframe HTML is the correct, complete mechanism.
5. Demo /package.json updated
- In-editor demo config still referenced @codesandbox/sandpack-client and
typecomposer@0.0.98 — sandpack migration artifacts left from before PR TypeComposer#15.
- Updated to reference only typecomposer at the current TYPECOMPOSER_VERSION.
Sign up for freeto 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.

2 participants

@lucas-spin@joaodibba