Skip to content

fix(sidebar): give a split tab's generated title only to the pane that produced it - #20857

Open
bokeeeey wants to merge 1 commit into
stablyai:mainfrom
bokeeeey:fix/split-pane-generated-title
Open

bokeeeey wants to merge 1 commit into
stablyai:mainfrom
bokeeeey:fix/split-pane-generated-title

Conversation

@bokeeeey

@bokeeeey bokeeeey commented Sep 15, 2026 •

Copy link
Copy Markdown

ELI5

With "Auto-generate tab titles" on, Orca names a tab after the first agent prompt it sees. When a tab is split and each pane runs its own agent, every agent row for that tab shows that one name, so the second agent is listed under the first agent's task. Orca now remembers which pane the name came from, and only that pane's rows use it.

What Changed

  • TerminalTab.generatedTitlePaneKey (optional) records the pane whose prompt produced generatedTitle.
    • applyGeneratedTabTitleUpdates sets it together with the title.
    • It is persisted with .catch(undefined), so a malformed value can't drop the tab on restore.
  • isGeneratedTabTitleOwnedByPane(tab, paneKey) in shared/agent-tab-title.ts is the single rule:
    • If no source is recorded (titles saved before this change), or the caller passes no pane, today's tab-wide behavior is kept.
    • Otherwise only the source pane's rows may use the title.
  • The rule is applied at the same place fix(sidebar): let an agent row show the provider's own session title #19936 scopes provider titles:
    • getAgentRowConversationName takes paneKey. useAgentRowConversationName and rowConversationName pass the row's pane key, which covers worktree-card rows, dashboard rows and the board snapshot.
    • getActivityThreadTaskTitle reads entry.paneKey, which covers the Agents list and the Activity page.
  • The paired web client carries its client-local source along with the title when it rebuilds a mirrored tab, but only under the same local tab id. terminalTabEqual compares the new field.
  • The tab strip label and first-write-wins are unchanged. An identical replacement title is still a no-op, so the source doesn't flip between panes.

Why

Known limits (out of scope):

  • Source pane closed or detached: its old title stays on the tab strip, while the remaining pane's rows show their own name. First-write-wins still blocks a new generated title ([Bug]: Generated tab title is pinned to the tab's first prompt and outranks the live title after /clear #11167).
  • Same-tab orchestration worker: a dispatch replacement from a worker in the same tab moves the source to that worker, whose rows never show tab names by design.
  • Older client: a client that doesn't know the field and re-persists a shared remote session drops the source. The title then falls back to today's tab-wide behavior.

Linked Issue

Fixes #20856

Visual Proof

Headless Electron e2e build, two Codex agents, tabAutoGenerateTitle on.

Scenario Before (main) After
Split tab, both agents start Both rows: "Fix the patient intake flow in the" Left: "Fix the patient intake flow in the"; right: "Design a Redis cache strategy for sessions"
Left pane names the tab, then split, then the right agent starts Both rows: the left pane's title Left keeps its title; right: its own prompt
One agent next to a shell "Fix the patient intake flow in the" Unchanged

The Agents list matches the worktree-card rows in each case.

Worktree card rows (split tab, both agents start)

Before (main) After
before-workspace-card v3b-two-workspace-card

Agents list (same scenario)

Before (main) After
before-agents-list v3b-two-agents-list

Left pane names the tab first, then split, then the right agent starts (after)

v3b-late-workspace-card

Testing

  • I manually tested these changes locally (macOS; headless Electron e2e build, scenarios above)
  • Automated tests added/updated
    • agent-generated-tab-title-pane.test.ts: the writer records the source, a later sibling doesn't take it, and a dispatch replacement moves it.
    • workspace-session-schema.test.ts: the field persists, and a malformed value keeps the tab.
    • agent-row-conversation-name.test.ts, use-agent-row-conversation-name.test.ts, dashboard-card-labels.test.ts: the producer keeps the title, a sibling shows its own name, and an omitted pane keeps today's behavior.
    • activity-thread-display.test.ts: the Agents-list title.
    • mirrored-generated-title.test.ts: the web client keeps the source only under the same tab id.
    • Reverting the production files makes 8 new assertions fail across 7 test files. The mirrored-tab guard (a source recorded under another tab id is not copied) passes either way and pins the copy condition.
  • pnpm lint, pnpm typecheck and pnpm run build:desktop pass locally.
  • vitest over src/shared, lib, store, runtime, hooks and components/{dashboard,activity,sidebar}: 22,846 tests pass.

AI Disclosure

I used Claude Code (Claude Opus 5) to help investigate the bug, write the change and tests, and run adversarial reviews of the diff. I reviewed every change myself and verified it locally, including the e2e scenarios above.

Review

  • Cross-platform: shared/renderer logic only, no platform APIs.
  • SSH / remote / paired web: the field is persisted like generatedTitle. Old sessions and old clients keep today's behavior. There is no RPC or stream change.
  • Mobile / wire: no RPC, stream or runtime-graph payload changes. The only new data is the optional persisted field above, which older builds ignore, and conversationName keeps its shape and meaning. Mobile per-leaf surface titles are not changed here; the headless-host projection (buildHeadlessMobileSessionTerminalTabs) still uses the tab-level title.
  • Performance: one string comparison per row; no new selectors or subscriptions.
  • Security: none.

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance

Checklist

  • This PR is small and focused
  • I explained what changed and why (including ELI5)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…t produced it

The auto-generated tab title comes from one pane's agent prompt but is stored
tab-wide, so every agent row in a split tab showed it. Record the pane that
produced the title and let only that pane's rows use it: workspace-card and
dashboard rows, the board snapshot, and the Agents list. Titles saved before
the source was recorded keep today's behavior.
@coderabbitai

coderabbitai Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 49d2f284-0a84-409a-8fa8-f0d93b0c5d53

📥 Commits

Reviewing files that changed from the base of the PR and between caa465d and 8490b06.

📒 Files selected for processing (17)
  • src/renderer/src/components/dashboard/dashboard-card-labels.test.ts
  • src/renderer/src/components/dashboard/dashboard-card-labels.ts
  • src/renderer/src/components/dashboard/use-agent-row-conversation-name.test.ts
  • src/renderer/src/components/dashboard/use-agent-row-conversation-name.ts
  • src/renderer/src/lib/activity-thread-display.test.ts
  • src/renderer/src/lib/activity-thread-display.ts
  • src/renderer/src/runtime/web-session-tabs-sync/mirrored-generated-title.test.ts
  • src/renderer/src/runtime/web-session-tabs-sync/state-equality-tabs.ts
  • src/renderer/src/runtime/web-session-tabs-sync/terminal-build.ts
  • src/renderer/src/store/slices/agent-generated-tab-title-pane.test.ts
  • src/renderer/src/store/slices/terminal-tab-title-batch.ts
  • src/shared/agent-row-conversation-name.test.ts
  • src/shared/agent-row-conversation-name.ts
  • src/shared/agent-tab-title.ts
  • src/shared/terminal-tab-types.ts
  • src/shared/workspace-session-schema.test.ts
  • src/shared/workspace-session-schema.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The change adds generatedTitlePaneKey to terminal tabs and persists valid values. Generated-title updates record the source pane, while mirrored-tab rebuilds preserve the key only for matching local tabs. Conversation-name and activity-thread resolution now receives pane context and uses a generated title only for its owning pane. Dashboard and agent-row callers pass that context. Tests cover ownership, persistence, synchronization, and split-pane display behavior.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 8490b

The pane-scoped title behavior is covered across persistence, synchronization, dashboard, agent-row, and activity-title paths. No actionable merge risk remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 17 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR meets the coding requirements in [#20856]. It records the source pane in generatedTitlePaneKey when it writes an auto-generated title. getAgentRowConversationName and `getActivityThreadTask…
Out of Scope Changes check ✅ Passed The changes stay within [#20856]. The new field, ownership helper, persistence handling, mirrored-tab handling, resolver updates, and related tests directly support pane-scoped generated titles and ba…
Title check ✅ Passed The title clearly and concisely describes the main change: limiting a split tab's generated title to the pane that produced it.
Description check ✅ Passed The description follows the repository template and includes the ELI5, change summary, rationale, linked issue, visual proof, testing, AI disclosure, compatibility notes, and checklist. It reports tar…
  • Fix all pre-merge checks with AI

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.

@pullfrog pullfrog 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.

ℹ️ No critical issues — one scope question and a small test nitpick.

Reviewed changes

  • Source pane recorded on the tab — TerminalTab.generatedTitlePaneKey is written alongside generatedTitle in applyGeneratedTabTitleUpdates, persisted through terminalTabSchema with .catch(undefined), and compared by terminalTabEqual.
  • Single ownership rule — isGeneratedTabTitleOwnedByPane in shared/agent-tab-title.ts gates the generated title on the row's pane key, falling back to tab-wide for untagged titles or callers that pass no pane.
  • Applied at both row producers — getAgentRowConversationName (worktree-card rows, dashboard rows, board snapshot) and getActivityThreadTaskTitle (Agents list, Activity page) now pass the row's paneKey.
  • Paired web client — buildMirroredTerminalTabs carries the client-local source pane only when the existing tab id matches the mirrored local id, and terminalTabEqual compares the new field.

I verified the tests actually pin the behavior: all 7 changed test files pass (85 tests) with the change, and reverting the 10 non-test files makes 8 new assertions fail across 7 files. tc:web passes, and the related activity / web-session-tabs-sync / generated-title / dashboard suites pass (256 tests).

ℹ️ Headless mobile projection still stamps the tab's generated title on every leaf

The PR body says "the host transport never carried generatedTitle", but the headless path does carry it. buildHeadlessMobileSessionTerminalTabs emits one surface per leaf and sets each leaf's title to tab.generatedTitle, so a headless-host split tab still shows one pane's generated title on every leaf — the same class of mislabel this PR fixes for rows. The renderer publication path deliberately scopes the tab-wide title to the launch-agent leaf only, so the two paths disagree.

Technical details
# Headless mobile projection applies the tab's generated title to every leaf

## Affected sites
- `src/main/runtime/mobile-session-terminal-projection.ts:26-31` — `title = customTitle || generatedTitle || title || defaultTitle` is computed per leaf, so all leaves of a split tab get the tab's `generatedTitle`.
- Contrast `src/renderer/src/runtime/sync-runtime-graph/mobile-session-terminal-tabs.ts:78-86` — the renderer path gates the tab-wide title on `tabWideFallbackSafe` (`launchAgentLeafId === leafId`), so only the launch-agent leaf receives it.

## Required outcome
- Decide whether a headless-host mobile surface should follow the same pane ownership as the desktop rows. If yes, the projection needs the leaf id (`tab.id::leafId` already carries it) compared against `generatedTitlePaneKey`; if no, the PR description's "host transport never carried generatedTitle" claim should be corrected.

## Open questions for the human
- Is the mobile per-leaf surface intentionally tab-wide (mirroring the tab strip), or should it match the row behavior this PR establishes?

ℹ️ Nitpicks

  • The second assertion in mirrored-generated-title.test.ts ("drops a source pane recorded under another tab id so the title is not hidden") passes without the production change — when the field does not exist, no pane key is ever carried. It still guards against an unconditional copy, but the PR's "every new assertion fails without the production change" claim does not hold for it.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@bokeeeey

Copy link
Copy Markdown
Author

Thanks for the review.

On the headless projection: buildHeadlessMobileSessionTerminalTabs gives every leaf a tab-level name in general, not just generatedTitle. It also falls back to tab.title, which is the focused pane's title, and it doesn't read tabAutoGenerateTitle. Scoping only the generated title there wouldn't make headless leaves pane-accurate, so I kept this PR to the row resolvers and corrected the description, which now notes the headless projection is unchanged. Happy to follow up separately if per-pane titles are wanted there.

On the nitpick: agreed, that guard passes without the change. I've reworded the testing note.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: In a split tab, one agent pane's auto-generated title names every agent row of that tab

1 participant