fix(quick-pane): load fresh categorized models
This commit is contained in:
@@ -134,6 +134,41 @@ function bridgeMock(
|
||||
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() {
|
||||
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 () => {
|
||||
let runtimeCount = 0;
|
||||
let closeAttempts = 0;
|
||||
|
||||
@@ -311,9 +311,17 @@ export default function QuickPane() {
|
||||
[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 current = runtimeRef.current;
|
||||
if (!current || requestActiveRef.current) return true;
|
||||
if (!current || requestActiveRef.current || submissionStartingRef.current)
|
||||
return true;
|
||||
try {
|
||||
await closeRuntime(current.runtimeId);
|
||||
clearRuntimeIfCurrent(current.runtimeId);
|
||||
@@ -326,8 +334,9 @@ export default function QuickPane() {
|
||||
}, [clearRuntimeIfCurrent, closeRuntime]);
|
||||
|
||||
const dismissPane = useCallback(async () => {
|
||||
invalidateIdlePreparation();
|
||||
if (await closeIdleRuntime()) await invoke("dismiss_quick_pane");
|
||||
}, [closeIdleRuntime]);
|
||||
}, [closeIdleRuntime, invalidateIdlePreparation]);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
@@ -420,7 +429,8 @@ export default function QuickPane() {
|
||||
focusInput();
|
||||
if (!requestActiveRef.current && !submissionStartingRef.current)
|
||||
void prepareRuntime();
|
||||
} else if (!submissionStartingRef.current) {
|
||||
} else if (!requestActiveRef.current && !submissionStartingRef.current) {
|
||||
invalidateIdlePreparation();
|
||||
void closeIdleRuntime();
|
||||
}
|
||||
});
|
||||
@@ -429,7 +439,12 @@ export default function QuickPane() {
|
||||
void settingsListener.then((unlisten) => unlisten());
|
||||
void focusListener.then((unlisten) => unlisten());
|
||||
};
|
||||
}, [closeIdleRuntime, dismissPane, prepareRuntime]);
|
||||
}, [
|
||||
closeIdleRuntime,
|
||||
dismissPane,
|
||||
invalidateIdlePreparation,
|
||||
prepareRuntime,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!runtime || !requestActive) return;
|
||||
|
||||
Reference in New Issue
Block a user