feat(workspace,settings): model menu chip in the composer, compact settings panels

The workspace home's composer takes ONE Model chip (ModelMenu: model list
plus Effort) and moves AI Router / Permission / Local-New worktree into the
row under it; Settings drops the General-page cost card and export row,
PricingSettings and the empty states are compacted for settings panels.
This commit is contained in:
2026-10-02 17:49:05 +07:00
parent da1b17778c
commit fadecaddd0
23 changed files with 925 additions and 640 deletions
@@ -2,8 +2,8 @@
* @file WorkspaceHome.test.tsx
* @description The workspace home: a new session starts in the first free
* pane slot and opens it, on the AI tool picked for it (the workspace's by
* default), with its fields in order — AI, AI Router, Model (the router's
* models on a router), Permission, Effort —, all-busy is refused, Recents open a live pane or the
* default), AI and Model in the box and AI Router / Permission / Worktree
* under it (the router's models on a router), all-busy is refused, Recents open a live pane or the
* transcript (never a resume), instructions save, context files upload and
* remove, a folder is added with the repo's untracked worktrees suggested
* (minus hidden sources), an unsupported tool is said so, and memory opens
@@ -32,7 +32,7 @@ vi.mock("../../run/RunSetup", () => ({
}) => (
<textarea aria-label={placeholder} value={value} onChange={(e) => onChange(e.target.value)} />
),
ModelPicker: ({
ModelMenu: ({
value,
onChange,
choices,
@@ -210,14 +210,21 @@ describe("WorkspaceHome", () => {
);
});
it("lays its fields out in order: AI, AI Router, Model, Permission mode, Thinking effort", async () => {
it("keeps AI and Model (with effort) in the box, Router/Permission/Worktree under it", async () => {
renderHome();
await screen.findByTestId("workspace-recent-s-live");
const box = screen.getByTestId("workspace-home-composer");
const labels = [
...box.querySelectorAll("button[aria-haspopup='listbox'], input[aria-label='model']"),
].map((x) => x.getAttribute("aria-label"));
expect(labels).toEqual(["AI", "AI Router", "model", "Permission mode", "Thinking effort"]);
const labels = (id: string) =>
[
...screen
.getByTestId(id)
.querySelectorAll("button[aria-haspopup='listbox'], input[aria-label='model']"),
].map((x) => x.getAttribute("aria-label"));
expect(labels("workspace-home-composer")).toEqual(["AI", "model"]);
expect(labels("workspace-home-options")).toEqual([
"AI Router",
"Permission mode",
expect.stringContaining("own git worktree"),
]);
});
it("starts the session on the AI tool picked for it, with that tool's own fields", async () => {
@@ -282,11 +289,14 @@ describe("WorkspaceHome", () => {
);
});
it("starts the session in its own worktree when Worktree is ticked (a repo only)", async () => {
it("starts the session in its own worktree when New worktree is picked (a repo only)", async () => {
const user = userEvent.setup();
renderHome();
await screen.findByTestId("workspace-recent-s-live");
await user.click(await screen.findByRole("checkbox", { name: "Worktree" }));
await user.click(
within(await screen.findByTestId("workspace-home-worktree")).getByRole("button")
);
await user.click(screen.getByText("New worktree"));
await user.type(screen.getByLabelText("How can I help you today?"), "try it");
await user.click(screen.getByTestId("workspace-home-start"));
expect(api.lanes.action).toHaveBeenCalledWith(1, "start", {