# feat\(web\): drag threads across sections with consistent motion · PR \#9731 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/9731)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: merged

Source repository: pingdotgg/t3code

Source branch: sidebar/drag-sections

Target branch: sidebar/mobile-active-order

Head commit: e9080323db5b6549d92a0c107c20fed6aae438fb

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/9731)

[Commits](https://git.cafe/pingdotgg/t3code/pull/9731/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/9731/diffs?format=markdown)

## Description

---

Only pinned threads previously had direct drag reordering. Threads can now move between Pinned, Active, and Settled, wake by dragging out of Snoozed, and keep the chosen position within Pinned or Active. Section hints appear during the drag without adding idle headers or moving the list at pickup.

A single sortable list keeps the dragged row and insertion gap continuous across section boundaries. Ordinary thread actions retain their section-change animations, including the card/compact fade, while drag completion avoids a second layout transition. Reduced-motion preferences are respected.

Older servers retain pinned reordering without settlement support. Drop planning reserves hidden order keys, and marker IDs cannot collide with scoped thread IDs.

Verified with 207 focused sidebar tests, web typecheck, targeted lint, and an integrated browser pass covering Active reordering, pin/unpin, and Settle/Un-settle. Recordings use disposable seeded threads. Web and desktop share this sidebar; mobile controls are in the preceding PR.

| Before: dragging an Active thread | After: the row follows the insertion gap |
| --- | --- |
| ![Before: Active row stays in place](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/b6227f318a2b5875/web-base-active-drag.png) | ![After: Active row reorders with a section hint](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/16ed50e616dee7c4/web-after-active-drag.png) |

| Before: idle sidebar | After: idle sidebar, without Pinned or Active headings |
| --- | --- |
| ![Before: idle sidebar](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/28493a24c72579b7/web-base-idle.png) | ![After: idle sidebar without drag hints](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/c5753764bf7d234e/web-after-idle.png) |

Active reorder recordings: [before](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/ee2340a7883add6c/web-base-active-drag.mp4), [after](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/f996b89b08876c61/web-after-active-drag.mp4).

Pinning and unpinning by drag:

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/1caa2d3fcb56a1d0/web-cross-sections-final.mp4

Restored Settle and Un-settle animations:

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/faabc7dac953ecb1/web-section-actions.mp4

Depends on [#9730](https://github.com/pingdotgg/t3code/pull/9730).

Prepared with Codex.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Large interaction surface touching pin/settle/snooze lifecycle and order-key persistence with optimistic concurrency, but logic is heavily unit-tested and gated on server capabilities.
> 
> **Overview**
> **Cross-section drag** replaces pinned-only reordering: one `DndContext` and sortable list spans Pinned, Active, Settled, and Snoozed (wake on drag out; no drop into snooze). Section headers, dividers, and empty placeholders are sortable markers so the gap and drop targets stay continuous.
> 
> **New `Sidebar.drag` and `Sidebar.logic` plumbing** resolves destinations (`resolveSidebarDropTarget`), plans server writes (`planSidebarThreadDrop` — pin, unpin, settle, active reorder), and previews row state (`applySidebarThreadDrop`). Custom collision detection handles invalid targets and empty Pins; a custom sorting strategy projects layout across section boundaries (collapsed settled shelf, route row visibility, etc.).
> 
> **Motion and animation**: `@formkit/auto-animate` is removed in favor of **`Sidebar.motion`** for post-action section moves (with reduced-motion and drag suspend). **`animateSidebarLayoutChanges`** avoids double animation after drop. Active list order now follows **`activeOrderKey`** via shared `client-runtime` sort helpers; drops call **`reorderActiveThread`** when the server exposes `threadActiveReorder`.
> 
> **Optimistic `optimisticDrop`** generalizes the old pinned-order hold across section moves and key writes until canonical state catches up. UI shows drag destination hints and boundary labels only while dragging.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit d68f19b8a655d670b7566b6901594a7ae13f5ae6. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- Macroscope's pull request summary starts here -->
<!-- Macroscope will only edit the content between these invisible markers, and the markers themselves will not be visible in the GitHub rendered markdown. -->
<!-- If you delete either of the start / end markers from your PR's description, Macroscope will append its summary at the bottom of the description. -->
> [!NOTE]
> ### Add cross-section thread dragging with consistent motion to sidebar
> - Adds dragging threads between Pinned, Active, Snoozed, and Settled sections. The drop planner generates lifecycle commands, and an optimistic projector previews the thread in its destination before server confirmation.
> - Introduces `createSidebarListMotion` in [Sidebar.motion.ts](https://github.com/pingdotgg/t3code/pull/9731/files#diff-33e9095bd736a0287a94ff2a9d24e4076154a4ff0c6902c6dcef98e5644dfbe8) to animate retained rows with 150ms ease-out translations, fade entering and exiting rows, and honor reduced-motion preferences.
> - Replaces pinned-only DnD utilities with a unified sortable list in [Sidebar.tsx](https://github.com/pingdotgg/t3code/pull/9731/files#diff-12c9f3c619e6ab8dda238e965323ac96e62f3084e3adfb0671b1a6459aa61ba1) spanning all sections, including structural markers and empty-section placeholders as drop targets.
> - Risk: `sortThreadsForSidebar` now uses the client-runtime Active order-key sorter instead of the previous timestamp-and-id comparator, and `activeThreadAnchorTimestampMs` is removed from the [threadSort](https://github.com/pingdotgg/t3code/pull/9731/files#diff-0e3be39caf95414e9d9732e11f3ec3b6436f9854f257d442f5bd2b5d783a1a4e) re-export surface.
>
> <!-- Macroscope's review summary starts here -->
>
> <sup><a href="https://app.macroscope.com">Macroscope</a> summarized 33e70c4.</sup>
> <!-- Macroscope's review summary ends here -->
>
<!-- Macroscope's pull request summary ends here -->
