Skip to content

fix(web): resize the canvas on layout changes, not just window resize - #4

Merged
AkashJana18 merged 1 commit into
mainfrom
fix/canvas-resize
Sep 30, 2026
Merged

AkashJana18 merged 1 commit into
mainfrom
fix/canvas-resize

Conversation

@AkashJana18

Copy link
Copy Markdown
Owner

Problem

Opening the first lesson (One transaction, two protocols) and then exiting it left the network diagram clipped — the TowerBFT panel was cut down the right side, losing EU-CENTRAL, US-EAST and the right arc of the guide ring.

Cause

web/src/render/CanvasView.tsx passes resizeTo: el to Pixi, which reads like "track this element", but Pixi's ResizePlugin only subscribes to the window resize event:

// pixi.js/lib/app/ResizePlugin.mjs:19-22
globalThis.removeEventListener("resize", this.queueResize);
globalThis._resizeTo = dom;
if (dom) {
  globalThis.addEventListener("resize", this.queueResize);
  this.resize();
}

Opening/closing the lesson panel flips .main between 340px 1fr 400px and 1fr 400px (styles/global.css:53, :73). That resizes .canvas-host but never fires a window resize, so the canvas bitmap kept its old, 340px-narrower size. Our own ResizeObserver meanwhile called scene.resize(...), which recomputes node positions for the new, wider box (scene.ts:48 → layout.ts:33) — so the diagram was laid out wider than the visible canvas and clipped.

Fix

web/src/render/CanvasView.tsx:64 — resize the renderer from the same observer, after the scene, so the frame app.resize() paints already uses the new layout:

ro = new ResizeObserver(() => {
  scene?.resize(el.clientWidth, el.clientHeight);
  app?.resize();
});

Application.resize() is the documented manual trigger and reads the same clientWidth/clientHeight off the same element, so the layout and the bitmap can no longer drift.

This also covers Alpenglow ↔ Compare switches, where the surviving CanvasView keeps its React key and its host halves without a remount.

Why mode switches never looked broken

computeLayout clamps the ring to min(width/2 - 104, height/2 - 76), so a stale-but-larger canvas still contained the whole (smaller) ring. Clipping needs the host to grow by more than the 104px label margin — which only the 340px lesson panel does. That's why this showed up on lesson close and never on mode switches. The new test pins the size invariant for all three transitions so this can't silently regress.

Test

New web/e2e/canvas-resize.spec.ts asserts canvas.getBoundingClientRect().width === host.clientWidth for every .canvas-host after lesson open, lesson exit, and both mode switches. No sim stepping, so it runs in ~3s.

Confirmed it fails without the fix — the pre-existing canvas reports ok: false at the panel-open step, where the 340px column is stolen.

Verification

  • bun run build (strict tsc, zero errors) ✅
  • bun run test — 53/53 ✅
  • bun run e2e — 5/5, including the 4 pre-existing specs ✅

The two committed e2e screenshots were regenerated by the suite with 15 and 23 scattered pixels of particle noise (verified by pixel diff — no layout change, since the compare screenshot shrinks the ring to fit), so they are left untouched in this PR.

🤖 Generated with Claude Code

Closing a lesson clipped the network diagram: the ring was drawn for the
new, wider canvas area but the canvas bitmap kept its old narrow size, so
the right side (EU-CENTRAL, US-EAST, the guide ring) was cut off.

Cause: `resizeTo: el` looks like it observes the element, but Pixi's
ResizePlugin only subscribes to the window `resize` event. The lesson
panel mounting/unmounting flips `.main` between `340px 1fr 400px` and
`1fr 400px`, which resizes `.canvas-host` without a window resize. Our own
ResizeObserver recomputed the scene layout for the new width, so the wider
diagram got clipped to the stale canvas.

Fix: resize the renderer from the same observer, after the scene, so the
frame `app.resize()` paints already uses the new layout. Also covers
Alpenglow <-> Compare switches, where the surviving canvas halves without
a remount.

Only visible when the host grows by more than the 104px label margin in
computeLayout, which is why the mode switches looked fine: the ring
shrinks to fit the host, so a stale-but-larger canvas still contained it.

e2e/canvas-resize.spec.ts asserts canvas width == host clientWidth after
lesson open, lesson exit and both mode switches. Confirmed it fails
without the fix (pre-existing canvas clipped at the panel-open step).
bun run build (strict tsc) clean, bun run test 53/53, bun run e2e 5/5.
@vercel

vercel Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
solana-consensus-lab Ready Ready Preview Sep 30, 2026 11:45am UTC

@AkashJana18
AkashJana18 merged commit 2541d79 into main Sep 30, 2026
5 of 6 checks passed

This branch was successfully deployed

1 active deployment
Preview — 545eb8d5 Deployed Sep 30, 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.

1 participant