Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,10 @@ const modelPressDragRelease: Play = async (ctx) => {
});
// Astra may sit under More models in this fixture: drag to the current model's row, which still
// exercises the button's press-drag-release path and closes the menu.
const row = { role: "menuitemradio", name: /GPT-6\.1 Sol/ } as const;
// Onto its name: the row's trailing columns hold its effort item, which opens the levels instead.
const row = {
selector: ".acpmux-quick-model-effort[data-running] > .acpmux-quick-item .acpmux-quick-label",
} as const;
await ctx.pointer.move(row);
await ctx.pointer.up(row);
await ctx.waitFor(
Expand Down
53 changes: 45 additions & 8 deletions webviews/src/agent-session/acpmux/modelPicker.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@
min-width: 0;
box-sizing: border-box;
}
/* The model menu and its side menus never let the chat read through them (Leo 2026-10-31: "plus all
this dumbass shit in the model picker", over a menu whose list showed the transcript's "23m 43s
Running" and "Done" behind its rows). Still glass, never opaque: the fill a translucent surface
that carries text keeps (desktop.css --surface-text-min) and the menus' stronger frost
(--ds-glass-menu-blur), in plain glass and under the lens alike. Chromium over the window glass
lays it over the theme surface as well (glass.css), so there it is near solid. Reduce
Transparency still makes it the solid popup surface (glass.css, !important). */
.ui-popup.ui-glass.acpmux-quick-menu,
:root[data-glass-lens]:is([data-glass="lens"], [data-glass="spectrum"]) .ui-popup.ui-glass.acpmux-quick-menu {
--glass-tint: color-mix(in srgb, var(--glass-surface) var(--surface-text-min, 92%), transparent);
--glass-backdrop: var(--ds-glass-menu-blur, blur(32px) saturate(1.6));
}
.acpmux-mp-rail-scroll {
flex: 0 0 auto;
min-width: 0;
Expand Down Expand Up @@ -642,28 +654,53 @@
edge, the running model's level and a chevron. The item opens the model's levels as a side
dropdown beside the row, a vertical list in its own popup (Leo 2026-10-30: "we dont like
horizontal text for effort. just make it vertical. hell, why not do a vertical slider?"). */
/* One right edge (Leo 2026-10-31: "plus all this dumbass shit in the model picker", over ⌘2 to ⌘4
floating mid-row while the running row's "Medium ›" sat far right): every row ends in the same
trailing columns, effort, then the star, then the ⌘N key (or the running row's check) at the
menu's edge, as a native menu's shortcuts. The model's own item spans the whole row and keeps
its key in its last column; the effort item and the star sit over the empty columns before it,
so the key lines up with every other row's whether or not a row has effort or a star. */
.acpmux-quick-model-effort {
display: flex;
--acpmux-quick-star-column: 0px;
--acpmux-quick-effort-width: 0px;
display: grid;
grid-template-columns:
minmax(0, 1fr) var(--acpmux-quick-effort-width) var(--acpmux-quick-star-column)
calc(var(--ds-space-2) + 16px + var(--ui-row-padding-inline));
align-items: center;
}
.acpmux-quick-model-effort:has(> .acpmux-quick-effort-trigger) { --acpmux-quick-effort-width: var(--acpmux-quick-effort-column); }
.acpmux-quick-model-effort:has(> .acpmux-quick-star) { --acpmux-quick-star-column: 26px; }
.acpmux-quick-menu { --acpmux-quick-effort-column: 112px; }
.acpmux-quick-effort-now {
overflow: hidden;
text-overflow: ellipsis;
}
.acpmux-quick-model-effort > .acpmux-quick-item:first-child {
flex: 1 1 auto;
.acpmux-quick-model-effort > .ui-menu-item.acpmux-quick-item:first-child {
grid-area: 1 / 1 / 2 / -1;
display: grid;
grid-template-columns: minmax(0, 1fr) calc(var(--acpmux-quick-effort-width) + var(--acpmux-quick-star-column)) 16px;
column-gap: var(--ds-space-2);
min-width: 0;
}
.acpmux-quick-model-effort > .ui-menu-item.acpmux-quick-item:first-child > .acpmux-quick-label { grid-column: 1; grid-row: 1; }
.acpmux-quick-model-effort > .ui-menu-item.acpmux-quick-item:first-child > :is(.ui-menu-shortcut, .ui-menu-check) { grid-column: 3; grid-row: 1; }
.acpmux-quick-model-effort > .acpmux-quick-star { position: relative; grid-area: 1 / 3; }
.ui-menu-item.acpmux-quick-effort-trigger {
flex: none;
/* Positioned and after the model's item in the row, so it paints over the item's empty column and
takes the pointer (no z-index: the layer scale is for surfaces, not a row's parts). */
position: relative;
grid-area: 1 / 2;
justify-content: flex-end;
gap: var(--ds-space-1);
/* One width on every row (the width probe's), so the rows' ⌘N keys and stars line up in columns
whatever level each row's hidden item names. */
width: var(--acpmux-quick-effort-column);
/* As wide as its level and chevron, at its column's trailing edge: the column keeps every row's
keys in line, and the item over the model's row covers only what it shows, so a click on the
row's middle picks the model. */
justify-self: end;
width: auto;
max-width: var(--acpmux-quick-effort-column);
min-width: 0;
padding-inline: var(--ds-space-2) var(--ui-row-padding-inline);
padding-inline: var(--ds-space-2) var(--ds-space-1);
color: var(--agent-muted);
font-size: var(--text-caption);
white-space: nowrap;
Expand Down
36 changes: 35 additions & 1 deletion webviews/test/quick-model-menu-layout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,38 @@ await requireBrowserLane("quick-model-menu-layout.test.ts", async () => {
} finally {
await chat.close();
}
// One right edge (Leo 2026-10-31: "plus all this dumbass shit in the model picker"): with each
// row's effort item on, every ⌘N key and the running row's check share the menu's trailing
// column, after the effort item and the star, never mid-row.
const inChat = await open("in-chat");
try {
await inChat.locator(".acpmux-quick-model-effort[data-running]").first().waitFor();
const edges = await inChat.locator(".acpmux-quick-model-effort").evaluateAll((rows) =>
rows.map((row) => {
const key = row.querySelector(
":scope > .ui-menu-item .ui-menu-shortcut, :scope > .ui-menu-item .ui-menu-check:not(:empty)",
);
const effort = row.querySelector(":scope > .acpmux-quick-effort-trigger");
const star = row.querySelector(":scope > .acpmux-quick-star");
const box = key?.getBoundingClientRect();
return {
right: box ? Math.round(box.right) : undefined,
left: box?.left,
effortRight: effort?.getBoundingClientRect().right,
starRight: star?.getBoundingClientRect().right,
};
}),
);
const keyed = edges.filter((edge) => edge.right !== undefined);
expect(keyed.length, JSON.stringify(edges)).toBeGreaterThan(1);
expect(new Set(keyed.map((edge) => edge.right)).size, JSON.stringify(edges)).toBe(1);
for (const edge of keyed) {
if (edge.effortRight !== undefined) expect(edge.effortRight).toBeLessThanOrEqual(edge.left! + 0.5);
if (edge.starRight !== undefined) expect(edge.starRight).toBeLessThanOrEqual(edge.left! + 0.5);
}
} finally {
await inChat.close();
}
});

test("many harnesses wrap into whole symbols, never one cut at the strip's edge", async () => {
Expand Down Expand Up @@ -389,7 +421,9 @@ await requireBrowserLane("quick-model-menu-layout.test.ts", async () => {
// Beside the row's effort item, clear of the model's name (a starred row's star may follow
// the effort item at the row's very end).
const triggerBox = (await trigger.boundingBox())!;
const nameBox = (await row.locator(":scope > .acpmux-quick-item:first-child").boundingBox())!;
const nameBox = (await row
.locator(":scope > .acpmux-quick-item:first-child > .acpmux-quick-label")
.boundingBox())!;
const menuBox = (await dropdown.boundingBox())!;
const viewport = page.viewportSize()!;
// Beside the effort item whenever the window has room on that side; a narrow window shifts it,
Expand Down
Loading