fix(quick-pane): restore model catalog controls

This commit is contained in:
2026-08-25 22:34:58 +02:00
parent ab482d3daa
commit 1b0b34fad8
10 changed files with 706 additions and 170 deletions
+80 -2
View File
@@ -7,16 +7,19 @@ import {
reconcileWorkspace,
workspaceReducer,
} from "./workspace/reducer";
import { modelIdentityFromValue, modelIdentityValue } from "./model-catalog";
const mocks = vi.hoisted(() => ({
invoke: vi.fn(),
loadSnapshot: vi.fn(),
refresh: vi.fn(),
dispatch: vi.fn(),
emitTo: vi.fn(),
useWorkspace: vi.fn(),
}));
vi.mock("@tauri-apps/api/core", () => ({ invoke: mocks.invoke }));
vi.mock("@tauri-apps/api/event", () => ({ emitTo: mocks.emitTo }));
vi.mock("@tauri-apps/api/window", () => ({
getCurrentWindow: () => ({ startDragging: vi.fn() }),
}));
@@ -87,10 +90,21 @@ function deferred<T>() {
return { promise, resolve, reject };
}
describe("model catalog identity", () => {
it("preserves model IDs containing slashes", () => {
const identity = {
provider: "openrouter",
modelId: "anthropic/claude/sonnet",
};
expect(modelIdentityFromValue(modelIdentityValue(identity))).toEqual(identity);
});
});
describe("App command progress and errors", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.invoke.mockResolvedValue(uiState);
mocks.emitTo.mockResolvedValue(undefined);
mocks.refresh.mockResolvedValue(undefined);
mocks.loadSnapshot.mockResolvedValue(true);
mocks.useWorkspace.mockReturnValue({
@@ -108,6 +122,69 @@ describe("App command progress and errors", () => {
expect(status).toHaveTextContent("Ready for your next prompt");
});
it("loads shared catalog selectors and derives providers from model choices", async () => {
const user = userEvent.setup();
const loaded: UiState = {
...uiState,
quickPane: {
workspace: "/repo",
provider: "openai",
model: "gpt-5",
followUpProvider: "openai",
followUpModel: "gpt-5",
instructions: "Stay concise.",
maximumSeconds: 60,
maximumToolCalls: 6,
},
};
mocks.invoke.mockImplementation((command: string) => {
if (command === "get_model_catalog")
return Promise.resolve({
models: [
{ provider: "openai", id: "gpt-5" },
{ provider: "openrouter", id: "anthropic/claude/sonnet" },
],
});
if (command === "load_ui_state") return Promise.resolve(loaded);
return Promise.resolve(undefined);
});
render(<App />);
await waitFor(() =>
expect(mocks.invoke).toHaveBeenCalledWith("get_model_catalog"),
);
await user.click(screen.getByRole("button", { name: "Controls" }));
const quickModel = screen.getByRole("combobox", { name: /Quick model/ });
expect(quickModel).toBeInTheDocument();
expect(
screen.getByRole("combobox", { name: /Follow-up model/ }),
).toBeInTheDocument();
expect(screen.queryByLabelText(/Quick provider/)).toBeNull();
expect(screen.queryByLabelText(/Follow-up provider/)).toBeNull();
await user.selectOptions(
quickModel,
modelIdentityValue({
provider: "openrouter",
modelId: "anthropic/claude/sonnet",
}),
);
await user.click(
screen.getByRole("button", { name: "Save Quick Pane settings" }),
);
await waitFor(() =>
expect(mocks.invoke).toHaveBeenCalledWith("save_ui_state", {
state: {
...loaded,
quickPane: {
...loaded.quickPane,
provider: "openrouter",
model: "anthropic/claude/sonnet",
},
},
}),
);
});
it("shows restart progress until the replacement snapshot is restored", async () => {
const user = userEvent.setup();
const restart = deferred<unknown>();
@@ -127,9 +204,10 @@ describe("App command progress and errors", () => {
).toBeInTheDocument();
expect(screen.getByRole("main")).toHaveAttribute("aria-busy", "true");
await user.click(restarting);
expect(mocks.invoke).toHaveBeenCalledTimes(2);
expect(mocks.invoke).toHaveBeenCalledTimes(3);
expect(mocks.invoke).toHaveBeenNthCalledWith(1, "load_ui_state");
expect(mocks.invoke).toHaveBeenNthCalledWith(2, "restart", {
expect(mocks.invoke).toHaveBeenNthCalledWith(2, "get_model_catalog");
expect(mocks.invoke).toHaveBeenNthCalledWith(3, "restart", {
agentId: "agent-a",
});