fix(quick-pane): load fresh categorized models

This commit is contained in:
2026-08-26 18:27:05 +02:00
parent ad66067a31
commit 05874a0be1
2 changed files with 160 additions and 4 deletions
+141
View File
@@ -134,6 +134,41 @@ function bridgeMock(
return { runtimeCount: () => runtimeCount }; return { runtimeCount: () => runtimeCount };
} }
function deferredCatalogBridge() {
const firstSnapshot = deferred<RuntimeSnapshot>();
const staleModels: Model[] = [
{ provider: "provider", id: "stale/model", name: "Stale" },
];
const freshModels: Model[] = [
{ provider: "provider", id: "fresh/model", name: "Fresh" },
];
let runtimeCount = 0;
mocks.invoke.mockImplementation(
(command: string, payload?: { runtimeId?: string }) => {
if (command === "load_ui_state") return Promise.resolve(uiState);
if (command === "create_quick_runtime") {
runtimeCount += 1;
return Promise.resolve({
runtime: {
agentId: `agent-${runtimeCount}`,
runtimeId: `runtime-${runtimeCount}`,
},
});
}
if (command === "get_session_runtime_snapshot") {
if (payload?.runtimeId === "runtime-1") return firstSnapshot.promise;
return Promise.resolve(runtimeSnapshot(freshModels));
}
return Promise.resolve(undefined);
},
);
return {
firstSnapshot,
staleModels,
runtimeCount: () => runtimeCount,
};
}
async function preparedTrigger() { async function preparedTrigger() {
return screen.findByRole("button", { name: /provider ·/i }); return screen.findByRole("button", { name: /provider ·/i });
} }
@@ -578,6 +613,112 @@ describe("QuickPane fresh runtime catalog", () => {
}); });
}); });
it.each([
{
action: "Close",
dismiss: async () => {
await userEvent.click(
screen.getByRole("button", { name: "Close Quick Pane" }),
);
},
},
{
action: "Escape",
dismiss: async () => {
fireEvent.keyDown(window, { key: "Escape" });
},
},
])(
"discards deferred catalog preparation after $action dismissal",
async ({ dismiss }) => {
const tracker = deferredCatalogBridge();
render(<QuickPane />);
await waitFor(() =>
expect(mocks.invoke).toHaveBeenCalledWith(
"get_session_runtime_snapshot",
{ runtimeId: "runtime-1" },
),
);
await dismiss();
await waitFor(() =>
expect(mocks.invoke).toHaveBeenCalledWith("dismiss_quick_pane"),
);
await act(async () =>
tracker.firstSnapshot.resolve(runtimeSnapshot(tracker.staleModels)),
);
await waitFor(() =>
expect(mocks.invoke).toHaveBeenCalledWith("close_quick_runtime", {
runtimeId: "runtime-1",
}),
);
expect(
screen.queryByRole("button", { name: /provider · Stale/i }),
).toBeNull();
await act(async () => mocks.focusHandler?.({ payload: true }));
expect(
await screen.findByRole("button", { name: /provider · Fresh/i }),
).toBeInTheDocument();
expect(tracker.runtimeCount()).toBe(2);
await ask();
await waitFor(() =>
expect(mocks.invoke).toHaveBeenCalledWith("set_model", {
agentId: "agent-2",
provider: "provider",
modelId: "fresh/model",
}),
);
expect(mocks.invoke).not.toHaveBeenCalledWith(
"set_model",
expect.objectContaining({ agentId: "agent-1" }),
);
},
);
it("discards deferred catalog preparation after idle focus loss", async () => {
const tracker = deferredCatalogBridge();
render(<QuickPane />);
await waitFor(() =>
expect(mocks.invoke).toHaveBeenCalledWith(
"get_session_runtime_snapshot",
{ runtimeId: "runtime-1" },
),
);
await act(async () => mocks.focusHandler?.({ payload: false }));
expect(mocks.invoke).not.toHaveBeenCalledWith("dismiss_quick_pane");
await act(async () =>
tracker.firstSnapshot.resolve(runtimeSnapshot(tracker.staleModels)),
);
await waitFor(() =>
expect(mocks.invoke).toHaveBeenCalledWith("close_quick_runtime", {
runtimeId: "runtime-1",
}),
);
expect(
screen.queryByRole("button", { name: /provider · Stale/i }),
).toBeNull();
await act(async () => mocks.focusHandler?.({ payload: true }));
expect(
await screen.findByRole("button", { name: /provider · Fresh/i }),
).toBeInTheDocument();
expect(tracker.runtimeCount()).toBe(2);
await ask();
await waitFor(() =>
expect(mocks.invoke).toHaveBeenCalledWith("set_model", {
agentId: "agent-2",
provider: "provider",
modelId: "fresh/model",
}),
);
expect(mocks.invoke).not.toHaveBeenCalledWith(
"set_model",
expect.objectContaining({ agentId: "agent-1" }),
);
});
it("retains a prior runtime when close fails and retries before replacement", async () => { it("retains a prior runtime when close fails and retries before replacement", async () => {
let runtimeCount = 0; let runtimeCount = 0;
let closeAttempts = 0; let closeAttempts = 0;
+19 -4
View File
@@ -311,9 +311,17 @@ export default function QuickPane() {
[clearRuntimeIfCurrent, closeRuntime], [clearRuntimeIfCurrent, closeRuntime],
); );
const invalidateIdlePreparation = useCallback(() => {
if (requestActiveRef.current || submissionStartingRef.current) return;
preparationGenerationRef.current += 1;
preparationLockRef.current = false;
catalogReadyRef.current = false;
}, []);
const closeIdleRuntime = useCallback(async () => { const closeIdleRuntime = useCallback(async () => {
const current = runtimeRef.current; const current = runtimeRef.current;
if (!current || requestActiveRef.current) return true; if (!current || requestActiveRef.current || submissionStartingRef.current)
return true;
try { try {
await closeRuntime(current.runtimeId); await closeRuntime(current.runtimeId);
clearRuntimeIfCurrent(current.runtimeId); clearRuntimeIfCurrent(current.runtimeId);
@@ -326,8 +334,9 @@ export default function QuickPane() {
}, [clearRuntimeIfCurrent, closeRuntime]); }, [clearRuntimeIfCurrent, closeRuntime]);
const dismissPane = useCallback(async () => { const dismissPane = useCallback(async () => {
invalidateIdlePreparation();
if (await closeIdleRuntime()) await invoke("dismiss_quick_pane"); if (await closeIdleRuntime()) await invoke("dismiss_quick_pane");
}, [closeIdleRuntime]); }, [closeIdleRuntime, invalidateIdlePreparation]);
useEffect(() => { useEffect(() => {
let active = true; let active = true;
@@ -420,7 +429,8 @@ export default function QuickPane() {
focusInput(); focusInput();
if (!requestActiveRef.current && !submissionStartingRef.current) if (!requestActiveRef.current && !submissionStartingRef.current)
void prepareRuntime(); void prepareRuntime();
} else if (!submissionStartingRef.current) { } else if (!requestActiveRef.current && !submissionStartingRef.current) {
invalidateIdlePreparation();
void closeIdleRuntime(); void closeIdleRuntime();
} }
}); });
@@ -429,7 +439,12 @@ export default function QuickPane() {
void settingsListener.then((unlisten) => unlisten()); void settingsListener.then((unlisten) => unlisten());
void focusListener.then((unlisten) => unlisten()); void focusListener.then((unlisten) => unlisten());
}; };
}, [closeIdleRuntime, dismissPane, prepareRuntime]); }, [
closeIdleRuntime,
dismissPane,
invalidateIdlePreparation,
prepareRuntime,
]);
useEffect(() => { useEffect(() => {
if (!runtime || !requestActive) return; if (!runtime || !requestActive) return;