Skip to content

Fix(diagrams): D2 diagrams draw on iPhone and in Safari - #959

Merged
adibhanna merged 2 commits into
mainfrom
fix/d2-webkit-main-thread
Oct 19, 2026
Merged

adibhanna merged 2 commits into
mainfrom
fix/d2-webkit-main-thread

Conversation

@adibhanna

Copy link
Copy Markdown
Contributor

D2 diagrams failed in WebKit (the iPhone app, Safari on the self-hosted web app): the Go runtime overflows the worker's stack while it starts, so every d2 fence showed "undefined is not an object (evaluating 'd2.compile')".

  • createD2 probes the package's worker with version(). Chromium answers and keeps the worker, unchanged.
  • When the worker cannot answer, the package's own worker script runs on the page through an in-page port. No blob URL is fetched or imported, so the server's CSP allows it. Go's process and path stubs are removed from the page once it has started.
  • Core packages move to 2.71.1 for iPhone 1.27.0 (core-only fix; the desktop stays at 2.71.0).

Verified in a WKWebView under the server's exact CSP (the unfixed path fails on the same page) and in Chromium (worker path, nothing on the page). New tests pin the script rewrite against the installed @terrastruct/d2.

@terrastruct/d2 starts its Go compiler in a module worker. In WebKit (the
iPhone app's WKWebView, Safari on the self-hosted web app) the Go runtime
overflows the worker's smaller stack while it starts: the WebAssembly loads,
the worker still reports ready, and every d2 fence then failed with
"undefined is not an object (evaluating 'd2.compile')". The same runtime
starts fine on the page's own thread.

createD2 now asks the package's worker for its version first. Chromium (the
desktop app, Android, Chrome on the web app) answers and keeps the worker, so
nothing changes there. When the worker cannot answer, the worker script the
package builds runs on the page instead: its Blob is read as text while the
D2 is constructed, its two module exports are dropped, and its message
handler is given an in-page port. Nothing fetches or imports a blob URL, so
the self-hosted server's Content-Security-Policy (script-src 'self'
'unsafe-eval') allows it as it is. The Go runtime's process and path stubs
leave the page once it has started, so no library mistakes the page for
Node; fs stays, since the runtime writes its output through it.

It deliberately does not patch the package or move the compiler for
everyone: a compile on the page holds the UI while it runs, which only
WebKit has to accept. A package update whose worker script no longer ends
the way this expects fails loudly instead of running something else, and a
test pins that against the installed version.

Checked in a WKWebView under the server's exact policy (two diagrams
compiled and rendered; the unfixed path fails on the same page) and in
Chromium, where the worker still does the work and nothing lands on the
page.
3d6feed lets D2 diagrams draw in WebKit, so the shared packages move to
2.71.1 and the prepared iPhone 1.27.0 adopts that instead of 2.71.0. The
self-hosted web client gets it with the next server pin. Android 1.1.41
ships on 2.71.0, where D2 already draws, and the desktop app stays at
2.71.0 until its next release.
@adibhanna
adibhanna merged commit ae28816 into main Oct 19, 2026
10 checks passed
@adibhanna
adibhanna deleted the fix/d2-webkit-main-thread branch October 19, 2026 01:26
@adibhanna
adibhanna deployed to boundary-artifacts October 19, 2026 01:30 — with GitHub Actions Active
@adibhanna
adibhanna deployed to boundary-artifacts October 19, 2026 01:33 — with GitHub Actions Active
adibhanna added a commit to ZenNotes/zennotesios that referenced this pull request Oct 19, 2026
Core core-2.71.1-core.h77db37926ce01390, built by CI from ZenNotes/zennotes
ae288166 (main, ZenNotes/zennotes#959); core:adopt checked SHA-256,
SHA-512, packed versions and clean-source provenance.

On 2.71.0 every d2 fence here showed "undefined is not an object
(evaluating 'd2.compile')": WebKit gives a worker a smaller stack than the
Go runtime inside the D2 compiler needs while it starts, so the worker
reported ready with no compiler. Core 2.71.1 asks the worker for its
version first and, when it cannot answer, runs the same compiler on the
page, where it starts fine. Chromium keeps the worker, so the desktop and
Android do not change.

Checked in a WKWebView on this build: the demo tour's D2 architecture
diagram and a Mermaid flowchart both draw in the same note, with no
diagram error. 265 tests, typecheck, boundary check and Capacitor sync
pass.

This branch was successfully deployed

1 active deployment
boundary-artifacts — ae288166 Deployed Oct 19, 2026 by adibhanna via draft #60
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