Skip to content

Add user-defined thread folders to the sidebar#3071

Open
TheIcarusWings wants to merge 4 commits into
pingdotgg:mainfrom
TheIcarusWings:t3code/sidebar-thread-folders
Open

Add user-defined thread folders to the sidebar#3071
TheIcarusWings wants to merge 4 commits into
pingdotgg:mainfrom
TheIcarusWings:t3code/sidebar-thread-folders

Conversation

@TheIcarusWings

@TheIcarusWings TheIcarusWings commented Jun 13, 2026

Copy link
Copy Markdown

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

  • Create / rename / delete folders from the project-header context menu, with inline rename.
  • Drag-and-drop threads into folders, reorder within a folder, and drag back out (a "Remove from folder" drop zone appears mid-drag). Multi-select moves are supported (drag/menu acts on the whole selection).
  • "Move to folder" submenus on the per-thread and multi-select context menus.
  • Collapsible folders with a count badge; manual ordering of folders (drag headers) and of threads within a folder.
  • Folder-member threads render inset with a vertical guide line for clear nesting.

Design decisions

  • Client-only persistence — folders, membership, order, and collapse live in 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.)
  • Within a single project — a folder belongs to one logical project and renders between the project header and its thread list.
  • A thread is in at most one folder (ordered threadKeys array is the single source of truth for both membership and within-folder order; a derived reverse index gives O(1) lookups).
  • Ungrouped threads stay sort-ordered below folders; pagination caps only the ungrouped list so a curated folder is never partially hidden behind "Show more".
  • syncThreadGroups garbage-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.tsx
Changed: apps/web/src/uiStateStore.ts (+ test), apps/web/src/components/Sidebar.tsx, apps/web/src/environments/runtime/service.ts

Testing

  • pnpm --filter @t3tools/web typecheck — clean
  • cd apps/web && pnpm exec vp test run --project unit1063 passed (incl. new reducer, persistence round-trip, and layout-helper tests)
  • pnpm exec vp lint / vp fmt — clean
  • Production build (pnpm --filter @t3tools/web build) — compiles
  • Manual + headless drive of the running app: create/rename folder, drag a thread in (count 0→1), persistence round-trip, zero console errors

🤖 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 uiStateStore with localStorage persistence; snapshot reconciliation prunes stale memberships via syncThreadGroups.

Sidebar now renders folder sections (SidebarThreadGroupRow) plus capped ungrouped threads using buildGroupedThreadLayout. 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

  • Introduces per-project thread folders (groups) in the sidebar with full CRUD: create, rename, delete, expand/collapse, and reorder via drag-and-drop or context menu.
  • Threads can be moved between folders or to ungrouped via a context menu 'Move to folder' submenu or by dragging (including multi-select), with a dedicated 'Remove from folder' drop zone during drags.
  • Folder state (membership, order, collapse) is persisted to localStorage and rehydrated on startup via sanitizePersistedThreadGroups in uiStateStore.ts.
  • Layout computation is handled by buildGroupedThreadLayout in sidebarThreadGrouping.ts, which produces ordered folder sections and a list of ungrouped threads; pagination ('Show more/less') applies only to ungrouped threads.
  • Keyboard jump indices and shortcut mappings in the sidebar now follow the folder-aware rendered order.

Macroscope summarized b954989.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 212117ee-81af-4e01-984f-78ddd4d5f6f1

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jun 13, 2026
Comment thread apps/web/src/components/Sidebar.tsx
Comment thread apps/web/src/components/Sidebar.tsx
Comment thread apps/web/src/components/Sidebar.tsx
Comment thread apps/web/src/components/Sidebar.tsx
Comment thread apps/web/src/components/SidebarThreadGroupRow.tsx
@macroscopeapp

macroscopeapp Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: 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.

TheIcarusWings and others added 2 commits June 14, 2026 11:15
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>
@TheIcarusWings TheIcarusWings force-pushed the t3code/sidebar-thread-folders branch from a14b59c to b7e73e1 Compare June 14, 2026 10:16
Comment thread apps/web/src/components/Sidebar.tsx Outdated
- 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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread apps/web/src/components/Sidebar.tsx Outdated
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(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 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).

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

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant