Fix workspace composer focus routing

This commit is contained in:
Mohamed Boudra
2026-04-20 17:41:00 +07:00
parent 33ac657fed
commit 95c1d1fe0f
2 changed files with 32 additions and 8 deletions
+30 -6
View File
@@ -7,7 +7,7 @@ import { createRoot, type Root } from "react-dom/client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { afterEach, describe, expect, it, vi } from "vitest"; import { afterEach, describe, expect, it, vi } from "vitest";
import { DaemonClient } from "@server/client/daemon-client"; import { DaemonClient } from "@server/client/daemon-client";
import { PaneFocusProvider, PaneProvider } from "@/panels/pane-context"; import { PaneFocusProvider, PaneProvider, type PaneFocusContextValue } from "@/panels/pane-context";
import { agentPanelRegistration } from "@/panels/agent-panel"; import { agentPanelRegistration } from "@/panels/agent-panel";
import { useDraftStore } from "@/stores/draft-store"; import { useDraftStore } from "@/stores/draft-store";
import { useSessionStore, type Agent } from "@/stores/session-store"; import { useSessionStore, type Agent } from "@/stores/session-store";
@@ -39,6 +39,7 @@ const {
composerRenderCount, composerRenderCount,
composerUnmountCount, composerUnmountCount,
latestComposerCwd, latestComposerCwd,
latestComposerIsPaneFocused,
streamRenderCount, streamRenderCount,
latestStreamPermissionKeys, latestStreamPermissionKeys,
latestStreamText, latestStreamText,
@@ -66,6 +67,7 @@ const {
composerRenderCount: vi.fn(), composerRenderCount: vi.fn(),
composerUnmountCount: vi.fn(), composerUnmountCount: vi.fn(),
latestComposerCwd: { current: null as string | null }, latestComposerCwd: { current: null as string | null },
latestComposerIsPaneFocused: { current: null as boolean | null },
streamRenderCount: vi.fn(), streamRenderCount: vi.fn(),
latestStreamPermissionKeys: { current: [] as string[] }, latestStreamPermissionKeys: { current: [] as string[] },
latestStreamText: { current: null as string | null }, latestStreamText: { current: null as string | null },
@@ -166,7 +168,7 @@ vi.mock("@/hooks/use-archive-agent", () => ({
})); }));
vi.mock("@/components/composer", () => ({ vi.mock("@/components/composer", () => ({
Composer: ({ cwd }: { cwd: string }) => { Composer: ({ cwd, isPaneFocused }: { cwd: string; isPaneFocused: boolean }) => {
React.useEffect( React.useEffect(
() => () => { () => () => {
composerUnmountCount(); composerUnmountCount();
@@ -175,6 +177,7 @@ vi.mock("@/components/composer", () => ({
); );
composerRenderCount(); composerRenderCount();
latestComposerCwd.current = cwd; latestComposerCwd.current = cwd;
latestComposerIsPaneFocused.current = isPaneFocused;
return <div data-testid="composer">{cwd}</div>; return <div data-testid="composer">{cwd}</div>;
}, },
})); }));
@@ -244,7 +247,14 @@ function seedReadyAgent(agent: Agent = makeAgent()) {
store.setAgentAuthoritativeHistoryApplied("server", "agent", true); store.setAgentAuthoritativeHistoryApplied("server", "agent", true);
} }
async function renderAgentPanel(root: Root) { async function renderAgentPanel(
root: Root,
focus: PaneFocusContextValue = {
isWorkspaceFocused: true,
isPaneFocused: false,
isInteractive: false,
},
) {
const AgentPanel = agentPanelRegistration.component; const AgentPanel = agentPanelRegistration.component;
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
@@ -267,9 +277,7 @@ async function renderAgentPanel(root: Root) {
openFileInWorkspace: vi.fn(), openFileInWorkspace: vi.fn(),
}} }}
> >
<PaneFocusProvider <PaneFocusProvider value={focus}>
value={{ isWorkspaceFocused: true, isPaneFocused: false, isInteractive: false }}
>
<AgentPanel /> <AgentPanel />
</PaneFocusProvider> </PaneFocusProvider>
</PaneProvider> </PaneProvider>
@@ -355,6 +363,7 @@ describe("AgentPanel render isolation", () => {
composerRenderCount.mockClear(); composerRenderCount.mockClear();
composerUnmountCount.mockClear(); composerUnmountCount.mockClear();
latestComposerCwd.current = null; latestComposerCwd.current = null;
latestComposerIsPaneFocused.current = null;
streamRenderCount.mockClear(); streamRenderCount.mockClear();
latestStreamPermissionKeys.current = []; latestStreamPermissionKeys.current = [];
latestStreamText.current = null; latestStreamText.current = null;
@@ -379,6 +388,21 @@ describe("AgentPanel render isolation", () => {
expect(composerRenderCount).toHaveBeenCalledTimes(composerBaseline); expect(composerRenderCount).toHaveBeenCalledTimes(composerBaseline);
}); });
it("does not advertise the composer as focused when its workspace is hidden", async () => {
seedReadyAgent();
container = document.createElement("div");
document.body.appendChild(container);
root = createRoot(container);
await renderAgentPanel(root, {
isWorkspaceFocused: false,
isPaneFocused: true,
isInteractive: false,
});
expect(latestComposerIsPaneFocused.current).toBe(false);
});
it("still invokes Composer for current-agent cwd changes and unmounts it for archives", async () => { it("still invokes Composer for current-agent cwd changes and unmounts it for archives", async () => {
seedReadyAgent(); seedReadyAgent();
container = document.createElement("div"); container = document.createElement("div");
+2 -2
View File
@@ -113,7 +113,7 @@ function useAgentPanelDescriptor(
function AgentPanel() { function AgentPanel() {
const { serverId, target, openFileInWorkspace } = usePaneContext(); const { serverId, target, openFileInWorkspace } = usePaneContext();
const { isPaneFocused } = usePaneFocus(); const { isInteractive } = usePaneFocus();
invariant(target.kind === "agent", "AgentPanel requires agent target"); invariant(target.kind === "agent", "AgentPanel requires agent target");
function openWorkspaceFile(input: { filePath: string }) { function openWorkspaceFile(input: { filePath: string }) {
@@ -126,7 +126,7 @@ function AgentPanel() {
<AgentPanelContent <AgentPanelContent
serverId={serverId} serverId={serverId}
agentId={target.agentId} agentId={target.agentId}
isPaneFocused={isPaneFocused} isPaneFocused={isInteractive}
onOpenWorkspaceFile={handleOpenWorkspaceFile} onOpenWorkspaceFile={handleOpenWorkspaceFile}
/> />
); );