PR type
development
Release impact
changeset — .changeset/olive-pumas-shake.md
What happens today
Every example preview on https://vgpu.sh fails in Firefox with an overlay that contains a stack and nothing else — no message, no indication of what went wrong:
Preview error
r@https://vgpu.sh/_next/static/immutable/chunks/25i9s9l19xma4.js:1:6014
t@https://vgpu.sh/_next/static/immutable/chunks/25i9s9l19xma4.js:1:15326
@https://vgpu.sh/_next/static/immutable/chunks/25i9s9l19xma4.js:1:34847
d@https://vgpu.sh/_next/static/immutable/chunks/25i9s9l19xma4.js:1:39474
...To reproduce, open any /preview/<slug> in a Firefox profile without dom.webgpu.enabled (the default on Linux today).
Two separate causes stack up.
1. The overlay drops the message
apps/docs/app/preview/[slug]/example-canvas.tsx formatted the error as:
return error instanceof Error ? error.stack || error.message : String(error);V8 puts name: message on the first line of error.stack, so this reads correctly in Chrome. SpiderMonkey and JavaScriptCore emit call frames only, so error.stack never contains the message and the || error.message fallback never fires. Measured in Firefox 151, for a thrown Error:
{
"message": "navigator.gpu.requestAdapter() returned null.",
"stackFirstLine": "VGPUError@https://vgpu.sh/preview/simple-gradient:2:7",
"stackIncludesMessage": false
}This one function feeds both the in-iframe overlay and the postMessage payload that components/example-preview.tsx renders on the docs page, so both lost the message.
2. The underlying error blamed the wrong thing
requestBrowserDevice optional-chained the request:
const adapter = await nav.gpu?.requestAdapter({ powerPreference: opts.powerPreference });
if (!adapter) throw unsupportedError("init", "navigator.gpu.requestAdapter() returned null.");When navigator.gpu is absent the optional chain yields undefined, so a browser that exposes no WebGPU at all was reported as an adapter that came back null — sending people to look for a driver or hardware problem instead of a missing API.
The change
- Extract the formatting into
apps/docs/lib/preview-error-message.tsso it is unit-testable, and composename: messageahead of the stack unless the engine already supplied it. V8 output is byte-identical; Firefox and Safari gain the headline. - Guard
navigator.gpuinrequestBrowserDeviceand throwWebGPU is not available in this browser.— the same wordingapps/docs/lib/browser-device.tsand the three.js examples already use, and the stringdocs/topics/agent-browser-webgpu.docs.mdalready tells readers to grep overlays for. A genuinely null adapter keeps its existing message.
With both applied, the same Firefox profile shows VGPUError: WebGPU is not available in this browser. above the frames instead of frames alone.
Verification
pnpm typecheck— passes.pnpm test:fast— 1208 passed, 76 skipped, 0 failed.pnpm migrations:check— passes.pnpm docs:verify-snippets—snippets=623 tsc=OK.pnpm check:filenames— passes.- New tests: 6 in
apps/docs/lib/preview-error-message.test.ts, 2 inpackages/vgpu-api/tests/init-device-options.test.ts. Reverting either fix turns 5 of them red, and the V8-path and null-adapter tests stay green both ways. - Firefox 151 over WebDriver BiDi, used both to capture the failure above and to confirm the new formatter puts the message on line one.
Bundle budget
vgpu experience full-root is a hard client gate and is already close to it, so measuring the delta seemed worth reporting rather than leaving to CI:
| gzip | budget | headroom | |
|---|---|---|---|
| canary | 41846 B | 41984 B | 138 B |
| this PR | 41871 B | 41984 B | 113 B |
+25 B, from the one added guard and its message. Happy to fold the message into the existing requestAdapter throw if you would rather not spend the bytes, though that costs the distinction between the two failures.
The four tooling budgets that pnpm bundle-check warns about (vgpu/node, vgpu/mock, both wgsl loaders) are over on canary already and unchanged here.
Notes
- Targets
canaryper CONTRIBUTING: the change is not web-only, since it touchespackages/vgpu-apias well asapps/docs. - The two halves are independent — if you would prefer the docs overlay fix and the
init()message as separate PRs, say the word and I will split them. - This is a different Firefox problem from #385, which is a naga validation failure on workgroup pointers in the FFT ocean demo.
🤖 Generated with Claude Code
Activity
3Heads up on the red release-impact check — it is failing before it reads anything in this PR, so it is not a declaration problem.
The job's checkout step stops with:
Refusing to check out fork pull request code from a 'pull_request_target' workflow. [...] To opt in, review the risks at https://gh.io/securely-using-pull_request_target and set 'allow-unsafe-pr-checkout: true' on the actions/checkout step.
That is actions/checkout's guard against pwn-request patterns, and it fires on any fork-authored PR, so the gate cannot currently go green on an external contribution regardless of what the description says. Flagging it because CONTRIBUTING describes release-impact as a required check on canary.
Worth noting the workflow already treats the candidate as data rather than executing it — the step is literally named "Checkout candidate as data only" — so the safe fix is likely fetching the candidate blobs without a working-tree checkout (or actions/checkout with persist-credentials: false plus the explicit opt-in, given nothing from the fork is executed). Happy to leave that to you; I did not want the red check read as a missing declaration on my side.
For the record, the description does carry both required sections: ## PR type → development, and ## Release impact → changeset — .changeset/olive-pumas-shake.md.
The Vercel check is the usual "Authorization required to deploy" that every fork PR gets.
@ParthKapoor-dev is attempting to deploy a commit to the Vercel Labs Team on Vercel.
A member of the Team first needs to authorize it.