Skip to content

feat: Add thread pinning#969

Open
Noojuno wants to merge 4 commits intopingdotgg:mainfrom
Noojuno:t3code/pin-thread-to-top
Open

feat: Add thread pinning#969
Noojuno wants to merge 4 commits intopingdotgg:mainfrom
Noojuno:t3code/pin-thread-to-top

Conversation

@Noojuno
Copy link
Contributor

@Noojuno Noojuno commented Mar 12, 2026

What Changed

Closes #698

  • Adds pinned threads.
    • A pinned thread is displayed above the rest of the threads for a project in the sidebar, indicated by a pin icon on the left hand side
  • Adds a right-click context menu action on a thread in the sidebar to pin/unpin a thread

Why

My workflow means that I am often creating lots of small threads, with potentially one larger thread running in the background in case I need to make changes with the full context of the plan. This larger thread was often getting buried by the smaller threads. This thread is also often where I would have my terminal running in dev mode, which would be tricky to find sometimes to cancel it.

UI Changes

image image

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

Add thread pinning to sidebar with persistence across server and client

  • Adds a pinned boolean field to the Thread type, OrchestrationThread schema, and projection_threads DB table (via a new migration).
  • The sidebar context menu gains a pin/unpin toggle that dispatches a thread.meta.update command; pinned threads show a pin icon and are sorted to the top of the list via a new sortThreadsForSidebar util.
  • The decider emits pinned: false on new thread.created events and forwards the pinned field on thread.meta-updated events when provided by the command.
  • The projection pipeline, in-memory read model, and repository all read and write the pinned state; the client store syncs it from the server snapshot.
  • Behavioral Change: existing rows in projection_threads get pinned = 0 (false) via the migration default, and historical thread.created event payloads decode with pinned: false by default.
📊 Macroscope summarized 7311ae8. 25 files reviewed, 2 issues evaluated, 1 issue filtered, 1 comment posted

🗂️ Filtered Issues

apps/server/src/orchestration/Layers/ProjectionPipeline.ts — 0 comments posted, 1 evaluated, 1 filtered
  • line 423: In the thread.created case (line 423), event.payload.pinned is directly assigned without a fallback default. If this code replays events from the event store that were created before the pinned field was added to the event schema, event.payload.pinned will be undefined. Since ProjectionThread.pinned is defined as Schema.Boolean (not nullable/optional), this would cause a schema validation failure during projectionThreadRepository.upsert(). The thread.meta-updated case at line 446 correctly handles this with a conditional spread ...(event.payload.pinned !== undefined ? { pinned: event.payload.pinned } : {}), but the thread.created case should similarly provide a default value like pinned: event.payload.pinned ?? false. [ Failed validation ]

Note

Medium Risk
Adds a new persisted pinned field to threads across contracts, event projection, and the DB, plus UI behavior changes in the sidebar; schema/migration and sorting logic changes carry moderate risk of data/UX regressions.

Overview
Adds thread pinning end-to-end by introducing a pinned boolean on threads (defaulting to false for backward compatibility) and allowing it to be updated via thread.meta.update / thread.meta-updated.

Persists pin state in the server projection layer by adding a pinned column to projection_threads (new migration) and wiring pinned through the decider, projector, projection pipeline, snapshot hydration, and projection thread repository.

Updates the web sidebar to display a pin icon for pinned threads, add a context-menu action to pin/unpin (dispatching thread.meta.update), and sort threads so pinned threads appear first; state syncing now hydrates pinned from the server read model, with tests updated/added accordingly.

Written by Cursor Bugbot for commit 7311ae8. This will update automatically on new commits. Configure here.

@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

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: dcd6d673-3e6b-4cb0-b7f8-f4291f203172

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:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Mar 12, 2026
@Noojuno Noojuno force-pushed the t3code/pin-thread-to-top branch from 4d0842f to dc8fed5 Compare March 15, 2026 08:04
@zortos293
Copy link
Contributor

Useful feature for threads, since they can’t normally be sorted. This way, you can pin them in a project and move important stuff above.

@Zimtente
Copy link

++

Noojuno added 4 commits March 25, 2026 10:03
- add `pinned` to orchestration contracts, projector, and projection persistence (with migration)
- support `thread.meta.update` pin toggles via sidebar context menu and `mod+shift+p`
- sort sidebar threads with pinned items first and show a pin indicator
Remove mod+shift+p keybinding for thread.togglePinned from contracts,
server defaults, and web handler. Pinning remains available via the
sidebar context menu.
Use ProjectionThread.mapFields(Struct.assign(...)) for the DB row
schema instead of manually duplicating every field, matching the
pattern used elsewhere in the codebase.
@Noojuno Noojuno force-pushed the t3code/pin-thread-to-top branch from fc69425 to 7311ae8 Compare March 24, 2026 21:09
Copy link
Contributor

@cursor cursor bot left a comment

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.

if (byDate !== 0) return byDate;
return right.id.localeCompare(left.id);
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Duplicate sortThreadsForSidebar function prevents pinned sorting

High Severity

A new sortThreadsForSidebar function (line 65) was added to handle pinned-thread sorting, but the existing sortThreadsForSidebar function (line 284) was not removed or merged. This creates two exported functions with the same name, which is a duplicate function implementation error. Additionally, the new SidebarThreadSortInput type on line 20 duplicates the existing type on line 18. The callers in Sidebar.tsx invoke sortThreadsForSidebar with a sortOrder argument that the new function doesn't accept. Pinned-thread sorting needs to be integrated into the existing function rather than added as a conflicting duplicate.

Additional Locations (1)
Fix in Cursor Fix in Web

Copy link
Contributor

Choose a reason for hiding this comment

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

🔴 Critical

sortThreadsForSidebar,

sortThreadsForSidebar is imported twice at lines 100 and 103 in the same import statement, which causes a compile-time error: "Identifier 'sortThreadsForSidebar' has already been declared." Remove the duplicate import.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file apps/web/src/components/Sidebar.tsx around line 100:

`sortThreadsForSidebar` is imported twice at lines 100 and 103 in the same import statement, which causes a compile-time error: "Identifier 'sortThreadsForSidebar' has already been declared." Remove the duplicate import.

Evidence trail:
apps/web/src/components/Sidebar.tsx lines 91-104 at REVIEWED_COMMIT - import statement shows sortThreadsForSidebar at line 100 and again at line 103

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:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pin / unpin threads to keep important conversations at the top

3 participants