Repository navigation
fix: bookmark reorder reverting, drag lag, and glass theme artifacts - #450
Merged
sajjadmrx merged 3 commits intoSep 13, 2026
Merged
Conversation
…rsor
Dragging a bookmark reverted on drop, and the dragged item trailed the
pointer before lunging after it. Two separate causes.
handleDragEnd looked the dragged bookmark up in a different list than
the one on screen. Render:
getCurrentFolderItems(currentFolderId, instanceId, isPrimary)
Drag end:
getCurrentFolderItems(currentFolderId, instanceId)
That third argument decides which root bookmarks the widget owns. Omitted,
the context falls back to `!widgetId || widgetId === 'bookmarks-default'`,
so a widget whose instanceId is anything else gets only bookmarks stamped
with that exact id. Reordering requires auth, an authenticated user's
layout comes from the server with server-assigned instance ids, and
existing bookmarks carry widgetId null or 'bookmarks-default'. So the list
on screen had bookmarks and the drag-end list was empty, sourceIndex came
back -1, and the handler returned with no state change and no toast.
bookmark.test.ts already documents the contract: pass true and a widget
with a server id gets its legacy bookmarks, omit it and it gets none.
The lag was a class overriding dnd-kit's own guard. sortable-bookmark-item
applied both the transition from useSortable and a Tailwind class:
className={`w-full h-full transition-transform duration-200 ...`}
dnd-kit strips the transition from the drag source so it tracks the pointer
exactly: with no DragOverlay, shouldDisplaceDragSource is true and
getTransition() returns undefined. React then omits the inline property and
the class is left standing, so every pointer move set a transform the
browser eased over 200ms. Non-dragged items get the same 200ms shuffle
inline from that function, so removing the class costs nothing.
Alongside, a behaviour-preserving pass on the same path. During a pointer
move BookmarkGrid does not re-render, so the props reaching each item keep
their identity for the whole drag, which makes memo effective without
touching a single callback:
- memo on BookmarkItem and FolderBookmarkItem, so only the dragged item
re-renders instead of every tile rebuilding its favicon url, title hash,
initials and sticker style array
- the folder tile's getCurrentFolderItems call, a find plus filter plus
copying sort over the whole bookmark array per folder per render, is now
memoised on [bookmarks, bookmark.id]
- stable items array for SortableContext and a hoisted pointer sensor
options object, both of which were new identities every render
Measured: background.js is byte identical, and the newtab chunk moves by
121 bytes for the memo wrappers and dependency arrays. The lazy
habit-share-modal chunk keeps its exact size; only its hash moves, because
it imports the newtab chunk by content-hashed filename.
In the glass theme, moving the mouse across the page intermittently drew
sharp, un-blurred page content over the widgets: wallpaper text crossing a
bookmark tile, or a dark slab landing on a neighbouring widget. It never
happened in dark or light.
That last detail is the whole diagnosis. .bg-glass only adds a filter in
two themes:
[data-theme="glass"] .widget-wrapper,
[data-theme="glass"] .bg-glass,
[data-theme="glass"] .tooltip {
backdrop-filter: blur(16px) saturate(150%);
}
dark and light ship no such rule, which is why they were clean.
Every widget on the canvas is positioned by canvas-widget-outer with
transform: baseTransform // translate3d(...), always present
A transformed ancestor gets its own compositing layer, and a
backdrop-filter inside that layer cannot reach the wallpaper, which is
painted on the body outside it. Given nothing valid to sample, Chromium
falls back to the wrong surface and composites raw page content on top.
The bookmark widget makes it easy to hit: ten tiles, each its own
backdrop-filter layer, each running three animations on hover.
So backdrop-filter cannot work inside the canvas, and the fix is to stop
asking it to:
[data-theme="glass"] .widget-outer .bg-glass {
backdrop-filter: none;
}
Scoped to widgets only. Modals, popovers and the search portals render
through createPortal, so they are not descendants of .widget-outer and keep
their blur, as do the navbar and tooltips.
The tiles stay at #171717b3, so they are now flat translucent rather than
blurred inside widgets. Bump that value if they read as too see-through.
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.
Bookmark reorder reverted on drop
handleDragEndlooked the dragged bookmark up in a different list than the one rendered.Without
isPrimarythe context falls back to!widgetId || widgetId === 'bookmarks-default', so a widget with a server-assigned instance id sees none of the legacy bookmarks.sourceIndexcame back-1and the handler returned silently.Drag lagged the cursor
sortable-bookmark-itemcarriedtransition-transform duration-200alongside the inline transition fromuseSortable. dnd-kit strips that transition from the drag source on purpose; React omits the inline value, so the class was left standing and every pointer move got eased over 200ms. Non-dragged items get the same 200ms shuffle inline, so removing the class changes nothing for them.Glass theme drew raw page content over widgets
Only in glass, because
.bg-glassonly addsbackdrop-filterthere. Canvas widgets are positioned withtranslate3d, and abackdrop-filterinside that composited layer cannot reach the wallpaper painted outside it, so Chromium sampled the wrong surface. Turned off for.bg-glassinside.widget-outer; modals and popovers portal out and keep their blur.Also
A behaviour-preserving memo pass on the same path:
memoon the two bookmark tiles, the folder tile's full-array lookup memoised, stableSortableContextitems and sensor options.background.jsis byte identical; the newtab chunk moves 121 bytes.Left out
Empty grid slots are still not drop targets, and four dead
group-hoverclasses are untouched pending a decision.Testing
npm run compile,npm test(77 pass),npx biome check src,npm run buildall green. Reorder, drag feel, folder reorder and the glass artifact verified in the browser.