Add user-defined thread folders to the sidebar#3071
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Needs human review 1 blocking correctness issue found. This PR introduces a substantial new feature (user-defined thread folders) with new UI components, drag-and-drop functionality, and state management. New user-facing features warrant human review. Additionally, an unresolved high-severity bug was identified where click suppression flags aren't reset when dropping outside valid targets. You can customize Macroscope's approvability policy. Learn more. |
Add collapsible, per-project folders to organize sidebar threads, with drag-and-drop (including multi-select moves), inline rename, and manual ordering of folders and of threads within a folder. State is client-only in useUiStateStore (localStorage); a thread belongs to at most one folder; ungrouped threads render below folders and remain sort-ordered. - uiStateStore: ThreadGroup model + reducers (create/rename/delete/move/ reorder/toggle), derived threadKey->groupId index, persistence round-trip, and syncThreadGroups orphan GC against the live snapshot. - sidebarThreadGrouping.ts: pure buildGroupedThreadLayout helper (+ tests). - SidebarThreadGroupRow.tsx: collapsible folder header that is both a sortable item and a drop target, with inline rename. - Sidebar.tsx: per-project DnD context, sortable thread rows with click-vs-drag guards, folder section rendering, pagination of the ungrouped list only, and context-menu CRUD (project header, multi-select, per-thread "Move to folder"). - service.ts: garbage-collect folder state when threads/projects disappear. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Folder-member rows now render inset with a left vertical guide so the nesting under a folder header is visually obvious; ungrouped and collapsed-pinned threads stay flush. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
a14b59c to
b7e73e1
Compare
- Keyboard jump labels now mirror the folder layout (expanded sections then preview-capped ungrouped), so labels match the rendered rows. - Drag and the multi-select "Move to folder" menu now act on the same project-scoped selection (includes off-screen selected rows; ignores other projects' threads), keeping the two paths consistent. - handleThreadDragEnd no-ops when dropped on itself (no spurious reorder). - handleThreadDragCancel clears the click-suppression flags so the next click after a cancelled drag isn't swallowed. - Collapsed project exposes only the pinned thread as its selectable order. - Folder rename: Escape no longer commits via the input's onBlur. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 708f2a5. Configure here.
resolveDraggedThreadKeys now orders the dragged selection by the folder-aware layout (folder members in their in-folder order, ungrouped in sort order) instead of raw sidebar sort order, so multi-dragging grouped threads keeps their order. Still includes off-screen selected rows and stays project-scoped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
| setActiveDragLabel(null); | ||
| }, []); | ||
|
|
||
| const handleThreadDragEnd = useCallback( |
There was a problem hiding this comment.
🟠 High components/Sidebar.tsx:2507
In handleThreadDragEnd, when the user drops outside any valid droppable target (!over is true), the function returns early without resetting suppressThreadClickAfterDragRef.current and suppressGroupClickAfterDragRef.current. These flags were set to true in handleThreadDragStart. In contrast, handleThreadDragCancel explicitly resets both flags with a comment explaining "otherwise the next click on the row/header after a cancelled drag is swallowed." When dropping outside valid targets, dnd-kit fires onDragEnd (not onDragCancel), so the suppression flags remain true, causing the user's next intentional click on a thread row or folder header to be incorrectly suppressed.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/Sidebar.tsx around line 2507:
In `handleThreadDragEnd`, when the user drops outside any valid droppable target (`!over` is true), the function returns early without resetting `suppressThreadClickAfterDragRef.current` and `suppressGroupClickAfterDragRef.current`. These flags were set to `true` in `handleThreadDragStart`. In contrast, `handleThreadDragCancel` explicitly resets both flags with a comment explaining "otherwise the next click on the row/header after a cancelled drag is swallowed." When dropping outside valid targets, dnd-kit fires `onDragEnd` (not `onDragCancel`), so the suppression flags remain `true`, causing the user's next intentional click on a thread row or folder header to be incorrectly suppressed.
Evidence trail:
apps/web/src/components/Sidebar.tsx lines 2478-2496 (handleThreadDragStart sets flags), lines 2498-2505 (handleThreadDragCancel resets flags with explanatory comment), lines 2507-2551 (handleThreadDragEnd never resets flags, early return at line 2512 when !over), lines 460-464 (thread click handler consumes suppressThreadClickAfterDragRef), lines 2407-2409 (group click handler consumes suppressGroupClickAfterDragRef).

Summary
Adds user-defined folders to organize threads within a project in the sidebar. You can create named folders (e.g. "PRs in review", "experiments"), drag threads into them, reorder threads and folders, and collapse/expand folders — all persisted locally.
What's included
Design decisions
useUiStateStore(localStorage), the same place project order/expand state already live. No server/contracts/decider/projector/DB changes. (Trade-off: does not sync across machines; can be lifted to the server later behind the same UI.)threadKeysarray is the single source of truth for both membership and within-folder order; a derived reverse index gives O(1) lookups).syncThreadGroupsgarbage-collects membership for threads/projects that disappear from the live snapshot.Known v1 limitation
Switching the project grouping mode can change a project's logical key and detach its folders. This is non-destructive — affected threads fall back to ungrouped, and membership (keyed by stable thread key) is never lost.
Files
New:
apps/web/src/sidebarThreadGrouping.ts(+ test),apps/web/src/components/SidebarThreadGroupRow.tsxChanged:
apps/web/src/uiStateStore.ts(+ test),apps/web/src/components/Sidebar.tsx,apps/web/src/environments/runtime/service.tsTesting
pnpm --filter @t3tools/web typecheck— cleancd apps/web && pnpm exec vp test run --project unit— 1063 passed (incl. new reducer, persistence round-trip, and layout-helper tests)pnpm exec vp lint/vp fmt— cleanpnpm --filter @t3tools/web build) — compiles🤖 Generated with Claude Code
Note
Medium Risk
Large Sidebar and uiStateStore changes affect selection, navigation, and keyboard shortcuts; behavior is client-only with no server sync, and grouping-mode project key changes can detach folders (threads fall back to ungrouped).
Overview
Adds per-project thread folders in the sidebar: create/rename/delete folders, collapse/expand, and move threads via context menus or drag-and-drop (including multi-select). Folder membership, order, and collapse state live in
uiStateStorewith localStorage persistence; snapshot reconciliation prunes stale memberships viasyncThreadGroups.Sidebarnow renders folder sections (SidebarThreadGroupRow) plus capped ungrouped threads usingbuildGroupedThreadLayout. Thread and folder headers are dnd-kit sortable; drops on folder headers, thread rows, or an “Remove from folder” zone update membership. Show more applies only to ungrouped threads so folder contents stay fully visible. Keyboard jump labels follow the same folder-aware visible order.Reviewed by Cursor Bugbot for commit b954989. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add user-defined thread folders to the sidebar
sanitizePersistedThreadGroupsin uiStateStore.ts.buildGroupedThreadLayoutin sidebarThreadGrouping.ts, which produces ordered folder sections and a list of ungrouped threads; pagination ('Show more/less') applies only to ungrouped threads.Macroscope summarized b954989.