fix: restore Changes focus on task return (#2091)

This commit is contained in:
Carlos Florêncio
2026-07-31 13:26:20 +01:00
committed by GitHub
parent d46ec16770
commit a8af0787b1
6 changed files with 261 additions and 29 deletions
@@ -179,6 +179,7 @@ describe("applyChangesPanelAutoFocusState fallback session keys", () => {
previousMarkers,
pendingEnvKeys,
isRestoringLayout: false,
isMaximized: false,
activate: () => {
activateCalls += 1;
return "activated";
@@ -207,6 +208,7 @@ describe("applyChangesPanelAutoFocusState fallback session keys", () => {
previousMarkers,
pendingEnvKeys,
isRestoringLayout: false,
isMaximized: false,
activate: () => {
activateCalls += 1;
return "activated";
@@ -223,8 +225,8 @@ describe("applyChangesPanelAutoFocusState fallback session keys", () => {
});
});
describe("applyChangesPanelAutoFocusState saved layouts", () => {
it("keeps a returning environment's saved active panel over pending changes", () => {
describe("applyChangesPanelAutoFocusState returning environments", () => {
it("activates pending changes when returning from another environment", () => {
const previousMarkers = {
envB: { count: 1, fingerprint: "b1" },
};
@@ -240,17 +242,75 @@ describe("applyChangesPanelAutoFocusState saved layouts", () => {
environmentIdBySessionId: {},
previousMarkers,
pendingEnvKeys,
hasSavedLayout: true,
isRestoringLayout: false,
isMaximized: false,
activate: () => {
activateCalls += 1;
return "activated";
},
});
expect(activateCalls).toBe(0);
expect(activateCalls).toBe(1);
expect(pendingEnvKeys.size).toBe(0);
});
it("keeps pending changes while maximized and activates them after exit", () => {
const previousMarkers = {
envB: { count: 1, fingerprint: "b1" },
};
const pendingEnvKeys = new Set(["envB"]);
let activateCalls = 0;
const baseArgs = {
signalsByEnv: {
envB: signal(1, "b1"),
},
activeEnvKey: "envB",
previousActiveEnvKey: "envA",
environmentIdBySessionId: {},
previousMarkers,
pendingEnvKeys,
isRestoringLayout: false,
};
applyChangesPanelAutoFocusState({
...baseArgs,
isMaximized: true,
activate: () => {
activateCalls += 1;
return "no-panel" as const;
},
});
const whileMaximized = {
activateCalls,
pendingEnvKeys: [...pendingEnvKeys],
};
applyChangesPanelAutoFocusState({
...baseArgs,
isMaximized: false,
activate: () => {
activateCalls += 1;
return "activated" as const;
},
});
expect({
whileMaximized,
afterExit: {
activateCalls,
pendingEnvKeys: [...pendingEnvKeys],
},
}).toEqual({
whileMaximized: {
activateCalls: 1,
pendingEnvKeys: ["envB"],
},
afterExit: {
activateCalls: 2,
pendingEnvKeys: [],
},
});
});
});
describe("applyChangesPanelAutoFocusState", () => {
@@ -270,6 +330,7 @@ describe("applyChangesPanelAutoFocusState", () => {
previousMarkers,
pendingEnvKeys,
isRestoringLayout: false,
isMaximized: false,
activate: () => {
activateCalls += 1;
return "activated";
@@ -288,6 +349,7 @@ describe("applyChangesPanelAutoFocusState", () => {
previousMarkers,
pendingEnvKeys,
isRestoringLayout: false,
isMaximized: false,
activate: () => {
activateCalls += 1;
return "activated";
@@ -309,6 +371,7 @@ describe("applyChangesPanelAutoFocusState", () => {
previousMarkers,
pendingEnvKeys,
isRestoringLayout: true,
isMaximized: false,
activate: () => {
activateCalls += 1;
return "activated";
@@ -328,6 +391,7 @@ describe("applyChangesPanelAutoFocusState", () => {
previousMarkers,
pendingEnvKeys,
isRestoringLayout: false,
isMaximized: false,
activate: () => {
activateCalls += 1;
return "activated";
@@ -480,6 +544,7 @@ describe("applyChangesPanelAutoFocusState restore races", () => {
previousMarkers,
pendingEnvKeys,
isRestoringLayout: false,
isMaximized: false,
getIsRestoringLayout: () => restoring,
activate: () => {
restoring = true;
+11 -20
View File
@@ -7,7 +7,6 @@ import { useAppStore } from "@/components/state-provider";
import { useDockviewStore } from "@/lib/state/dockview-store";
import type { AppState } from "@/lib/state/store";
import type { FileInfo, GitStatusEntry } from "@/lib/state/slices/session-runtime/types";
import { getEnvLayout } from "@/lib/local-storage";
import { djb2Hash } from "@/lib/utils/hash";
type DockviewPanel = NonNullable<ReturnType<DockviewApi["getPanel"]>>;
@@ -251,9 +250,10 @@ export function applyChangesPanelAutoFocusState(args: {
environmentIdBySessionId: Record<string, string>;
previousMarkers: Record<string, ChangesMarker>;
pendingEnvKeys: Set<string>;
hasSavedLayout?: boolean;
isRestoringLayout: boolean;
getIsRestoringLayout?: () => boolean;
isMaximized: boolean;
getIsMaximized?: () => boolean;
activate: () => ActivateChangesPanelResult;
}): string | null {
const {
@@ -263,9 +263,10 @@ export function applyChangesPanelAutoFocusState(args: {
environmentIdBySessionId,
previousMarkers,
pendingEnvKeys,
hasSavedLayout = false,
isRestoringLayout,
getIsRestoringLayout,
isMaximized,
getIsMaximized,
activate,
} = args;
@@ -288,17 +289,13 @@ export function applyChangesPanelAutoFocusState(args: {
});
const isCurrentlyRestoringLayout = () => isRestoringLayout || getIsRestoringLayout?.() === true;
const isCurrentlyMaximized = () => isMaximized || getIsMaximized?.() === true;
// A saved env layout records the user's active panel. Pending Changes
// attention may choose the first panel for an unseen env, but must not
// override that explicit focus when the user returns to a task.
if (activeEnvKey && hasSavedLayout) {
pendingEnvKeys.delete(activeEnvKey);
} else if (activeEnvKey && !isCurrentlyRestoringLayout() && pendingEnvKeys.has(activeEnvKey)) {
if (activeEnvKey && !isCurrentlyRestoringLayout() && pendingEnvKeys.has(activeEnvKey)) {
const result = activate();
if (
shouldClearPendingChangesFocus(result) &&
!(result === "no-panel" && isCurrentlyRestoringLayout())
!(result === "no-panel" && (isCurrentlyRestoringLayout() || isCurrentlyMaximized()))
) {
pendingEnvKeys.delete(activeEnvKey);
}
@@ -310,12 +307,12 @@ export function applyChangesPanelAutoFocusState(args: {
export function useChangesPanelAutoFocus(activeEnvKey: string | null) {
const api = useDockviewStore((s) => s.api);
const isRestoringLayout = useDockviewStore((s) => s.isRestoringLayout);
const isMaximized = useDockviewStore((s) => s.preMaximizeLayout !== null);
const signalsByEnv = useAppStore(useShallow(selectChangesSignalByEnvironment));
const environmentIdBySessionId = useAppStore((state) => state.environmentIdBySessionId);
const previousMarkersRef = useRef<Record<string, ChangesMarker>>({});
const pendingEnvKeysRef = useRef<Set<string>>(new Set());
const previousActiveEnvKeyRef = useRef<string | null>(null);
const hasSavedLayout = activeEnvKey !== null && getEnvLayout(activeEnvKey) !== null;
useEffect(() => {
previousActiveEnvKeyRef.current = applyChangesPanelAutoFocusState({
@@ -325,17 +322,11 @@ export function useChangesPanelAutoFocus(activeEnvKey: string | null) {
environmentIdBySessionId,
previousMarkers: previousMarkersRef.current,
pendingEnvKeys: pendingEnvKeysRef.current,
hasSavedLayout,
isRestoringLayout,
getIsRestoringLayout: () => useDockviewStore.getState().isRestoringLayout,
isMaximized,
getIsMaximized: () => useDockviewStore.getState().preMaximizeLayout !== null,
activate: () => activateChangesPanel(api),
});
}, [
signalsByEnv,
activeEnvKey,
api,
isRestoringLayout,
environmentIdBySessionId,
hasSavedLayout,
]);
}, [signalsByEnv, activeEnvKey, api, isRestoringLayout, isMaximized, environmentIdBySessionId]);
}