Conversation
Users on weaker machines reported the canvas lagging while editing the widget layout. The lag starts the moment edit mode opens, before anything is dragged. The drag path itself is not the problem. Previews are throttled through requestAnimationFrame and only run the layout engine when the target grid cell actually changes, reconcileIdentity keeps the object identity of untouched widgets so the memo on CanvasWidgetOuter holds, and a preview never touches storage or the server - only the drop commits. The cost is rendering. In edit mode every unselected widget gets an infinite rotation: animation: widget-wiggle 0.46s ease-in-out infinite alternate; and every widget contains a WidgetContainer carrying bg-glass, which on the glass and icy themes resolves to backdrop-filter: blur(16px) saturate(150%). Rotating an element that contains a backdrop-filter forces the browser to re-sample and re-blur the backdrop every frame, once per widget. With ten widgets that is ten blur passes per frame. Nothing set will-change either, so the rounded corners, borders and text were re-rasterised each frame on the main thread as well. Three changes: Live blur is dropped while the canvas is in edit mode. The canvas root now carries a canvas-editing class and #widgets-canvas.canvas-editing .bg-glass clears backdrop-filter. Specificity is (1,2,0) against the theme rule's (0,2,0), so it wins regardless of file order, and background-color is left alone - the widgets keep their translucent look, they just stop recomputing a blur sixty times a second while being rearranged. The wiggle classes get will-change: transform so the rotation is composited instead of re-rasterised. The wiggle is switched off under html.optimal-mode and prefers-reduced-motion. optimal-mode only zeroed transition-duration, so the single most expensive thing on the page kept running for the users most likely to have turned it on. This is scoped to the three wiggle classes, leaving the deliberate decision to keep keyframe animations alive for spinners and the notification ping intact. Widgets still run live in edit mode - clocks tick, pets move. Pausing them is likely the next win but touches each widget, so it is left out here.
The dashed grid squares shown while editing the layout were hard to make out over a light wallpaper. border-base-content/15 was the cause: on the glass and icy themes --color-base-content is close to white, so a 15% white border all but disappeared against a bright background. Raised to 26%, which reads clearly without competing with the widgets being arranged.
Widgets showed a shadow with sharp points along their bottom edge while the layout was being rearranged, on some widgets but not others. Two wrappers clipped a rounded, shadowed surface with a square cornered box. WidgetContainer: widget-outer relative h-full w-full overflow-hidden wraps a body carrying bg-content bg-glass rounded-widget, and on the icy theme .bg-glass resolves to box-shadow: 0 6px 24px #0000000f, inset 0 1px #ffffff1f The body's downward shadow was therefore clipped by a square box the same size as the body, so in the bottom corners - where the rounded body curves away and leaves the square corner empty - the shadow filled that corner and came to a point. bookmark-folder.tsx had the same square clip around a tile carrying rounded-widget shadow-xs. That one explains why only part of the bookmarks widget was affected: only folders render through this component, while plain bookmarks and empty slots have no such wrapper. In an RTL grid the folders sit on the right, which is exactly where the artifact showed. Both wrappers now carry rounded-widget so the clip follows the shape it is clipping. The bookmarks widget needed its own fix because it does not render through WidgetContainer at all. Other overflow-hidden wrappers in the widget tree clip inner content rather than a rounded shadowed surface at the widget edge, so they do not produce this and are left alone.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Users on weaker machines reported the canvas lagging while editing the widget layout. The lag starts the moment edit mode opens, before anything is dragged.
What is not the problem
The drag path is already well optimised, and worth stating so it does not get "fixed" later:
requestAnimationFrameand only run the layout engine when the target grid cell actually changesreconcileIdentitypreserves the object identity of untouched widgets, so thememoonCanvasWidgetOutergenuinely holdsThe cost is rendering.
Root cause
In edit mode every unselected widget gets an infinite rotation:
and every widget contains a
WidgetContainercarryingbg-glass, which on the glass and icy themes resolves tobackdrop-filter: blur(16px) saturate(150%).Rotating an element that contains a
backdrop-filterforces the browser to re-sample and re-blur the backdrop every frame, once per widget. Ten widgets means ten blur passes per frame at 60fps. Nothing setwill-changeeither, so rounded corners, borders and text were re-rasterised on the main thread each frame on top of that.Changes
canvas-editingclass;#widgets-canvas.canvas-editing .bg-glassclearsbackdrop-filter. Specificity is(1,2,0)against the theme rule's(0,2,0), so it wins regardless of file order, andbackground-coloris untouched — widgets keep their translucent look, they just stop recomputing a blur 60×/sec while being rearranged.will-change: transformon the wiggle classes so the rotation is composited rather than re-rasterised.html.optimal-modeandprefers-reduced-motion.optimal-modeonly zeroedtransition-duration, so the single most expensive thing on the page kept running for exactly the users most likely to have enabled it. Scoped to the three wiggle classes, leaving the deliberate choice to keep keyframe animations alive for spinners and the notification ping intact.Also raises the edit-mode grid border from
border-base-content/15to/26— on glass/icy--color-base-contentis near-white, so 15% white all but vanished over a light wallpaper.Deliberately left out
Widgets still run live in edit mode — clocks tick, pets move. Pausing them is likely the next win, but it touches each widget individually, so it belongs in its own change.
Testing
npm run compilenpm testnpx biome check srcnpm run buildnewtab-*.css(this repo has a history of classes compiling to nothing)