Skip to content

fix: bookmark reorder reverting, drag lag, and glass theme artifacts - #450

Merged
sajjadmrx merged 3 commits into
feat/free-widget-canvasfrom
fix/bookmark-reorder-revert-and-lag
Sep 13, 2026
Merged

sajjadmrx merged 3 commits into
feat/free-widget-canvasfrom
fix/bookmark-reorder-revert-and-lag

Conversation

@ThePrimeShak

Copy link
Copy Markdown
Collaborator

Bookmark reorder reverted on drop

handleDragEnd looked the dragged bookmark up in a different list than the one rendered.

// render
getCurrentFolderItems(currentFolderId, instanceId, isPrimary)
// drag end
getCurrentFolderItems(currentFolderId, instanceId)

Without isPrimary the context falls back to !widgetId || widgetId === 'bookmarks-default', so a widget with a server-assigned instance id sees none of the legacy bookmarks. sourceIndex came back -1 and the handler returned silently.

Drag lagged the cursor

sortable-bookmark-item carried transition-transform duration-200 alongside the inline transition from useSortable. 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-glass only adds backdrop-filter there. Canvas widgets are positioned with translate3d, and a backdrop-filter inside that composited layer cannot reach the wallpaper painted outside it, so Chromium sampled the wrong surface. Turned off for .bg-glass inside .widget-outer; modals and popovers portal out and keep their blur.

Also

A behaviour-preserving memo pass on the same path: memo on the two bookmark tiles, the folder tile's full-array lookup memoised, stable SortableContext items and sensor options. background.js is byte identical; the newtab chunk moves 121 bytes.

Left out

Empty grid slots are still not drop targets, and four dead group-hover classes are untouched pending a decision.

Testing

npm run compile, npm test (77 pass), npx biome check src, npm run build all green. Reorder, drag feel, folder reorder and the glass artifact verified in the browser.

…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.
@sajjadmrx
sajjadmrx merged commit 3f5d58c into feat/free-widget-canvas Sep 13, 2026
2 checks passed
@sajjadmrx
sajjadmrx deleted the fix/bookmark-reorder-revert-and-lag branch September 13, 2026 14:04
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