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
+65
View File
@@ -0,0 +1,65 @@
import { Text, View } from "react-native";
import { FolderTree } from "lucide-react-native";
import { withUnistyles } from "react-native-unistyles";
import { useCallback } from "react";
import { useTranslation } from "react-i18next";
import invariant from "tiny-invariant";
import { FileExplorerPane } from "@/components/file-explorer-pane";
import { usePaneContext } from "@/panels/pane-context";
import type { PanelRegistration } from "@/panels/panel-registry";
import { useAddFileToChat } from "@/panels/use-add-file-to-chat";
import { useWorkspaceDirectory } from "@/stores/session-store-hooks";
const ThemedFolderTree = withUnistyles(FolderTree);
const CENTERED_PADDED_STYLE = {
flex: 1,
alignItems: "center",
justifyContent: "center",
padding: 16,
} as const;
function useFilesPanelDescriptor() {
const { t } = useTranslation();
return {
label: t("panels.files.label"),
subtitle: t("panels.files.subtitle"),
tooltip: t("panels.files.tooltip"),
titleState: "ready" as const,
icon: ThemedFolderTree,
statusBucket: null,
};
}
function FilesPanel() {
const { t } = useTranslation();
const { serverId, workspaceId, target, retargetCurrentTab } = usePaneContext();
const workspaceRoot = useWorkspaceDirectory(serverId, workspaceId);
const { addFile, canAddToChat } = useAddFileToChat({ serverId, workspaceId });
invariant(target.kind === "files", "FilesPanel requires files target");
const onOpenFile = useCallback(
(path: string) => retargetCurrentTab({ kind: "file", path }),
[retargetCurrentTab],
);
if (!workspaceRoot) {
return (
<View style={CENTERED_PADDED_STYLE}>
<Text>{t("panels.file.directoryMissing")}</Text>
</View>
);
}
return (
<FileExplorerPane
serverId={serverId}
workspaceId={workspaceId}
workspaceRoot={workspaceRoot}
onOpenFile={onOpenFile}
onAddToChat={canAddToChat ? addFile : undefined}
/>
);
}
export const filesPanelRegistration: PanelRegistration<"files"> = {
kind: "files",
component: FilesPanel,
useDescriptor: useFilesPanelDescriptor,
};