feat(desktop): refine sidebar session drag reordering#43661
Conversation
Dragging a sidebar session row between the PINNED and SESSIONS sections did nothing: each section owns a private dnd-kit DndContext (scoped to in-section reorder from the grab handle), and the row body's native drag only targets the composer. Pinning required the context menu or shift-click. The native row drag already carries application/x-hermes-session, so the two sections become native drop zones for it: drop a row on Pinned to pin, drop a pinned row on Sessions to unpin — matching Claude Desktop's drag-to-pin gesture. A marker MIME (application/x-hermes-session-pinned) rides the drag because dragover can only inspect types, letting each zone engage only for drags it would act on; the payload now also carries the durable pin id (lineage root) so drop targets key the store the same way the context-menu path does. The hovered section highlights, drops land on the header too (works while collapsed or empty), and the target section auto-opens so the landed row is visible. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ns render, drag hint Three gaps from the drag-to-pin feature (#145): Pinned messaging/cron-adjacent rows now render: sessionByAnyId only indexed cron + recents, so pinning a messaging-platform row (menu, shift-click, or drag) stored an id the Pinned section could never resolve — the pin silently vanished. Messaging sessions join the index, and pinned rows are filtered out of their platform section so pinning MOVES the row (matching how recents behave) instead of duplicating it. Drops are positional: rows carry data-session-id, the drop zone hands the drop event to its handler, and sessionDropAnchor() resolves the row under the pointer (top half = before, bottom half = after). Drag-to-pin inserts at that index in the raw pinned-id store (translated via the anchor's durable pin id, since rendered rows can be a subset of stored ids); drag-to-unpin splices into the saved flat-list order, falling back to the old surface-at-top reconcile for header drops, fresh anchors, or grouped/ALL-profiles views. The empty-Pinned hint now teaches the gesture: 'Drag a chat here, or shift-click to pin' (en/zh/zh-hant/ja). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pushed a second commit folding in three follow-ups so the PR lands complete:
Verified the same way as the base commit (live Chromium drag simulation + 13 unit tests; tsc/eslint clean on this branch). |
…nned rows The Sessions label counted unpinned rows against a total that includes pinned ones, so pinning a single row left the count stuck at '17/18' forever — advertising a page that can never arrive. Pinned rows are always loaded (the refresh keep-set preserves them), so drop them from BOTH sides of the label. Messaging sections get the same honesty: pinned rows are hidden from the section (they live in Pinned) but still count as LOADED for the count label and reveal-step math, so hiding them can't make the pager think more rows remain on disk. Section recency now comes from every loaded row so a platform doesn't reshuffle when its newest thread gets pinned, and a platform only drops its section when it has nothing left to show AND nothing more on disk. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Third commit: pin-aware count labels and pagers. The Sessions label previously counted unpinned rows against a pin-inclusive total (pin one row and it reads 17/18 forever); pinned rows are always loaded, so they're now dropped from both sides. Messaging platform sections count pinned rows as loaded for their label/reveal-step (they're housed in Pinned, not missing from disk), take section recency from every loaded row, and only drop their section when nothing is left to show and nothing more exists on disk. tsc/eslint clean on this branch; the drop-zone suite still passes 13/13. |
|
Maintainer-ready after refresh. I merged current Verification:
MeshBoard merge dry-run passes with green checks and a fresh base. I attempted the actual merge through |
Problem
The first pass made sidebar rows draggable between Pinned and Sessions for pin/unpin, but the final accepted UX needs the whole row to behave like Claude/Codex session reordering:
Change
This updates the sidebar drag path around the native session row drag payload:
The existing dnd-kit grab-handle path is left for grouped workspace ordering; the flat Pinned/Sessions row reorder path no longer requires the handle.
How to review
Testing
npx vitest run --environment jsdom src/app/chat/sidebar/use-session-drop-zone.test.tsx— 21 tests passed.npm --prefix apps/desktop run typecheck— passed.npx eslint src/app/chat/sidebar/index.tsx src/app/chat/sidebar/session-row.tsx src/app/chat/sidebar/use-session-drop-zone.ts src/app/chat/sidebar/use-session-drop-zone.test.tsx src/app/chat/sidebar/virtual-session-list.tsx— passed.git diff --check— passed.npm --prefix apps/desktop run build— passed. The build-stamp script warned that the worktree was dirty before commit, as expected during pre-commit verification.