feat(app): polish workspace tabs and changes

Size tabs from rendered labels and switch to horizontal overflow once minimum click targets are reached, keeping the add-tab action adjacent until it must remain fixed.

Make Changes an always-expanded document with an optional desktop navigation tree, keep Git actions in their platform-owned headers, remove pinned and inline split shortcuts, and place review tabs beside Changes.
This commit is contained in:
Mohamed Boudra
2026-08-17 21:14:37 +02:00
parent 6d7606160e
commit 75adf53893
41 changed files with 929 additions and 1901 deletions
+1 -12
View File
@@ -79,7 +79,6 @@ export interface PanelState {
explorerTab: ExplorerTab;
explorerTabByCheckout: Record<string, ExplorerTab>;
expandedPathsByWorkspace: Record<string, string[]>;
diffExpandedPathsByWorkspace: Record<string, string[]>;
// Changes-view folder tree. Inverted semantics vs the fields above:
// this stores COLLAPSED directory paths (empty = all folders expanded), keyed
// by full uncompressed dir path, so folders default to expanded and new
@@ -111,7 +110,6 @@ export interface PanelState {
setExplorerTab: (tab: ExplorerTab) => void;
setExplorerTabForCheckout: (params: ExplorerCheckoutContext & { tab: ExplorerTab }) => void;
setExpandedPathsForWorkspace: (workspaceKey: string, paths: ExpandedPathsUpdate) => void;
setDiffExpandedPathsForWorkspace: (workspaceKey: string, paths: string[]) => void;
setDiffCollapsedFoldersForWorkspace: (workspaceKey: string, dirPaths: string[]) => void;
activateExplorerTabForCheckout: (checkout: ExplorerCheckoutContext) => void;
setSidebarWidth: (width: number) => void;
@@ -148,7 +146,6 @@ export const usePanelStore = create<PanelState>()(
explorerTab: "changes",
explorerTabByCheckout: {},
expandedPathsByWorkspace: {},
diffExpandedPathsByWorkspace: {},
diffCollapsedFoldersByWorkspace: {},
sidebarWidth: DEFAULT_SIDEBAR_WIDTH,
explorerWidth: DEFAULT_EXPLORER_SIDEBAR_WIDTH,
@@ -276,13 +273,6 @@ export const usePanelStore = create<PanelState>()(
},
};
}),
setDiffExpandedPathsForWorkspace: (workspaceKey, paths) =>
set((state) => ({
diffExpandedPathsByWorkspace: {
...state.diffExpandedPathsByWorkspace,
[workspaceKey]: paths,
},
})),
setDiffCollapsedFoldersForWorkspace: (workspaceKey, dirPaths) =>
set((state) => ({
diffCollapsedFoldersByWorkspace: {
@@ -308,7 +298,7 @@ export const usePanelStore = create<PanelState>()(
}),
{
name: "panel-state",
version: 13,
version: 14,
storage: createValidatedPersistStorage(AsyncStorage, PanelPersistedStateSchema),
migrate: (persistedState, version) => migratePanelState(persistedState, version, { isWeb }),
partialize: (state) => ({
@@ -316,7 +306,6 @@ export const usePanelStore = create<PanelState>()(
explorerTab: state.explorerTab,
explorerTabByCheckout: state.explorerTabByCheckout,
expandedPathsByWorkspace: state.expandedPathsByWorkspace,
diffExpandedPathsByWorkspace: state.diffExpandedPathsByWorkspace,
diffCollapsedFoldersByWorkspace: state.diffCollapsedFoldersByWorkspace,
sidebarWidth: state.sidebarWidth,
explorerWidth: state.explorerWidth,
@@ -120,6 +120,14 @@ describe("panel-store migration", () => {
expect(state.diffCollapsedFoldersByWorkspace).toEqual({ ws: ["src/app"] });
});
it("drops the retired per-file diff expansion state", () => {
const state = migratePanelState({ diffExpandedPathsByWorkspace: { ws: ["src/app.ts"] } }, 13, {
isWeb: false,
});
expect(state.diffExpandedPathsByWorkspace).toBeUndefined();
});
it("replaces the dead explorer split ratio with the shared tree rail width", () => {
const state = migratePanelState({ explorerFilesSplitRatio: 0.5 }, 12, { isWeb: false });
+2 -7
View File
@@ -183,6 +183,7 @@ export const PanelPersistedStateSchema = z.strictObject({
explorerTab: ExplorerTabSchema.optional(),
explorerTabByCheckout: z.record(z.string(), ExplorerTabSchema).optional(),
expandedPathsByWorkspace: z.record(z.string(), z.array(z.string())).optional(),
// Accepted only so migration can discard the former per-file diff expansion state.
diffExpandedPathsByWorkspace: z.record(z.string(), z.array(z.string())).optional(),
diffCollapsedFoldersByWorkspace: z.record(z.string(), z.array(z.string())).optional(),
sidebarWidth: z.number().optional(),
@@ -290,13 +291,7 @@ export function migratePanelState(
) {
state.expandedPathsByWorkspace = {};
}
if (
version < 10 ||
typeof state.diffExpandedPathsByWorkspace !== "object" ||
!state.diffExpandedPathsByWorkspace
) {
state.diffExpandedPathsByWorkspace = {};
}
delete state.diffExpandedPathsByWorkspace;
if (
version < 12 ||
typeof state.diffCollapsedFoldersByWorkspace !== "object" ||
@@ -423,6 +423,25 @@ describe("workspace-layout-store actions", () => {
expect(findPaneContainingTab(layout.root, "pull_request")?.id).toBe(explorerPaneId);
});
it("places an auto-added pull request in the pane containing Changes", () => {
const workspaceKey = createWorkspaceKey();
const store = workspaceLayoutStore.getState();
store.openTabFocused(workspaceKey, { kind: "working_diff" });
useWorkspaceLayoutIds("explorer");
const explorerPaneId = store.splitPaneEmpty(workspaceKey, {
targetPaneId: "main",
position: "right",
});
store.setExplorerPaneId(workspaceKey, explorerPaneId);
store.observePullRequest(workspaceKey, "url:https://example.test/pulls/1");
const layout = workspaceLayoutStore.getState().layoutByWorkspace[workspaceKey];
expect(findPaneContainingTab(layout.root, "working_diff")?.id).toBe("main");
expect(findPaneContainingTab(layout.root, "pull_request")?.id).toBe("main");
});
it("opens assistant files in an ensured explorer pane and reveals it on the next open", () => {
const workspaceKey = createWorkspaceKey();
const store = workspaceLayoutStore.getState();
@@ -521,10 +521,17 @@ export function createWorkspaceLayoutStore(
}
const layout = getWorkspaceLayout(state.layoutByWorkspace, normalizedWorkspaceKey);
const workingDiffTab = collectAllTabs(layout.root).find(
(tab) => tab.target.kind === "working_diff",
);
const workingDiffPane = workingDiffTab
? findPaneContainingTab(layout.root, workingDiffTab.tabId)
: null;
const explorerPaneId = state.explorerPaneIdByWorkspace[normalizedWorkspaceKey] ?? null;
const explorerPane = findPaneById(layout.root, explorerPaneId);
const placementLayout = explorerPane
? (focusPaneInLayout({ layout, paneId: explorerPane.id }) ?? layout)
const placementPane = workingDiffPane ?? explorerPane;
const placementLayout = placementPane
? (focusPaneInLayout({ layout, paneId: placementPane.id }) ?? layout)
: layout;
const result = openTabInLayoutFocused({
layout: placementLayout,