Skip to content

feat(web): group sidebar v2 by worktree and scope panes to the checkout#4521

Open
jakeleventhal wants to merge 2 commits into
pingdotgg:mainfrom
jakeleventhal:t3code/group-sidebar-threads-by-worktree
Open

feat(web): group sidebar v2 by worktree and scope panes to the checkout#4521
jakeleventhal wants to merge 2 commits into
pingdotgg:mainfrom
jakeleventhal:t3code/group-sidebar-threads-by-worktree

Conversation

@jakeleventhal

@jakeleventhal jakeleventhal commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Demo

Video Demo: https://cap.so/s/re2f8y944mb8vfr

t3-sidebar-1-worktree-cards-overview t3-sidebar-2-worktree-level-settle-snooze t3-sidebar-3-worktree-activity-indicators t3-sidebar-4-panes-persist-across-threads

What

Sidebar v2 rows now represent a worktree instead of a thread, and the right-panel surfaces follow the checkout rather than the thread.

Sidebar

  • Threads sharing a checkout collapse into one card. Each thread's summary is its own clickable row, carrying that thread's provider logo — threads in one worktree can run different providers.
  • Branch, PR badge, diff stats and the repository row stay at the worktree level. Single-thread worktrees look essentially as they do today.
  • Settle / snooze (and un-settle / wake) act on every thread in the worktree, from the hover actions, the context menu (labelled Settle worktree (N threads) when it applies to more than one) and multi-select. A snoozed worktree gets one Undo toast, not one per thread.
  • The v1 activity indicators are back: pulsing terminal icon and emerald dev-server globe, inline with the repository row — that is the worktree level, which is where terminals and dev servers actually live.
  • Settled and snoozed shelves collapse a whole worktree to a single row.

Panes

Terminal, browser, diff and files are keyed to the worktree, so switching between sibling threads keeps the same terminal session (including a running dev server), the same browser tabs, and the same diff/files state. They change only when the checkout does.

Keybinding

New chat.newInWorktree, default ⌘T: the keyboard equivalent of a card's "New thread on {branch}" action. Falls back to a normal new thread when there is no checkout to join.

How

There is no worktree entity in the data model — a worktree is only thread.worktreePath — so identity is composed client-side in apps/web/src/worktreeScope.ts as environment + project + worktreePath, with a null path meaning the project's local checkout.

Server terminal and preview sessions stay keyed by threadId on the wire, so every thread in a worktree routes those calls through a stable canonical thread ref (the oldest thread ever in the checkout; archived threads stay eligible so the ref cannot shift out from under live sessions).

rightPanelStore, terminalUiStateStore, diffPanelStore and previewStateStore still take a ScopedThreadRef publicly but key internally by worktree scope key.

Notes for reviewers

  • Persisted state is dropped on upgrade. Right-panel (v7→v8), terminal UI (v4→v5) and diff-panel (v1→v2) migrations discard old thread-keyed entries — they can never match a worktree key again, and leaving them would be unreachable garbage.
  • One server change (apps/server/src/ws.ts): archiving a thread no longer closes its terminals while another non-archived thread shares the same checkout. Without this, archiving one thread kills a dev server its siblings are still using. Covered by a new test.
  • Known trade-offs: terminals opened by an older client under a non-canonical sibling thread will not appear in the worktree drawer; multiple local-checkout (non-worktree) threads in one project now share a card and one set of panes, which follows from "panes belong to the checkout".

Testing

  • pnpm typecheck clean in apps/web, apps/server, packages/contracts, packages/shared
  • pnpm test in apps/web: 1527 passing, including 14 new unit tests for the worktree grouping/bucketing/ordering logic
  • pnpm test in apps/server: passing, including a new test for the shared-worktree archive guard. One unrelated GitManager commit-hook test fails on my machine on a clean checkout too — pre-existing, not from this change.
  • Verified manually against a seeded local instance: multi-thread cards, per-thread provider logos, worktree-level settle/snooze, live terminal + dev-server indicators, and terminal/dev-server persistence across sibling-thread navigation.

🤖 Generated with Claude Code


Note

Medium Risk
Touches orchestration archive/terminal lifecycle and re-keys persisted UI state (intentional data loss on upgrade); behavior changes when multiple threads share a checkout.

Overview
Threads that share the same checkout are treated as one worktree in the UI: client-side scope keys (environment + project + worktreePath) drive sidebar grouping and pane state, while terminal/preview RPCs still use a stable canonical thread id (oldest thread in that checkout).

Sidebar v2 collapses sibling threads into a single card with one row per thread (provider logo per row), worktree-level branch/PR/activity (terminal pulse, dev-server globe), and settle/snooze that apply to every member thread; snoozed/settled shelves show one slim row per worktree.

Chat and panes route terminal open/close, preview open/close/automation, and mounted terminal drawers through the canonical ref and worktree scope keys so switching threads in the same checkout keeps one terminal session, one set of browser tabs, and shared diff panel selection (diffPanelStore / previewStateStore re-keyed with version bumps that drop old thread-keyed persistence).

Server: thread.archive no longer calls terminalManager.close when another non-archived thread in the same project shares the normalized worktree path (avoids killing a shared dev server).

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

Note

[!NOTE]

Group sidebar v2 threads by worktree and scope UI state to checkout

  • Introduces buildSidebarWorktreeGroups in SidebarV2.logic.ts to group threads sharing a checkout into a single SidebarV2WorktreeCard in the active list, with slim representative rows for snoozed/settled worktrees.
  • All lifecycle actions (settle, snooze, wake, un-settle) now operate on the full worktree group; context menus and selection batches expand to include all group members.
  • Terminal UI state, right-panel layout, diff-panel selections, and preview tabs are re-keyed by worktree scope key (via resolveWorktreeScopeKeyForThreadRef) so sibling threads in the same checkout share state; all affected stores bump their persisted version and drop older entries on upgrade.
  • Preview and terminal wire calls (open, close, resize, automation) now target the worktree's canonical thread id resolved by resolveWorktreeCanonicalThreadRef.
  • The server skips closing terminals on thread.archive when a sibling thread shares the same worktree.
  • Adds a chat.newInWorktree command bound to mod+t that creates a new thread in the active thread's worktree/branch.
  • Risk: persisted state in terminalUiStateStore (v<5), rightPanelStore (v<8), and diffPanelStore (v<2) is dropped on upgrade.
📊 Macroscope summarized 48a7020. 18 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted > ### 🗂️ Filtered Issues No issues evaluated. > >
>

Sidebar v2 cards now represent a worktree rather than a thread. Threads
sharing a checkout collapse into one card, where each thread's summary is
its own clickable row carrying that thread's provider logo; the branch,
PR badge and repository row stay at the worktree level. Settle and snooze
(and un-settle/wake) act on every thread in the worktree, and the v1
activity indicators — pulsing terminal and dev-server globe — return on
the card's top row, where they belong now that terminals and dev servers
are worktree-scoped.

The right-panel surfaces (terminal, browser, diff, files) follow the same
rule: switching between sibling threads keeps the same terminal session,
dev server, browser tabs and diff state, and only changes when the
checkout does. There is no worktree entity in the data model, so identity
is composed client-side in worktreeScope.ts (environment + project +
worktreePath, with null paths meaning the project's local checkout).
Server terminal and preview sessions remain keyed by threadId on the
wire, so every thread in a worktree routes those calls through a stable
canonical thread id.

Also adds chat.newInWorktree (default mod+t): the keyboard equivalent of
a card's "New thread on {branch}" action.

Persisted panel state is re-keyed, so right-panel (v8), terminal UI (v5)
and diff-panel (v2) drop their old thread-keyed entries on migrate.
Server-side, archiving a thread no longer closes terminals that sibling
threads in the same checkout still use — that would kill a shared dev
server.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 25, 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 Plus

Run ID: f2ca5a97-18cd-49f5-aceb-1d39f7cf459f

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 vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Jul 25, 2026
@@ -240,7 +242,7 @@ function isValidTerminalId(terminalId: string): boolean {
}

function terminalThreadKey(threadRef: ScopedThreadRef): string {

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.

🟡 Medium src/terminalUiStateStore.ts:244

When a terminal UI action is performed before the thread's shell is bootstrapped, terminalThreadKey resolves to the fallback scoped thread key (via resolveWorktreeScopeKeyForThreadRef). Once readThreadShell becomes available, the same thread ref resolves to the worktree scope key instead. State written under the fallback key is never migrated, so the drawer layout appears reset and the old entry becomes stranded in storage. Consider migrating the fallback-keyed entry to the worktree key when the shell becomes available, or document this bootstrap-window behavior if it is intentional.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/terminalUiStateStore.ts around line 244:

When a terminal UI action is performed before the thread's shell is bootstrapped, `terminalThreadKey` resolves to the fallback scoped thread key (via `resolveWorktreeScopeKeyForThreadRef`). Once `readThreadShell` becomes available, the same thread ref resolves to the worktree scope key instead. State written under the fallback key is never migrated, so the drawer layout appears reset and the old entry becomes stranded in storage. Consider migrating the fallback-keyed entry to the worktree key when the shell becomes available, or document this bootstrap-window behavior if it is intentional.

Comment on lines +16 to +19
function normalizeWorktreePath(worktreePath: string | null | undefined): string | null {
const trimmed = worktreePath?.trim();
return trimmed && trimmed.length > 0 ? trimmed : null;
}

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.

🟡 Medium src/worktreeScope.ts:16

normalizeWorktreePath trims leading and trailing whitespace from worktreePath, so distinct worktrees whose paths differ only by surrounding spaces (e.g. /repo/wt and /repo/wt ) produce the same scope key. Their terminal/preview panes are then merged, and canonical RPC resolution can route operations through the wrong thread. Trimming discards information that is semantically meaningful in filesystem paths — consider using the raw value (only mapping empty/whitespace-only strings to null) so distinct checkout paths stay distinct.

Suggested change
function normalizeWorktreePath(worktreePath: string | null | undefined): string | null {
const trimmed = worktreePath?.trim();
return trimmed && trimmed.length > 0 ? trimmed : null;
}
+function normalizeWorktreePath(worktreePath: string | null | undefined): string | null {
+ return worktreePath && worktreePath.length > 0 ? worktreePath : null;
+}
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/worktreeScope.ts around lines 16-19:

`normalizeWorktreePath` trims leading and trailing whitespace from `worktreePath`, so distinct worktrees whose paths differ only by surrounding spaces (e.g. `/repo/wt` and `/repo/wt `) produce the same scope key. Their terminal/preview panes are then merged, and canonical RPC resolution can route operations through the wrong thread. Trimming discards information that is semantically meaningful in filesystem paths — consider using the raw value (only mapping empty/whitespace-only strings to `null`) so distinct checkout paths stay distinct.

// worktree's PR state applies to every member thread of the group.
const prState = pr?.state ?? null;
const { changeRequestThreadKeys } = props;
useEffect(() => {

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.

🟡 Medium components/SidebarV2.tsx:484

The useEffect in SidebarV2Row pushes the representative thread's prState to every member via changeRequestThreadKeys, but for local-checkout groups resolveThreadPr returns null whenever the representative's thread.branch doesn't match the checked-out branch. When the representative isn't the thread on that branch (e.g. a sibling is), this writes null over the real PR state for all members, hiding the PR badge and clearing closed/merged-PR auto-settlement for the whole group.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/SidebarV2.tsx around line 484:

The `useEffect` in `SidebarV2Row` pushes the representative thread's `prState` to every member via `changeRequestThreadKeys`, but for local-checkout groups `resolveThreadPr` returns `null` whenever the representative's `thread.branch` doesn't match the checked-out branch. When the representative isn't the thread on that branch (e.g. a sibling is), this writes `null` over the real PR state for all members, hiding the PR badge and clearing closed/merged-PR auto-settlement for the whole group.

let anyWoke = false;
const wokeAtByKey = new Map<string, string | null>();
threads.forEach((thread, index) => {
const lastVisitedAt = visited[index] === "" ? undefined : visited[index];

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.

Card visit stamps are corrupted

High Severity

The visitedSignature processing incorrectly breaks lastVisitedAt timestamps into individual characters. This corrupts the timestamp values, causing inaccurate "Done/Woke" status and recede styling on worktree cards.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eebafbb. Configure here.

return null;
}
return selectCanonicalShell(ref, shell, shells);
}, [ref, shell, shells]);

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.

Canonical ref shifts after archive

High Severity

The client's canonical thread selection for a worktree, which keys shared resources like terminals, incorrectly excludes archived threads. This causes the canonical thread ID to shift when the oldest thread is archived, breaking shared terminal/session access for active sibling threads. It also leads to terminal leaks, as the server's closing logic may miss terminals associated with an earlier-archived canonical thread when the last active sibling is archived.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eebafbb. Configure here.

// sit inline with the repository row because terminals and dev servers
// belong to the checkout, not to an individual thread.
const runningTerminals = useRunningTerminalsForThreads({ environmentId, threadIds });
const discoveredPorts = useDiscoveredPortsForThreads({ environmentId, threadIds });

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.

Indicators miss canonical sessions

Medium Severity

Worktree terminal and dev-server indicators query only current card member thread ids. Sessions opened through the worktree canonical id disappear from those indicators whenever that canonical thread is no longer a visible member, so the restored v1 activity glyphs can stay off while a process is still running.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eebafbb. Configure here.


function terminalThreadKey(threadRef: ScopedThreadRef): string {
return scopedThreadKey(threadRef);
return resolveWorktreeScopeKeyForThreadRef(threadRef);

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.

Delete wipes shared terminal UI

Medium Severity

clearTerminalUiState is now keyed by worktree scope, but delete still clears it for the deleted thread ref. Removing one thread in a multi-thread checkout resets the shared drawer layout for every remaining sibling. Delete also closes terminals by the deleted threadId with no shared-worktree guard, so deleting the current canonical member can kill sessions siblings still use.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit eebafbb. Configure here.

@macroscopeapp

macroscopeapp Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

3 blocking correctness issues found. This PR introduces a significant new feature (worktree-based sidebar grouping) with cross-cutting changes to multiple state stores and server-side terminal logic. Multiple high-severity bugs identified in review comments (corrupted timestamps, canonical ref shifts) require resolution before approval.

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

@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 1 potential issue.

There are 5 total unresolved issues (including 4 from previous reviews).

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 48a7020. Configure here.

Comment thread apps/server/src/ws.ts
error: error.message,
}),
),
);

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.

Archive closes wrong terminal owner

High Severity

Terminal closure and UI state clearing logic incorrectly targets the specific archived or deleted thread ID. Client terminal sessions, however, are shared across a worktree and associated with a canonical thread ID. This mismatch can lead to PTY and dev server leaks when a worktree is fully parked, as the canonical session may not be closed. It can also cause unintended UI state resets for other active threads in the same worktree.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 48a7020. Configure here.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eebafbb2a0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

update: (current: ThreadPreviewState) => ThreadPreviewState,
): void {
const threadKey = scopedThreadKey(ref);
const threadKey = previewScopeKey(ref);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the canonical preview ref in the aggregate index

When a known thread opens an Electron preview, this now stores a worktree key such as environment:project:path in the active-preview index, but ElectronBrowserHost still passes every index key to parseScopedThreadKey. That parser accepts the value as a thread ref whose thread ID is project:path, so usePreviewBridge sends preview.reportStatus under a nonexistent thread ID while the session was opened under the canonical thread ID. Consequently navigation, loading, and failure updates cannot find the server session; retain the canonical thread ref alongside the worktree-keyed state instead of interpreting the state key as a thread ref.

Useful? React with 👍 / 👎.

Comment on lines +103 to +104
readEnvironmentThreadRefs(ref.environmentId).map((candidateRef) =>
readThreadShell(candidateRef),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Retain a canonical owner across thread deletion

When the oldest thread in a shared checkout is deleted while a sibling remains, deleted threads disappear from readEnvironmentThreadRefs, so this resolver immediately elects a different canonical thread. All shared terminal and preview RPCs were previously routed through the deleted thread ID; the delete flow closes those terminals, while preview sessions remain keyed to an ID the sibling no longer queries. Thus deleting one conversation resets or orphans checkout-level panes for the surviving conversation. The canonical owner must remain durable across deletion or the server sessions must be migrated before changing it.

Useful? React with 👍 / 👎.

Comment thread apps/server/src/ws.ts
Comment on lines +1168 to +1171
thread.id !== archivedThread.id &&
thread.projectId === archivedThread.projectId &&
normalizeWorktreePathForArchive(thread.worktreePath) ===
normalizeWorktreePathForArchive(archivedThread.worktreePath),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Close terminals after the last live sibling is archived

When the final unarchived thread in a checkout is archived after any sibling was archived earlier, shell snapshots still contain that earlier thread with a non-null archivedAt, and this predicate counts it as continued use. The close branch is therefore skipped, leaving the canonical checkout PTYs and subprocesses—including dev servers—running indefinitely with no live thread from which to access them. This check must consider only non-archived siblings and, when none remain, close the checkout's canonical terminal owner rather than assuming the just-archived thread owns the sessions.

Useful? React with 👍 / 👎.

Comment on lines +1080 to +1081
const runningTerminals = useRunningTerminalsForThreads({ environmentId, threadIds });
const discoveredPorts = useDiscoveredPortsForThreads({ environmentId, threadIds });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include the archived canonical owner in activity lookup

When the oldest thread is archived while a sibling remains active, the canonical-thread rule deliberately keeps all terminal and preview RPCs keyed to that archived thread. Sidebar groups, however, contain only unarchived members, so threadIds excludes the actual owner and both hooks return no matches. The active worktree card then loses its running-terminal and dev-server indicators even though those sessions remain available in its shared panes. Resolve the canonical owner for the group or query activity by worktree identity rather than only visible member IDs.

Useful? React with 👍 / 👎.

Comment on lines +2234 to +2235
const settleable = pending.filter((thread) => canSettle(thread, { now }));
const targets = settleable.length > 0 ? settleable : [pending[0]!];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reject settling a worktree when any member is blocked

When a worktree contains both an idle thread and a running, starting, approval-blocked, or queued thread, the card still offers “Settle worktree,” but this filtering silently settles only the eligible members. The blocked member keeps the card active, so the advertised worktree-level action only partially applies and appears ineffective while mutating sibling state. As already done for worktree snoozing, disable or reject the action unless every affected non-settled member can settle.

Useful? React with 👍 / 👎.

Comment on lines +1193 to +1196
const pr = resolveThreadPr({
threadBranch: newest.branch,
gitStatus: gitStatus.data,
hasDedicatedWorktree: worktreePath !== null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Derive local-checkout branch state from the checkout

When local-mode sibling threads were created or last used on different branches, the newest-created thread is not necessarily the branch currently checked out in their shared workspace. Using newest.branch here makes the worktree card show a stale branch and causes resolveThreadPr to discard the checkout's valid PR whenever gitStatus.refName differs; the null PR state is then fanned out to every member and can also prevent PR-driven settlement. For a local-checkout group, derive the worktree-level branch and PR from the queried checkout status rather than the newest thread.

Useful? React with 👍 / 👎.

Comment on lines +42 to +43
const shell = readThreadShell(ref);
return shell === null ? scopedThreadKey(ref) : threadWorktreeScopeKey(shell);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Migrate draft pane state when its shell materializes

When a user opens a terminal, preview, diff, or right-panel surface on an unsent draft, the shell is absent and all of those stores write under the plain scoped thread key. After the first send creates the server shell, the same ref resolves to a worktree key here, but no state is transferred from the fallback key. The promoted thread therefore appears to lose its open panes, terminal layout, and preview sessions even though the draft was deliberately promoted with the same preallocated thread ID. Promotion must migrate the fallback entry or use a stable scope key throughout bootstrap.

Useful? React with 👍 / 👎.

reconcileTurnSelection: (ref, availableTurnIds) =>
set((state) => {
const threadKey = scopedThreadKey(ref);
const threadKey = diffScopeKey(ref);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clear stale turn selection for siblings without turn diffs

When thread A leaves the shared diff store in a turn selection and the user switches to sibling B with no completed turn diffs, B reads A's worktree-keyed selection. reconcileTurnSelection returns early when availableTurnIds is empty, so selectedTurnId remains non-null while no selectedTurn exists; DiffPanel consequently disables both the checkpoint request and the branch/working-tree queries and renders no useful diff. Turn selections must remain thread-specific or fall back to the checkout's git scope when the active sibling has no matching turns.

Useful? React with 👍 / 👎.

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