Skip to content

feat(desktop): reorder pinned sessions by dragging within the sidebar - #70223

Open
SHL0MS wants to merge 1 commit into
NousResearch:mainfrom
SHL0MS:feat/desktop-pinned-reorder-drop
Open

feat(desktop): reorder pinned sessions by dragging within the sidebar#70223
SHL0MS wants to merge 1 commit into
NousResearch:mainfrom
SHL0MS:feat/desktop-pinned-reorder-drop

Conversation

@SHL0MS

@SHL0MS SHL0MS commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Two related asks for the pinned-sessions sidebar: reorder pinned chats, and let the pinned section grow past ~6 rows.

Reorder (the substantive half)

Reordering pinned rows was effectively broken. The pinned list had a reorder affordance on a near-invisible dnd-kit grab handle (the leading status dot), but commit ac4f596ca2 made the whole row body a pointer session-drag source (drag a row onto the chat to link it as an @session ref, or onto a zone edge to split). The two drag systems fought on the same row and session-drag won: grabbing a pin started a link-into-chat drag, and even when you found the handle its dotted focus ring appeared but the reorder never committed.

Fixed by resolving the conflict the way the session-drag engine already works — by drop location, not by which pixel you grab. The engine is a resolver: it snapshots targets at drag engage, hit-tests the pointer each move, and acts on release. This adds sidebar reorder as one more resolved target:

  • reorder-zones.ts — a small registry a flat list joins while mounted (container element + live id getter + onReorder). The drag snapshots registered zones at engage; resolveReorderTarget picks the insertion slot by pointer-Y (skipping the dragged row); reorderIds applies it with a no-op-identity guard. Both are pure functions, unit-tested.
  • session-drag.tsresolveMove checks reorder first (inside the bar's bounds it wins), otherwise falls through to the existing stack/split/link zone logic unchanged; onCommit reorders in place instead of opening a tile or link. A dedicated $sidebarReorderHint atom drives the insertion line so the chat surfaces never re-render on reorder churn (they subscribe to the heavy $dropHint only for real link/split hints).
  • reorder-zone-list.tsx — registers the zone, tags rows (data-reorder-row) for the geometry snapshot, and paints the insertion line. No dnd-kit handle. The pinned section renders through it (reorderViaDrag) instead of ReorderableList.

Net behavior: drag a pin within the sidebar → reorder; drag it onto the chat → link (unchanged). Recents/Sessions rows keep session-drag exactly as before. This is the model the reporter asked for and it matches how the engine was built to route.

Height

The pinned body was hard-capped at max-h-44 (~6 rows) then scrolled. Relaxed to max-h-[40vh] so more pins show on a tall window. Still bounded, so the Sessions section below can never be buried — preserving the #43147 anti-overlap invariant — and GROUP_BODY still flattens the cap in compact mode.

Related Issue

Fixes the reorder half of #47728 (closed implemented-on-main, but reorder never actually worked once row-body session-drag landed — the handle was shadowed by the link drag). The height cap is the other half of the reporter's ask.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (the height relaxation + drop-location routing)

How was this tested?

  • Unit (reorder-zones.test.ts, 11 cases): slot-boundary math, dragged-row skip, foreign-drop rejection (a non-pinned row over the zone is not a reorder), multi-zone hit-testing, and reorderIds no-op identity + end-insertion.
  • Component (reorder-zone-list.test.tsx, 4 cases): zone register/unregister lifecycle, live-id reporting across re-render, per-row tagging, and insertion-line rendering only for a drag of the list's own row.
  • Real pointer-drag e2e (pinned-reorder.spec.ts): creates two pinned sessions, drags the top row down within the sidebar via actual mouse.down/move/up (stepped, like tile-unread-bug.spec), asserts the pinned order flips. Ran locally against the packaged macOS app — passes (1 passed (7.7s)).
  • npx tsc -p . / tsconfig.electron.json / tsconfig.e2e.json all clean; full renderer suite 2050 passed; eslint clean on touched files (test-file document warnings match existing test conventions).

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature
  • I've run the desktop renderer suite (npx vitest run --project ui) and all tests pass; pytest N/A (no Python changes)
  • I've added tests for my changes (unit + component + real-drag e2e)
  • I've tested on my platform: macOS 26.5 (arm64), packaged app

Documentation & Housekeeping

  • Docs — N/A (no user-facing doc describes the pinned drag/height behavior)
  • cli-config.yaml.example — N/A (no config keys)
  • CONTRIBUTING.md / AGENTS.md — N/A
  • Cross-platform impact considered — renderer + shared drag engine; the max-h-[40vh] and pointer-drag path are platform-agnostic. The e2e was exercised on macOS.
  • Tool descriptions/schemas — N/A

The pinned list had a reorder affordance on a near-invisible dnd-kit
grab handle (the leading status dot), but commit ac4f596 made the
whole row body a pointer session-drag source (drag onto chat to link /
split). The two drag systems fought on the same row and session-drag
won: grabbing a pin started a link-into-chat drag, and the handle's
dotted focus ring appeared but reorder never committed.

Resolve it the way the session-drag engine already works — by DROP
LOCATION, not by which pixel you grab. The engine is a resolver:
snapshot targets at engage, hit-test the pointer, act on release. This
adds sidebar reorder as one more resolved target:

- reorder-zones.ts: a small registry a flat list joins while mounted
  (container el + live ids + onReorder). The drag snapshots registered
  zones at engage; resolveReorderTarget picks the insertion slot by
  pointer-Y (skipping the dragged row); reorderIds applies it with a
  no-op guard. Both are pure and unit-tested.
- session-drag.ts: resolveMove checks reorder FIRST (inside the bar it
  wins), else falls through to the existing stack/split/link zone
  logic; onCommit reorders in place instead of opening a tile/link.
  A dedicated  atom drives the insertion line so
  chat surfaces never re-render on reorder churn.
- reorder-zone-list.tsx: registers the zone, tags rows
  (data-reorder-row) for the geometry snapshot, and paints the
  insertion line — no dnd-kit handle. The pinned section renders
  through it (reorderViaDrag) instead of ReorderableList.

Net: drag a pin within the sidebar → reorder; drag it onto the chat →
link (unchanged). Recents/Sessions rows still use session-drag as
before. Fixes the reorder half of NousResearch#47728 (which was closed
implemented-on-main but never actually worked once row-body drag
landed).

Also relax the pinned section's fixed max-h-44 (~6 rows) to max-h-[40vh]
so more pins show on tall windows; still bounded so Sessions can't be
buried (the NousResearch#43147 anti-overlap invariant) and GROUP_BODY still
flattens it in compact mode.

Tests: reorder-zones (11 pure-logic cases: slot boundaries, dragged-row
skip, foreign-drop rejection, no-op identity), reorder-zone-list (zone
registration lifecycle, live ids, own-drag-only insertion line), and a
real-pointer-drag e2e (pinned-reorder.spec) asserting the order flips.
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/desktop Electron desktop app (apps/desktop/*) area/sessions Session lifecycle, resume, persistence, history sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state needs-decision Awaiting maintainer decision before any implementation labels Jul 23, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: #43661 also changes Desktop sidebar drag/reorder behavior, while #66051 handles persisted manual pin order. This PR resolves the row-drag conflict through drop-location routing with separate reorder zones; the overlapping but different mechanisms need maintainer consolidation.

@Nicolas-Formenton

Copy link
Copy Markdown
Contributor

FYI: opened #84984 (drag to reorder profile groups in the All-profiles sidebar). It also touches sessions-section.tsx/index.tsx but only the profile-mode groups branch, disjoint from the pinned-session path here — no behavioral overlap, likely trivial import-context conflicts if either merges first.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/desktop Electron desktop app (apps/desktop/*) needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants