Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6d38b68
fix(next): the running tray starts folded to its header, and holds th…
austinywang Oct 31, 2026
a67bdc6
test(next): open the running tray where a test reads its lines; fixtu…
austinywang Oct 31, 2026
2cfb275
fix(next): the tray's header takes focus when it folds under a focuse…
austinywang Oct 31, 2026
23071fa
fix(next): a subagent line's icons and title keep the row's 8px icon gap
austinywang Oct 31, 2026
21a3d62
fix(next): the narrow tray keeps its 8px state-icon gap; tests wait f…
austinywang Oct 31, 2026
6494853
test(next): a tray folding with motion off moves focus from its line …
austinywang Oct 31, 2026
27bce40
fix(next): the tray's header takes focus when a motionless fold remov…
austinywang Oct 31, 2026
2fb048a
fix(tray): rename the header button ref after the feat-cmux-next merge
lawrencecchen Oct 31, 2026
7be4a01
fix(tray): icon-gap tests and CSS follow #29608, which removed the ki…
lawrencecchen Oct 31, 2026
6696424
perf(agent-pane): skip hidden subagent tray rows when folded
teamleaderleo Oct 31, 2026
16a40ca
test(agent-pane): 300 collapsed agents construct no hidden lines
teamleaderleo Oct 31, 2026
c66f025
test(tray): avoid 300 hidden row reads across repeated collapsed renders
teamleaderleo Oct 31, 2026
23460ae
perf(agent-pane): index 300 siblings without copying arrays
teamleaderleo Oct 31, 2026
71e6934
test(agent-pane): keep 300 sibling rows and cycle fallback
teamleaderleo Oct 31, 2026
4608d0c
fix(agent-pane): every part over the composer on the box's edges and …
teamleaderleo Oct 30, 2026
80c1c69
fix(tray): align fixed status and action tracks with the composer
teamleaderleo Oct 31, 2026
2449d2f
Merge remote-tracking branch 'origin/feat-cmux-next' into lane/strand…
teamleaderleo Oct 31, 2026
6615ed9
fix(tray): keep workflow progress out of the status track
teamleaderleo Oct 31, 2026
2a22d79
fix(tray): keep action columns inside the narrowest panes
teamleaderleo Oct 31, 2026
73817e0
fix(web): center tray controls across row heights
teamleaderleo Nov 1, 2026
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
4 changes: 4 additions & 0 deletions release-notes/next/highlights/running-tray-alignment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
title: Aligned controls in the running tray
category: fixed

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This highlight has neither a docs: link nor an action:, so the What's New digest validator rejects and skips it. Add the relevant documentation link (as the related tray entry does) or a try-it action.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At release-notes/next/highlights/running-tray-alignment.md, line 2:

<comment>This highlight has neither a `docs:` link nor an `action:`, so the What's New digest validator rejects and skips it. Add the relevant documentation link (as the related tray entry does) or a try-it action.</comment>

<file context>
@@ -0,0 +1,4 @@
+title: Aligned controls in the running tray
+category: fixed
+
+The running tray above the composer keeps durations, status labels and controls aligned across running and finished work. It starts folded and remembers each chat's choice.
</file context>


The running tray above the composer keeps durations, status labels and controls aligned across running and finished work. It starts folded and remembers each chat's choice.
1 change: 1 addition & 0 deletions webviews/src/agent-session/acpmux/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8193,6 +8193,7 @@ function AcpmuxPane() {
<SubagentClockContext.Provider value={snapshot.clockOffsetMs ?? 0}>
<SubagentTray
key={shownSessionId}
chat={shownSessionId}
items={trayList}
finished={trayFinished}
working={snapshot.isWorking}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,14 @@ export default agentPaneEntry({
},
variants: {
closed: {
note: "Before it opens: the tray above the composer lists the running agents; the chat's ⋯ menu has Background tasks.",
note: "Before it opens: the tray above the composer, opened from its folded header, lists the running agents; the chat's ⋯ menu has Background tasks.",
snapshot: snapshot(false),
play: async (ctx) => {
await ctx.waitFor(() => ctx.document.querySelector(".acpmux-running-tray"));
// A headed tray starts folded (cmux#28875): open it, as the person does.
if (ctx.document.querySelector('.acpmux-running-tray__head[aria-expanded="false"]'))
await ctx.click({ selector: ".acpmux-running-tray__head" });
await ctx.waitFor(() => ctx.document.querySelector(".acpmux-running-tray__list .acpmux-agent-line"));
},
},
docked: {
Expand Down
4 changes: 3 additions & 1 deletion webviews/src/agent-session/acpmux/bottomStack.css
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,10 @@
}
/* The tray of running subagents and background commands (subagents/SubagentTray.tsx), aligned
with the composer card; in a short pane it gives up its height like the parts below. */
/* Match the composer's 8px outset below 298px of content width (#28587). */
.acpmux-dock > .acpmux-running-tray {
margin: 0 var(--cv-inset-end, var(--cv-inset)) 0 var(--cv-inset);
--acpmux-tray-outset: clamp(0px, calc((297.5px - (100% - var(--cv-inset) - var(--cv-inset-end, var(--cv-inset)))) * 1000), 8px);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The tray and composer use different narrow-outset breakpoints. At fractional widths between 297px and 297.5px, this rule gives the tray an 8px outset while the composer does not, so their edges no longer align; use the composer’s 297px threshold (or a shared breakpoint token).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/bottomStack.css, line 62:

<comment>The tray and composer use different narrow-outset breakpoints. At fractional widths between 297px and 297.5px, this rule gives the tray an 8px outset while the composer does not, so their edges no longer align; use the composer’s 297px threshold (or a shared breakpoint token).</comment>

<file context>
@@ -57,8 +57,10 @@
+/* Match the composer's 8px outset below 298px of content width (#28587). */
 .acpmux-dock > .acpmux-running-tray {
-  margin: 0 var(--cv-inset-end, var(--cv-inset)) 0 var(--cv-inset);
+  --acpmux-tray-outset: clamp(0px, calc((297.5px - (100% - var(--cv-inset) - var(--cv-inset-end, var(--cv-inset)))) * 1000), 8px);
+  margin: 0 calc(var(--cv-inset-end, var(--cv-inset)) - var(--acpmux-tray-outset)) 0 calc(var(--cv-inset) - var(--acpmux-tray-outset));
 }
</file context>
Suggested change
--acpmux-tray-outset: clamp(0px, calc((297.5px - (100% - var(--cv-inset) - var(--cv-inset-end, var(--cv-inset)))) * 1000), 8px);
--acpmux-tray-outset: clamp(0px, calc((297px - (100% - var(--cv-inset) - var(--cv-inset-end, var(--cv-inset)))) * 1000), 8px);

margin: 0 calc(var(--cv-inset-end, var(--cv-inset)) - var(--acpmux-tray-outset)) 0 calc(var(--cv-inset) - var(--acpmux-tray-outset));
}
.acpmux-dock > .acpmux-composer {
display: flex;
Expand Down
4 changes: 3 additions & 1 deletion webviews/src/agent-session/acpmux/composerLocation.css
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@
}
/* The trailing cluster takes the row's free space on its leading side, in either direction. */
.acpmux-composer-bar .acpmux-composer-actions {
gap: 4px;
gap: var(--acpmux-composer-action-gap);
margin-left: 0;
margin-inline-start: auto;
}
Expand Down Expand Up @@ -1338,7 +1338,9 @@
whole pane (pane-has-restyle.browser.test.ts). */
/* The control tokens live on the composer's root, so the rows outside the box (the strip over it,
the quiet row under it) stand on the same column. New Tab derives its inset (newtab/screen.css). */
.acpmux-dock,
:is(.acpmux-composer, .nt-composer) {
--acpmux-composer-action-gap: 4px;
--acpmux-composer-control: 28px;
--acpmux-composer-inset: calc(8px * var(--cmux-interface-scale, 1));
--acpmux-composer-lead-gap: calc(8px * var(--cmux-interface-scale, 1));
Expand Down
22 changes: 11 additions & 11 deletions webviews/src/agent-session/acpmux/subagents/AgentLine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@
// subtitles are bad"): one leading glyph (the spinner while it works, else its own icon or its
// harness's mark), never a second kind glyph beside it; the title; one muted trailing meta (what it
// spent, a state worth reading, its time); the chevron. No agent-type subtitle on the line (the
// tooltip and the hover card keep it), and no "Done" or "Running" word: the glyph says those
// (`quiet`), and the word stays for screen readers. design/chat-transcript.md "Subagent row anatomy".
// tooltip and the hover card keep it). The tray reserves a status-word track beside the time,
// including Running and Done, so every state keeps the same control geometry.
import type { CSSProperties, ReactNode } from "react";
import { AgentMark, knownMark } from "../../shared/AgentMark";
import { Tooltip } from "../../../ui/Tooltip";
Expand Down Expand Up @@ -166,21 +166,21 @@ export function AgentLine({
)}
<span className="acpmux-summary-agent__name">{name}</span>
{detail && detail !== name && <span className="acpmux-summary-agent__kind">{detail}</span>}
{progress && trayColumns && progressAt === "meta" && (
<span className="acpmux-agent-line__stats">{progress}</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This moves workflow progress into the title’s ellipsized track, so a long agent name can hide the counts completely. Keep progressAt="meta" in a separate non-overlapping metadata/grid track so workflow progress remains visible.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At webviews/src/agent-session/acpmux/subagents/AgentLine.tsx, line 170:

<comment>This moves workflow progress into the title’s ellipsized track, so a long agent name can hide the counts completely. Keep `progressAt="meta"` in a separate non-overlapping metadata/grid track so workflow progress remains visible.</comment>

<file context>
@@ -166,21 +166,21 @@ export function AgentLine({
         <span className="acpmux-summary-agent__name">{name}</span>
         {detail && detail !== name && <span className="acpmux-summary-agent__kind">{detail}</span>}
+        {progress && trayColumns && progressAt === "meta" && (
+          <span className="acpmux-agent-line__stats">{progress}</span>
+        )}
         {progress && !(trayColumns && progressAt === "meta") && (
</file context>

)}
{progress && !(trayColumns && progressAt === "meta") && (
<span className="acpmux-agent-line__progress">{progress}</span>
)}
</span>
{trayColumns ? (
<>
{/* One muted trailing meta: what it spent and a state worth a word, then its time. */}
{((progress && progressAt === "meta") || !quiet) && (
<span className="acpmux-summary-meta acpmux-agent-line__meta">
{progress && progressAt === "meta" && <span className="acpmux-agent-line__stats">{progress}</span>}
{/* Running turns to Waiting or Failed with a tick, never a jump (trayMotion.tsx). */}
{!quiet && <Tick className="acpmux-summary-agent__state" text={state} />}
</span>
)}
{quiet && <span className="acpmux-agent-line__sr">{state}</span>}
{/* The tray reserves a visible status track for every row. Keeping the word in the same
track for quiet (Running/Done/Cancelled) rows prevents a missing state from pulling
the duration toward the title or moving the Stop and chevron columns. */}
<span className="acpmux-summary-meta acpmux-agent-line__meta" data-tray-status>
<Tick className="acpmux-summary-agent__state" text={state} />
</span>
<span className="acpmux-summary-meta acpmux-agent-line__time" aria-hidden={time === undefined}>
{time}
</span>
Expand Down
4 changes: 3 additions & 1 deletion webviews/src/agent-session/acpmux/subagents/AgentsPill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export function nestAgents(agents: readonly Subagent[]): { agent: Subagent; dept
const under = new Map<string | null, Subagent[]>();
for (const agent of agents) {
const parent = agent.parent !== null && ids.has(agent.parent) ? agent.parent : null;
under.set(parent, [...(under.get(parent) ?? []), agent]);
const siblings = under.get(parent);
if (siblings) siblings.push(agent);
else under.set(parent, [agent]);
}
const rows: { agent: Subagent; depth: number }[] = [];
const placed = new Set<string>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,19 @@ const panelScript = [
{ name: "open all and scroll to the end", run: scrollPanel },
];

/** Waits for the tray's live `count` lines, and checks no group card is left in the transcript. */
/** A headed tray starts folded to its header (cmux#28875): open it, as the person clicks it. */
const openTray = async (ctx: PlayContext) => {
await ctx.waitFor(() => ctx.document.querySelector(".acpmux-running-tray"));
const head = ctx.document.querySelector<HTMLElement>(".acpmux-running-tray__head");
if (!head || head.getAttribute("aria-expanded") === "true") return;
await ctx.click({ selector: ".acpmux-running-tray__head" });
await ctx.waitFor(() => ctx.document.querySelector(".acpmux-running-tray__list"));
};

/** Opens the tray, waits for its live `count` lines, and checks no group card is left in the
* transcript. */
const trayLines = (count: number) => async (ctx: PlayContext) => {
await openTray(ctx);
await ctx.waitFor(
() =>
ctx.document.querySelectorAll(".acpmux-running-tray li:not([data-finished]) .acpmux-agent-line").length === count,
Expand Down Expand Up @@ -285,6 +296,27 @@ export default agentPaneEntry({
// The panel's looks (trayPanel.experiment.ts), on the two- and 118-agent panels.
experiment: { definition: trayPanelLook, script: panelScript },
variants: {
geometry: {
note: "Geometry fixture: running, completed and cancelled rows share the trailing tracks above a running empty composer.",
play: async (ctx) => {
await openTray(ctx);
},
snapshot: chat(
[
user("Keep the three task states visible", 4),
group(
[
agent("Running check", 2.1, "running", { action: "Run the check" }),
agent("Done check", 3.8, "completed", { ran: 1.2 }),
agent("Cancelled check", 3.8, "cancelled", { ran: 1.4 }),
],
2.1,
),
assistant("The checks are visible above the composer.", 1),
],
{ isWorking: true },
),
},
"grid-31": {
note: "Grid fixture: 31 rows (29 ended and two running), expanded with the header and footer visible.",
snapshot: panel(29),
Expand All @@ -297,6 +329,7 @@ export default agentPaneEntry({
note: "Leo's panel with two chats running and three ended (2026-10-25: \"this section, the whatever background tasks ... it's kind of wack\"): the header pinned on top, running first, then the latest that ended; no line carries a subtitle, so none shows a raw shell line.",
play: async (ctx) => {
await ctx.waitFor(() => ctx.document.querySelector(".acpmux-running-tray__head"));
await openTray(ctx);
headInView(ctx);
// No line carries a subtitle, so none shows a raw shell line (subagents/AgentLine.tsx).
const tray = ctx.document.querySelector<HTMLElement>(".acpmux-running-tray")!;
Expand All @@ -309,6 +342,7 @@ export default agentPaneEntry({
"panel-one-running": {
note: "One of the two chats ended: the header and its collapse stay while ended lines show (it used to unmount once fewer than two ran).",
play: async (ctx) => {
await openTray(ctx);
await ctx.waitFor(() => ctx.document.querySelector(".acpmux-running-tray .acpmux-agent-line"));
headInView(ctx);
},
Expand All @@ -331,6 +365,7 @@ export default agentPaneEntry({
{ isWorking: true },
),
play: async (ctx) => {
await openTray(ctx);
await ctx.waitFor(() => ctx.document.querySelector(".acpmux-running-tray__finished"));
const shell = ctx.document.querySelector<HTMLElement>(".acpmux-shell")!;
shell.style.width = "300px";
Expand Down Expand Up @@ -389,9 +424,6 @@ export default agentPaneEntry({
"duration-columns": {
note: "Duration-column stress fixture: running work spans 9s, 2h 20m and 12h 59m 59s so every time stays on one line in the shared trailing track.",
play: async (ctx) => {
await ctx.waitFor(() => ctx.document.querySelector(".acpmux-running-tray__head"));
if (ctx.document.querySelector(".acpmux-running-tray__head")?.getAttribute("aria-expanded") !== "true")
await ctx.click({ selector: ".acpmux-running-tray__head" });
await trayLines(6)(ctx);
},
snapshot: chat(
Expand Down Expand Up @@ -444,7 +476,7 @@ export default agentPaneEntry({
snapshot: chat([user("Run the red/green tests in the background", 59), testRun], { isWorking: true }),
},
"several-running": {
note: "Two subagents and a background command: a header with the counts and the time since the first started, a line each (up to three show open). The ended task remains a compact status card.",
note: "Two subagents and a background command: a header with the counts and the time since the first started, a line each once its header is opened (it starts folded to the header). The ended task remains a compact status card.",
play: trayLines(3),
snapshot: chat(
[
Expand Down Expand Up @@ -497,6 +529,7 @@ export default agentPaneEntry({
"between-turns": {
note: "After the turn: a background subagent and two chats the agent started still run. The tray says so above the composer; the transcript ends with the turn's footer.",
play: async (ctx) => {
await openTray(ctx);
await ctx.waitFor(() => ctx.document.querySelectorAll(".acpmux-running-tray .acpmux-agent-line").length === 3);
if (ctx.document.querySelector(".cv-bgwork")) throw new Error("no Running in background row in the transcript");
},
Expand Down Expand Up @@ -575,6 +608,7 @@ export default agentPaneEntry({
"codex-lineage": {
note: 'A Codex chat\'s subagents as its records say (Leo 2026-10-22: "why is it like... staggered? were they created by another subagent?"): the chat itself started both, so they sit side by side. A child that reports its parent thread never makes the chat a subagent of its own, nested under that child.',
play: async (ctx) => {
await openTray(ctx);
await ctx.waitFor(() => ctx.document.querySelector(".acpmux-running-tray .acpmux-agent-line"));
const names = [...ctx.document.querySelectorAll(".acpmux-running-tray .acpmux-summary-agent__name")];
if (names.some((name) => /^[0-9a-f]{8}-/.test(name.textContent ?? "")))
Expand All @@ -594,7 +628,7 @@ export default agentPaneEntry({
play: async (ctx) => {
await ctx.waitFor(() => ctx.document.querySelector(".acpmux-running-tray__head"));
if (ctx.find({ selector: ".acpmux-running-tray__head" }).getAttribute("aria-expanded") !== "false")
throw new Error("more than three running lines start folded");
throw new Error("a headed tray starts folded");
},
snapshot: chat(
[
Expand Down
Loading
Loading