Skip to content

refactor(highlight-updates): draw outlines on a canvas, not clones - #40

Merged
lxsmnsyc merged 3 commits into
mainfrom
refactor/highlight-outlines
Oct 29, 2026
Merged

lxsmnsyc merged 3 commits into
mainfrom
refactor/highlight-outlines

Conversation

@lxsmnsyc

Copy link
Copy Markdown
Member

Draw the update highlight on one canvas instead of cloning the updated elements into the page, and stop the Ownership and Reactivity panels from rebuilding every row on each refresh.

Changes

  • highlight-updates: the ghost clones are gone. Each update is outlined on one <canvas> in a shadow root on body. A changed text node gets one box per line it covers, from Range.getClientRects(). Any other change gets the element's box.
  • The canvas adds no elements to the page and page styles cannot reach it. It is sized for the device pixel ratio and draws only while boxes are showing.
  • Boxes follow window scrolls. One frame reports at most 200 updates. The fade is off when the user prefers reduced motion.
  • canvas.ts takes the 2D context as a parameter, so it can move to an OffscreenCanvas worker later.
  • Ownership rows, Reactivity nodes and Reactivity edges are keyed by id (row.node.id, node.id, from>to). Before, each refresh made new objects and the lists were keyed by object, so every refresh rebuilt every row.
  • The demo has a "Frames" card that updates every animation frame (createSignal + requestAnimationFrame), with a pause button, to watch the highlight while scrolling or resizing.

Proof

Measured in Chrome on the demo with the Frames ticker running and the page width changing every frame:

Panel open main This branch
Ownership 50 to 66ms median frame 16.7ms (p95 17.5ms)
Reactivity 67ms median, 83ms p95 16.7ms (p95 17.1ms)

After the runs, the same row and node elements are still on the page, and the ticker-frame node's value updated in place.

Drawing 200 boxes, the cap for one frame:

Rendering Main-thread time
200 divs (create, append, layout) 3.7ms median, 6.8ms max
One canvas redraw of 200 boxes 0.7ms median, 2.2ms max

Checks

  • Local: build pass, 143 unit tests pass, typecheck pass, format pass, publint and attw pass, 14 e2e tests pass.
  • The highlight e2e test reads the canvas pixels: the middle of the changed text is blue after the first update and purple after the second, and the button padding beside the text stays transparent.

Roadmap

  • none

Cost

  • clone.ts (296 lines) and overlay.css are removed. Net diff without the demo: +441 and -767 lines.
  • No new dependencies.

Risk

  • Boxes are measured when the update happens. If the layout moves before the 400ms fade ends, for example during a resize, a box can sit on a stale position until that node updates again.
  • Scrolls of inner scroll containers are not followed, only window scrolls.
  • The keyed lists now update rows in place. Expanding, selecting and the "fresh" marker are covered by the existing e2e tests.

Not done

🤖 Generated with Claude Code

lxsmnsyc and others added 3 commits October 29, 2026 15:32
The ghost clone copied the updated element into the document, so the page
could react to the copy, and each update cloned the whole subtree. Draw
boxes on one canvas in a shadow root instead: one per line of a changed
text node, from Range.getClientRects(), and the element box for other
changes. Nothing is added to the page, and page styles cannot reach it.

The boxes follow window scrolls. One frame reports at most 200 updates,
and the fade is off when the user prefers reduced motion. The draw
function takes its context as a parameter, so it can move to an
OffscreenCanvas worker later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A counter and a moving dot driven by a signal and requestAnimationFrame,
with a pause button, to watch the update highlight while scrolling or
resizing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each refresh of the Ownership and Reactivity panels makes new row, node
and edge objects, and the lists were keyed by object, so every refresh
rebuilt all of them. An app that writes a signal every frame kept the
page at 50 to 67ms frames while one of these panels was open. Key the
lists by owner id, node id and edge endpoints instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@lxsmnsyc
lxsmnsyc merged commit d66c2ed into main Oct 29, 2026
3 checks passed
@lxsmnsyc
lxsmnsyc deleted the refactor/highlight-outlines branch October 29, 2026 07:45
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