Make the workspace right pane a first-class tab host (#3287)

* feat(app): add persistent workspace side panes

Keep the common review and file-browsing workflow beside the active agent without tearing down pane contents. Workspace tabs now share one compact chip language, assistant file links route into the retained right pane, and diff/file trees share stable geometry.

* fix(app): localize pull request empty state

* test(app): update explorer pane coverage

* fix(app): preserve explorer pane workflows

* test(app): make explorer tab selection idempotent

* fix(app): persist workspace review pins

* fix(app): preserve sidebar scrim drag hook

* fix(app): restore companion pane interactions

* test(app): follow selected sidebar surface
This commit is contained in:
Mohamed Boudra
2026-08-16 18:18:32 +02:00
committed by GitHub
parent 6dcf6a4b08
commit ab274d635e
81 changed files with 2900 additions and 1051 deletions
@@ -80,6 +80,19 @@ describe("working diff tab identity", () => {
});
});
describe("workspace utility panel identity", () => {
it.each(["files", "pull_request"] as const)(
"normalizes and deterministically keys %s",
(kind) => {
const target = { kind };
expect(normalizeWorkspaceTabTarget(target)).toEqual(target);
expect(buildDeterministicWorkspaceTabId(target)).toBe(kind);
expect(workspaceTabTargetsEqual(target, target)).toBe(true);
},
);
});
describe("commit diff tab identity", () => {
it("keys a commit diff tab by its sha", () => {
expect(buildDeterministicWorkspaceTabId({ kind: "commit_diff", sha: "abc123" })).toBe(
@@ -49,6 +49,9 @@ function normalizeSimpleWorkspaceTabTarget(value: WorkspaceTabTarget): Workspace
const browserId = trimNonEmpty(value.browserId);
return browserId ? { kind: "browser", browserId } : null;
}
case "files":
case "pull_request":
return { kind: value.kind };
case "setup": {
const workspaceId = trimNonEmpty(value.workspaceId);
return workspaceId ? { kind: "setup", workspaceId } : null;
@@ -121,6 +124,12 @@ function secondaryWorkspaceTabTargetsEqual(
if (left.kind === "working_diff" && right.kind === "working_diff") {
return left.focusPath === right.focusPath && left.focusRequestId === right.focusRequestId;
}
if (left.kind === "files" && right.kind === "files") {
return true;
}
if (left.kind === "pull_request" && right.kind === "pull_request") {
return true;
}
if (left.kind === "setup" && right.kind === "setup") {
return left.workspaceId === right.workspaceId;
}
@@ -188,6 +197,9 @@ export function buildDeterministicWorkspaceTabId(target: WorkspaceTabTarget): st
if (target.kind === "working_diff") {
return "working_diff";
}
if (target.kind === "files" || target.kind === "pull_request") {
return target.kind;
}
return `file_${target.path}`;
}
+2
View File
@@ -22,6 +22,8 @@ export type WorkspaceTabTarget =
| { kind: "provider_subagent"; parentAgentId: string; subagentId: string }
| { kind: "terminal"; terminalId: string }
| { kind: "browser"; browserId: string }
| { kind: "files" }
| { kind: "pull_request" }
| WorkspaceFileTabTarget
| WorkspaceWorkingDiffTabTarget
| { kind: "setup"; workspaceId: string }