Skip to content

cards demo: fall back to a flat card when WebGL is unavailable - #1075

Merged
patcapulong merged 5 commits into
mainfrom
pat/cards-demo-webgl-fallback
Sep 25, 2026
Merged

patcapulong merged 5 commits into
mainfrom
pat/cards-demo-webgl-fallback

Conversation

@patcapulong

Copy link
Copy Markdown
Contributor

Why

A visitor whose browser can't create a WebGL context got Next's "Application error: a client-side exception has occurred" in place of the whole cards playground at docs.lightspark.com/cards/demo. The console showed THREE.WebGLRenderer: A WebGL context could not be created ... GL_VENDOR = Disabled. This happens when hardware acceleration is off, the GPU is blocklisted, or a managed machine disables WebGL, and Chrome no longer falls back to a software renderer. The 3D card threw during render and nothing caught it, so React unmounted the page.

What changed

  • CardStageBoundary wraps the 3D stage on the playground (AppPanel) and the share page (ShareCard). When the 3D card throws, only the card is replaced; the configure panel, phone, and API panel stay up.
  • FlatCard shows the card's faces painted by the same paintFront and paintBack the 3D mesh uses, as two canvases turned in CSS 3D. Upright cards are turned back from the texture's quarter turn.
  • FlatCardStage runs the flat card on the same CardMotion (drag, fling, tilt, idle bob, decline shake), the same intro blueprint, the flight into the phone's card slot, and the flight into the share frame.
  • FlatExporter implements a new CardFrameSource interface (the part of CardExporter the share code uses). It projects the faces through the 3D exporter's camera with a mesh of affine triangle draws on a 2D canvas, so share stills, all four poses, the hand treatment, and the spin video render without WebGL.
  • StageGL draws the same dot lattice (dotLayout) on a 2D canvas when it can't get a WebGL context. There is no ripple or phone-glass refraction in that case.
  • The rest placement, phone-slot flight, screen clip, and share-frame flight moved from CardRig into stagePlacement.ts so both stages use one copy. The math is unchanged.

Risk to the WebGL experience

The 3D path runs the same code as before plus an idle error boundary. There is no WebGL probe on load; the boundary alone detects the failure. The CardRig frame loop was compared line by line with the old inline code. First Load JS for / goes from 568 kB to 573 kB and for /c/[slug] from 377 kB to 380 kB.

One behavior change: any error thrown inside the 3D stage now swaps in the flat card instead of showing the error page. The error is still logged with console.error.

Duplicate code between FlatCardStage, CardStage, and ShareCard (the per-frame placement, the intro clock, a few effects) is left for a follow-up so this PR doesn't rework the 3D frame loop further.

Test plan

  • Chrome with --disable-gpu --disable-software-rasterizer, against the dev server and embedded in local Mintlify: the playground loads with the flat card, the intro plays, the card drags and flings, presets and uploaded art repaint it, portrait works, and Issue card, Reveal, and Lock fly it into the phone.
  • The same Chrome: the share sheet opens with the card in its frame; square, post, and hand stills render; Angled, Turned, and Back poses render with card art; the spin video encodes (5 s, about 6 s to render).
  • The same Chrome: /c/acme-bank shows the flat card with the dot grid, intro, and drag.
  • Chrome with the GPU on: the 3D intro, drag, flows into the phone, share sheet, 3D stills, and the share page behave as before.
  • Chrome with the GPU on, with getContext returning null for the renderer's context only: the boundary swaps in the flat card and the page stays up.
  • tsc --noEmit reports no new errors, and next build passes.
  • Not yet tested: Safari and iOS Safari, with or without WebGL.

Visitors whose browser can't create a WebGL context (hardware acceleration
off, a blocklisted GPU, a managed machine) got Next's "Application error"
page in place of the whole playground: the 3D card threw and nothing caught
it.

An error boundary now wraps the 3D stage on the playground and the share
page. When the card fails, a flat card takes its place: the same painted
faces in CSS 3D, on the same motion (drag, fling, tilt, bob), intro, phone
and share-frame flights. Share works through a 2D exporter that projects the
faces through the 3D exporter's camera, so stills and the spin video render
without WebGL. The dot grid draws its lattice on a 2D canvas when WebGL is
missing.

For WebGL visitors the 3D path is unchanged: the placement and share-flight
math moved into stagePlacement.ts with identical behavior, and the boundary
does nothing unless the stage throws.

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
grid-cards-demo Ready Ready Preview Sep 25, 2026 6:09am UTC
2 Skipped Deployments
Project Deployment Actions Updated
grid-flow-builder Ignored Ignored Preview Sep 25, 2026 6:09am UTC
grid-wallet-demo Ignored Ignored Preview Sep 25, 2026 6:09am UTC

Request Review

@patcapulong

Copy link
Copy Markdown
Contributor Author

@greptileai

@patcapulong

Copy link
Copy Markdown
Contributor Author

@faraday review

@greptile-apps

greptile-apps Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium risk] Adds a flat card fallback when WebGL is unavailable.

The PR appears safe to merge; no new actionable issue was found, and the previous findings are resolved.

Summary

The PR keeps the cards playground and share page available when WebGL fails by replacing the 3D card with a flat, interactive card and providing a canvas-based export path.

  • Shared placement logic keeps the flat and 3D stages aligned through phone and share-frame flights.
  • The dot-grid fallback now draws without WebGL; its failed-shader background-image path defers PNG encoding until resizing settles.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Page[Playground or share page] --> Boundary[3D stage boundary]
  Boundary -->|3D stage works| WebGL[WebGL card]
  Boundary -->|3D stage throws| Flat[Flat card]
  WebGL --> Share[Share stills and video]
  Flat --> FlatExport[Canvas-based exporter]
  FlatExport --> Share
Loading

Reviews (6) · Last reviewed commit: "cards demo: encode the dot grid backgrou..."

Comment thread components/grid-cards-demo/src/components/CardStage/export/flatExporter.ts Outdated
Comment thread components/grid-cards-demo/src/components/glass-gl/StageGL.tsx Outdated
…inted

Co-authored-by: Cursor <cursoragent@cursor.com>
@patcapulong

Copy link
Copy Markdown
Contributor Author

@greptileai

@patcapulong

Copy link
Copy Markdown
Contributor Author

@faraday review

@ls-bolt

ls-bolt Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

⚡ Running fastpass on this PR first (https://zeus.dev.dev.sparkinfra.net/#/instance?id=encrypted-keeper). A full council convenes only if fastpass assesses the risk at critical or above. Post a new comment '@faraday full review' to convene one anyway.

@ls-bolt

ls-bolt Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

📌 Bolt Status

2026-09-25 05:45:11 UTC — ⚡ Agent encrypted-keeper(#1) started
2026-09-25 05:46:56 UTC — ⚡ Agent encrypted-keeper-2(#2) started
2026-09-25 06:04:01 UTC — ⚡ Agent encrypted-keeper-3(#3) started


Feedback

Co-authored-by: Cursor <cursoragent@cursor.com>
@patcapulong

Copy link
Copy Markdown
Contributor Author

@greptileai

@patcapulong

Copy link
Copy Markdown
Contributor Author

@faraday review

@ls-bolt

ls-bolt Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

⚡ Running fastpass on this PR first (https://zeus.dev.dev.sparkinfra.net/#/instance?id=encrypted-keeper-2). A full council convenes only if fastpass assesses the risk at critical or above. Post a new comment '@faraday full review' to convene one anyway.

@ls-bolt

ls-bolt Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

⚡ Fastpass is reviewing this PR now and posts its findings here shortly. Fastpass assessed this PR as moderate risk, below the critical bar for convening a full council, so no council was convened. Post a new comment '@faraday full review' to convene one anyway.

@lightspark-faraday lightspark-faraday Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠ Partial review: quote_verify did not run (quote_verify: all_unchecked); findings below were not quote-checked against the reviewed commit.

} else {
// No WebGL: the same lattice, still, with no ripple and no lens. A failed
// WebGL context leaves the canvas free for a 2D one.
const flatCtx = canvas.getContext('2d');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 components/grid-cards-demo/src/components/glass-gl/StageGL.tsx:810 - Use a fresh canvas for the 2D fallback when WebGL shader or program setup fails.

initGL obtains a WebGL context at line 448 but can return false on shader compilation or program linking at lines 456–464. The new fallback then asks that same canvas for a 2D context at line 810; a canvas already bound to WebGL cannot supply one, so paintFlat is never installed and the dot field remains blank.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 86b14b5, without a second canvas element. When canvas.getContext('2d') returns null because the canvas already holds the WebGL context whose shaders failed, paintFlat draws the lattice on the existing offscreen canvas (off) and sets it as the visible canvas's background-image. It's redrawn on resize and theme change, and cleared on unmount. The null-context path is unchanged. I checked it in Chrome with the WebGL1 COMPILE_STATUS forced false: the dot grid canvas gets the lattice as its background.

@lightspark-faraday

Copy link
Copy Markdown

Faraday review

fastpass advisory - comment-only, never blocks a merge. To retire a finding, either fix it or reply explaining why it is wrong: a rebuttal gets an agree or disagree answer on the thread, and if you reply again after a disagreement we concede. Acknowledging it ("will fix") or resolving the thread does not retire it on its own. This comment is edited in place every round; inline findings still post as new comments each round.

⚠ Partial review: quote_verify did not run (quote_verify: all_unchecked); findings below were not quote-checked against the reviewed commit.

Faraday score: 4/5 (P2/P3 findings) - 5 nothing blocking open | 4 non-blocking only | 3 one blocking open | 2 two | 1 a P0 or 3+ blocking; never drops without a new blocker

Still open:

  • P2 components/grid-cards-demo/src/components/glass-gl/StageGL.tsx:810 - Use a fresh canvas for the 2D fallback when WebGL shader or program setup fails. · github · graphite fp:6167b0be

5016fa7e77a4 | 1 inline + 0 in-body finding(s) (1 new) | 0 suggestion(s) | 0 refuted pre-post | route_deep: true | fail-open: 2 events (top: posting_filter_skipped)

fastpass re-review: 1 new finding(s).

Fastpass assessed this PR as moderate risk, below the bar for convening a full Faraday council, so none was convened. Mention @faraday on the PR to request one directly.

This summary supersedes the per-round summary blocks on the faraday reviews above; those are left in place as history and are not edited.

…fail

Co-authored-by: Cursor <cursoragent@cursor.com>
@patcapulong

Copy link
Copy Markdown
Contributor Author

@greptileai

@patcapulong

Copy link
Copy Markdown
Contributor Author

@faraday review

@ls-bolt

ls-bolt Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

⚡ Running fastpass on this PR first (https://zeus.dev.dev.sparkinfra.net/#/instance?id=encrypted-keeper-3). A full council convenes only if fastpass assesses the risk at critical or above. Post a new comment '@faraday full review' to convene one anyway.

Comment thread components/grid-cards-demo/src/components/glass-gl/StageGL.tsx
Co-authored-by: Cursor <cursoragent@cursor.com>
@patcapulong

Copy link
Copy Markdown
Contributor Author

@greptileai

@patcapulong

Copy link
Copy Markdown
Contributor Author

@faraday review

@ls-bolt

ls-bolt Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

⚡ Fastpass posted its review of 5016fa7 on this PR: #1075 (review). @faraday fastpass not launched - debounced - last fastpass run launched 4m50s ago (min interval 10m0s). Post a new comment '@faraday full review' to convene one anyway.

@patcapulong
patcapulong merged commit c9f98b2 into main Sep 25, 2026
7 checks passed
@patcapulong
patcapulong deleted the pat/cards-demo-webgl-fallback branch September 25, 2026 06:21

This branch was successfully deployed

1 active deployment
Preview – grid-cards-demo — 5248d293 Deployed Sep 25, 2026 by vercel[bot]
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.

2 participants