Skip to content

feat(sidebar): pin chats within projects#3913

Open
12ya wants to merge 10 commits into
pingdotgg:mainfrom
12ya:main
Open

feat(sidebar): pin chats within projects#3913
12ya wants to merge 10 commits into
pingdotgg:mainfrom
12ya:main

Conversation

@12ya

@12ya 12ya commented Jul 12, 2026

Copy link
Copy Markdown

What Changed

  • Persist pinned chat state per logical project.
  • Keep pinned chats above the normal thread sort order for each project.
  • Add hover pin and unpin controls without consuming title space.
  • Support pinning and unpinning by dragging a chat across the pinned divider.
  • Show a compact divider with a centered pin icon and distinct pin/unpin feedback colors.
  • Animate chats moving between pinned and regular sections.

Why

Important chats should stay easy to reach without being pinned globally across unrelated projects. Project-scoped pinning keeps each sidebar group predictable while preserving the existing thread sort order for unpinned chats.

The hover control provides a quick direct action, while divider-crossing drag behavior makes bulk sidebar organization feel natural.

UI Changes

Interaction demo:

Screen.Recording.2026-07-12.at.11.51.13.PM.mp4

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Low Risk
Client-only UI and localStorage preferences; no server or auth changes. Drag-and-drop and preview logic are the main regression surfaces.

Overview
Adds project-scoped thread pinning in the sidebar: pinned chats stay at the top of each project’s list (after the existing sort), with state in pinnedThreadKeysByProject persisted via uiStateStore.

Users can pin/unpin from hover actions on each row and by dragging a thread across a pin divider (dnd-kit); divider visibility and pin vs unpin intent use new helpers in Sidebar.logic.ts. Rows split into pinned and regular sections with a SidebarPinnedDivider and safer drag activation (blocked on buttons/inputs).

getVisibleThreadsForProject now accepts an optional getThreadId so folded previews work with scoped thread keys in grouped projects. Thread row UX shifts to explicit hover/focus row actions (pin + archive) and shouldHideThreadMeta instead of pure CSS group-hover for metadata.

Reviewed by Cursor Bugbot for commit 02c19ff. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Add pin-to-top support for chat threads within sidebar projects

  • Introduces per-project thread pinning stored in uiStateStore as pinnedThreadKeysByProject, persisted across sessions.
  • Adds a pin/unpin button on thread rows and drag-to-pin UX: dragging a thread across a divider pins or unpins it via resolveThreadPinCrossingAction.
  • Pinned threads are sorted to the top of each project's thread list using prioritizeThreadsByPinnedKeys, preserving relative order within each group.
  • New sidebar logic utilities (shouldBlockThreadDragActivation, shouldHideThreadMeta, shouldShowPinnedThreadDivider) manage drag safety, metadata visibility, and divider rendering.
  • Behavioral Change: thread row action visibility now depends on hover/focus/mobile state and suppresses during active drags.

Macroscope summarized 02c19ff.

Persist pinned thread keys per logical project, keep pinned chats above the project's normal sort order, and separate pinned chats with a compact divider.
@coderabbitai

coderabbitai Bot commented Jul 12, 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: 2121fdbe-e6fc-4830-87f5-76089d202041

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.

@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Jul 12, 2026
Comment thread apps/web/src/components/Sidebar.tsx
12ya added 2 commits July 13, 2026 09:32
Track row hover and focus directly instead of relying on nested group selectors, and hide row actions while a thread drag is active.
@12ya 12ya marked this pull request as ready for review July 13, 2026 00:36
Comment thread apps/web/src/components/Sidebar.tsx
Render the pinned divider during active drags when all threads are in one group, allowing the first pin and last unpin gestures to cross a measurable boundary.
Comment thread apps/web/src/components/Sidebar.tsx
Comment thread apps/web/src/components/Sidebar.tsx
Comment thread apps/web/src/components/Sidebar.tsx
@macroscopeapp

macroscopeapp Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

New feature introducing thread pinning with drag-and-drop, new UI components, and state management. Additionally, 3 medium-severity bugs have been flagged in review comments identifying potential correctness issues.

You can customize Macroscope's approvability policy. Learn more.

Comment thread apps/web/src/components/Sidebar.tsx Outdated
12ya added 2 commits July 13, 2026 09:49
Prevent drag activation from interactive controls, preserve mobile row metadata beside actions, and keep scoped active threads visible in collapsed previews.
Overlay the temporary divider when only pinned or unpinned threads exist so mounting it during a drag does not shift the active row.

@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 using high effort and found 3 potential issues.

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 02c19ff. Configure here.

scopedThreadKey(scopeThreadRef(thread.environmentId, thread.id)),
),
)
: threadPreview.hiddenThreads;

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.

Stale preview hides active thread

Medium Severity

The useMemo for renderedThreads omits activeRouteThreadKey from its dependencies. When a project is expanded and its thread list is folded, this causes the sidebar to display a stale thread preview, potentially hiding the active thread or showing a previously active one.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 02c19ff. Configure here.

<SidebarMenuSubItem
ref={nodeRef}
aria-hidden="true"
className={`flex h-3 w-full items-center gap-1.5 px-2 transition-colors before:h-px before:flex-1 after:h-px after:flex-1 ${overlaysThreadList ? "absolute inset-x-0 z-10" : ""} ${colorClass}`}

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.

Overlay divider clips on last unpin

Medium Severity

The SidebarPinnedDivider is absolutely positioned with inset-x-0 but no top or bottom when either the pinned or regular thread list is empty. This misplaces its hit target at the top of the list and can hide the divider, leading to incorrect unpinning behavior during drag-and-drop.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 02c19ff. Configure here.

hiddenThreads: threads.filter((thread) => !visibleThreadIds.has(thread.id)),
visibleThreads: threads.filter((thread) => visibleThreadIds.has(thread.id)),
hiddenThreads: threads.filter((thread) => !visibleThreadIds.has(getThreadId(thread))),
visibleThreads: threads.filter((thread) => visibleThreadIds.has(getThreadId(thread))),

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.

Spurious Show more with empty hidden

Medium Severity

When getVisibleThreadsForProject pulls the active thread into the folded preview, it still returns hasHiddenThreads: true even if hiddenThreads is empty. Wiring that into the sidebar makes hasOverflowingThreads stay true whenever total threads exceed the preview limit, so “Show more” can appear after every below-fold chat is already visible—common once pinned rows fill the preview and the active unpinned chat is force-included.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 02c19ff. Configure here.

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

Labels

size:L 100-499 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