fix(quick-pane): recover catalog after bridge restarts

Treat missing stale runtimes as already closed and defer thinking reconciliation until the refreshed model catalog is ready.
This commit is contained in:
2026-09-02 10:07:04 +02:00
parent 56fe4e734d
commit 636ef5dcc2
2 changed files with 51 additions and 1 deletions
+46
View File
@@ -459,6 +459,52 @@ describe("QuickPane", () => {
); );
}); });
it("recovers a stale Quick runtime and keeps persisted thinking after bridge restart", async () => {
const thinkingModels: Model[] = [
{
provider: "provider",
id: "quick/model",
reasoning: true,
thinkingLevelMap: { high: "high" },
},
];
const persistedSettings = {
...settings,
defaultThinkingLevel: "high",
};
const tracker = bridgeMock([thinkingModels, thinkingModels], undefined, {
...uiState,
quickPane: persistedSettings,
});
const invoke = mocks.invoke.getMockImplementation();
mocks.invoke.mockImplementation(
(command: string, payload?: { runtimeId?: string }) => {
if (
command === "close_quick_runtime" &&
payload?.runtimeId === "runtime-1"
)
return Promise.reject(
new Error("unknown session runtime: stale-runtime-id"),
);
return invoke?.(command, payload);
},
);
render(<QuickPane />);
await preparedTrigger();
const settingsListener = mocks.listen.mock.calls.find(
([event]) => event === "quick-pane-settings-saved",
)?.[1] as ((event: { payload: QuickPaneSettings }) => void) | undefined;
expect(settingsListener).toBeTypeOf("function");
await act(async () => settingsListener?.({ payload: persistedSettings }));
await waitFor(() => expect(tracker.runtimeCount()).toBe(2));
expect(screen.queryByText(/unknown session runtime/)).toBeNull();
expect(screen.getByRole("combobox", { name: "Thinking" })).toHaveValue(
"high",
);
});
it("derives thinking choices from selected model metadata and falls back to Off", async () => { it("derives thinking choices from selected model metadata and falls back to Off", async () => {
const thinkingModels: Model[] = [ const thinkingModels: Model[] = [
{ {
+5 -1
View File
@@ -341,10 +341,11 @@ export default function QuickPane() {
!!extension; !!extension;
useEffect(() => { useEffect(() => {
if (catalogStatus !== "ready") return;
setThinkingLevel((current) => setThinkingLevel((current) =>
reconcileThinkingLevel(selectedCatalogModel, current), reconcileThinkingLevel(selectedCatalogModel, current),
); );
}, [selectedCatalogModel]); }, [catalogStatus, selectedCatalogModel]);
const recordInputSelection = (input: HTMLTextAreaElement) => { const recordInputSelection = (input: HTMLTextAreaElement) => {
if ( if (
@@ -468,6 +469,9 @@ export default function QuickPane() {
cleanupQueueRef.current.add(runtimeId); cleanupQueueRef.current.add(runtimeId);
const cleanup = invoke("close_quick_runtime", { runtimeId }) const cleanup = invoke("close_quick_runtime", { runtimeId })
.catch((reason) => {
if (!String(reason).includes("unknown session runtime:")) throw reason;
})
.then(() => { .then(() => {
cleanupQueueRef.current.delete(runtimeId); cleanupQueueRef.current.delete(runtimeId);
}) })