Skip to content

Clicking a file in the Changes sidebar opens a split pane even when Changes is set to open in a new tab #9039

Open

Description

@videvian

Where did this happen?

Desktop app (macOS)

Version

2.0.0

What happened?

I set Settings → Behavior → Changes open target to "New tab". The Changes button in the top bar respects it and opens the diff in its own tab. Clicking a specific file in the right sidebar's Changes list does not: it opens the diff as a split pane next to the agent session, so I move it to its own tab by hand every time.

Settings → Files → Sidebar file rows doesn't help either. "Open in tab" also opens a split, and "Open in new tab" opens a new tab for every file I click.

Expected: with "New tab" set, clicking a file in the Changes list opens the diff in a Changes tab, or reuses the one already open.

Steps to reproduce

  1. Settings → Behavior → Changes open target: "New tab".
  2. Open a workspace with one agent session in the active tab and no diff open.
  3. In the right sidebar's Changes tab, click a changed file.
  4. The diff opens as a split pane next to the session instead of in a new tab.

Logs, screenshots, or recordings

Where it opens on the file click

Image

Where I want it to be

Image

Activity

  1. github-actions commented on Oct 29, 2026

    @github-actions
    Contributor

    The setting is ignored on this path. Clicking a file in the Changes sidebar goes to openDiffPane, and that function never reads "Changes open target". Only the top-bar Changes button reads it.

    Summary

    With Settings → Behavior → Changes open target set to "New tab", the top-bar Changes button opens or reuses a diff tab as expected. A plain click on a file in the right sidebar's Changes list does not: when no diff pane exists yet, it opens a split next to the active session. The "Sidebar file rows" setting can't work around this. Its "pane" action becomes a diff with openInNewTab = false, which ends up as the same split. Its "newTab" action becomes diffNewTab, which always adds a new tab and never reuses one. The report matches the code.

    Affected code

    • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/hooks/useWorkspacePaneOpeners/useWorkspacePaneOpeners.ts:216: openChangesPane (top-bar button) passes useSettings.getState().changesOpenTarget to openChangesPaneInStore. This is the only place the setting is read.
    • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/utils/openChangesPaneInStore/openChangesPaneInStore.ts:40-47: for "tab", it focuses the first diff pane in tab order, or calls addTab if there is none.
    • useWorkspacePaneOpeners.ts:68-136: openDiffPane, which handles the sidebar file click. It has three branches:
      • openInNewTab → addTab every time (L85-99).
      • Otherwise, reuse the first diff pane in any tab (L101-122).
      • If there is none, state.openPane(...) (L123-133), which splits the active tab's center area.
    • apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/$workspaceId/page.tsx:673: onSelectDiffFile={openDiffPane}.
    • .../WorkspaceSidebar/hooks/useChangesTab/components/ChangesFileList/components/ChangesTreeView/ChangesTreeView.tsx:251-260 (Pierre onSelect, always false) and :280-289 (onSelectDiff from the click policy).
    • apps/desktop/src/renderer/lib/clickPolicy/policies/changesSidebarFilePolicy.ts:20-28: maps the "Sidebar file rows" policy (useSidebarFilePolicy) to intents. newTab → diffNewTab; a plain click with pane → diff.
    • apps/desktop/src/renderer/lib/clickPolicy/usePierreChangesSidebarRowClickPolicy.ts:66-67: diffNewTab → onSelectDiff(path, true), diff → onSelectDiff(path, false).

    Suspected root cause

    The setting was added only to the "open Changes" entry point (openChangesPaneInStore). The per-file path (openDiffPane) was not updated. In the reported case there is no diff pane yet, so the reuse loop finds nothing and the code falls through to state.openPane(...) at useWorkspacePaneOpeners.ts:123. That creates a split, whatever the setting says. If a diff tab is already open, the reuse loop at L101-122 already does what the user expects. So the bug only shows on the first file click.

    The "Sidebar file rows" setting is a separate click policy. It has no option meaning "reuse the Changes tab", so it can't fix this. Its "Open in new tab" calls addTab unconditionally (L85-99), which is why every click makes a new tab.

    Reproduction

    1. Settings → Behavior → Changes open target: "New tab". Leave "Sidebar file rows" at its default.
    2. Open a workspace whose active tab holds only an agent session, with no diff pane in any tab.
    3. Click a changed file in the right sidebar's Changes list. The diff opens as a split in the active tab.
    4. Compare with the top-bar Changes button from the same state. It opens a dedicated tab.

    Sketch for a bun:test: build a WorkspaceStore with one tab holding a terminal or chat pane. Run the no-existing-diff branch of openDiffPane (probably easiest after pulling it out into a pure openDiffPaneInStore(store, args, target) helper, like openChangesPaneInStore). Assert that with target = "tab" the store ends with 2 tabs and the new diff pane alone in the active tab. Today you'd get 1 tab with 2 panes.

    Fix direction

    • In openDiffPane, read useSettings.getState().changesOpenTarget. When it is "tab" and the reuse loop finds no diff pane, call state.addTab({ panes: [diffPane] }) instead of state.openPane(...). This matches openChangesPaneInStore. The best option is to move the logic into a shared store helper so the two entry points can't drift apart again.
    • Optional, product decision: under "tab", the sidebar's diffNewTab intent could also reuse the existing Changes tab instead of always adding one. That may break users who want one tab per file on purpose, so it's best decided separately.
    • Smaller mismatch in "pane" mode: openChangesPaneInStore only reuses a diff pane in the active tab (L27-36). openDiffPane reuses the first diff pane in any tab. A shared helper should settle which one is right.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions