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
22 changes: 3 additions & 19 deletions webviews/src/agent-session/acpmux/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,6 @@ import {
type SubagentScope,
} from "./inspector/subagentScope";
import { pendingToolCalls, waitingSubagents } from "./summary/SubagentStatusList";
import { AgentsPill } from "./subagents/AgentsPill";
import { SubagentTray } from "./subagents/SubagentTray";
import { BackgroundTasksPanel } from "./backgroundTasks/BackgroundTasksPanel";
import { backgroundGroups, type TaskCache } from "./backgroundTasks/backgroundTasks";
Expand Down Expand Up @@ -7567,10 +7566,9 @@ function AcpmuxPane() {
(id) => showSubagent(id, false),
snapshot.clockOffsetMs ?? 0,
);
// The agents pill (subagents/AgentsPill.tsx) and each subagent's color (agentTones.ts), which
// the pill's list and the transcript's subagent rows share. A streamed chunk makes a new list of
// the same agents: the last one stays while nothing the pill shows changed, so a snapshot that
// streams text redraws neither the pill nor a subagent row.
// Each subagent's color (agentTones.ts), which the transcript's subagent rows share. A streamed
// chunk makes a new list of the same agents: the last one stays while nothing a row shows changed,
// so a snapshot that streams text redraws neither the row nor its tone.
const agentsKey = freshAgents
.map((agent) =>
[
Expand Down Expand Up @@ -11880,20 +11878,6 @@ function AcpmuxPane() {
{/* The tray (subagents/SubagentTray.tsx): in the bottom stack, right above the composer, every
running or recently ended subagent, background command and started chat, one card each. */}
{trayPlacement === "tray" && trayNode}
{/* The agents pill (subagents/AgentsPill.tsx), with the tray off (Debug Settings): in the bottom
stack, right above the composer, while the turn works; an agent a cancel or a reconnect left
"running" shows none. */}
{!subagentTray && !quick && !freshView && snapshot.isWorking && chatTones && chatAgents.some(running) && (
<SubagentClockContext.Provider value={snapshot.clockOffsetMs ?? 0}>
<AgentsPill
key={shownSessionId}
agents={chatAgents}
waiting={waitingAgents}
tones={chatTones}
onOpen={showSubagent}
/>
</SubagentClockContext.Provider>
)}
{statusCardOpen && (
<HarnessStatusCard
summary={snapshot.summary}
Expand Down
21 changes: 7 additions & 14 deletions webviews/src/agent-session/acpmux/bottomStack.css
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* The chat's bottom stack (hq#1957, Leo 2026-10-14), .acpmux-dock: everything under the transcript,
in this order: a question waiting for an answer (.acpmux-asks-stack), notices, the tray while subagents or background commands run
(subagents/SubagentTray.tsx; the agents pill, subagents/AgentsPill.tsx, with it off),
(subagents/SubagentTray.tsx),
then the composer form, which holds the queued prompts (ComposerQueue.tsx), the location strip and
the card. Every part takes its own height in the stack's own flow, so none draws over another or
under the pane's edge; a new part joins that flow here.
Expand Down Expand Up @@ -37,9 +37,9 @@
.acpmux-main[data-dock="float"]:has(.acpmux-composer-foot) > .acpmux-dock {
max-height: calc(100% - 44px - max(64px, var(--acpmux-transcript-min, 96px) - 32px));
}
/* A part of the floating stack is a band the pane's width wide whose card, bar or pill sits inset in
it by the column's gutters (--cv-inset): the question card, the pull request dock, an error card,
the agents pill. The band takes the pointer, since the transcript's rows under the stack are only
/* A part of the floating stack is a band the pane's width wide whose card or bar sits inset in it by
the column's gutters (--cv-inset): the question card, the pull request dock, or an error card. The
band takes the pointer, since the transcript's rows under the stack are only
masked: a hover, click or drag in its gutters or empty room must not reach a row no one sees. The
wheel there scrolls the transcript all the same (wheelPassThrough.ts forwards it), and a band that
scrolls itself (the pull request dock, the question card in a short pane) keeps its wheel and its
Expand All @@ -49,12 +49,6 @@
.acpmux-main[data-dock="float"] > .acpmux-dock > .acpmux-composer > * {
pointer-events: auto;
}
.acpmux-dock > .acpmux-agents-pill-anchor {
position: relative;
flex: none;
display: flex;
padding: 0 var(--cv-inset-end, var(--cv-inset)) 8px var(--cv-inset);
}
/* 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. */
.acpmux-dock > .acpmux-running-tray {
Expand Down Expand Up @@ -191,10 +185,9 @@
}
}

/* What joins the bottom stack rises out of the composer as it appears (Leo 2026-10-20, directional
motion): a question, a notice, the pull request dock, the agents pill and a queued prompt fade in
from a few points below where they land (ui-enter, ui/popupSurface.css). Their space opens in one
frame, as the stack's flow takes it; only the drawing travels. */
/* What joins the bottom stack enters from the composer as it appears (Leo 2026-10-20): a question,
a notice, the pull request dock, the running tray and a queued prompt use the shared entry tokens.

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: This comment says the running tray uses the shared entry tokens, but subagents.css overrides the tray to an opacity-only cv-enter-fade arrival. Drop the tray from this list, or say it is handled in subagents.css.

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 189:

<comment>This comment says the running tray uses the shared entry tokens, but subagents.css overrides the tray to an opacity-only `cv-enter-fade` arrival. Drop the tray from this list, or say it is handled in subagents.css.</comment>

<file context>
@@ -191,10 +185,9 @@
-   from a few points below where they land (ui-enter, ui/popupSurface.css). Their space opens in one
-   frame, as the stack's flow takes it; only the drawing travels. */
+/* What joins the bottom stack enters from the composer as it appears (Leo 2026-10-20): a question,
+   a notice, the pull request dock, the running tray and a queued prompt use the shared entry tokens.
+   Their space opens in one frame, as the stack's flow takes it; only the drawing travels. */
 .acpmux-dock > :not(.acpmux-composer, .acpmux-home-area, .acpmux-asks-stack),
</file context>
Suggested change
a notice, the pull request dock, the running tray and a queued prompt use the shared entry tokens.
a notice, the pull request dock and a queued prompt use the shared entry tokens; the running tray has its own (subagents.css).

Their space opens in one frame, as the stack's flow takes it; only the drawing travels. */
.acpmux-dock > :not(.acpmux-composer, .acpmux-home-area, .acpmux-asks-stack),
.acpmux-asks-stack > *,
.acpmux-composer-queue > li {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3516,6 +3516,12 @@ a.cv-sha:focus-visible {
.acpmux-row--enter {
animation: cv-enter var(--agent-motion-in, 180ms) var(--agent-ease-out, ease-out) both;
}
/* Live activity is absolutely placed by VirtualTranscript. Its first paint must not slide the
visible transcript when a subagent or command arrives; the row's measured box is already in
the layout, so only its ink fades in. Message rows keep their dedicated presentation motion. */
.acpmux-row.acpmux-row--enter:not(.acpmux-user):not(.acpmux-assistant) {

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: The :not(.acpmux-user) guard leaves live notice rows on the 4px rise (cv-enter), while other activity rows fade only. Message rows never get acpmux-row--enter (they get acpmux-row--message-enter), so the guard only excludes these notices. Drop the guard so all live acpmux-row--enter rows use the opacity-only entrance, and update the comment.

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/conversation/conversation.css, line 3522:

<comment>The `:not(.acpmux-user)` guard leaves live notice rows on the 4px rise (`cv-enter`), while other activity rows fade only. Message rows never get `acpmux-row--enter` (they get `acpmux-row--message-enter`), so the guard only excludes these notices. Drop the guard so all live `acpmux-row--enter` rows use the opacity-only entrance, and update the comment.</comment>

<file context>
@@ -3516,6 +3516,12 @@ a.cv-sha:focus-visible {
+  /* Live activity is absolutely placed by VirtualTranscript. Its first paint must not slide the
+     visible transcript when a subagent or command arrives; the row's measured box is already in
+     the layout, so only its ink fades in. Message rows keep their dedicated presentation motion. */
+  .acpmux-row.acpmux-row--enter:not(.acpmux-user):not(.acpmux-assistant) {
+    animation: cv-enter-fade var(--agent-motion-in, 180ms) var(--agent-ease-out, ease-out) both;
+  }
</file context>
Suggested change
.acpmux-row.acpmux-row--enter:not(.acpmux-user):not(.acpmux-assistant) {
.acpmux-row.acpmux-row--enter {

animation: cv-enter-fade var(--agent-motion-in, 180ms) var(--agent-ease-out, ease-out) both;
}
.acpmux-row__message-presentation {
transform-origin: center bottom;
animation: cv-message-enter var(--agent-motion-in, 180ms) var(--agent-ease-out, ease-out) both;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,25 @@ test("does not move the row itself when its streaming text grows", () => {
expect(next).toBe(oldTop);
});

test("keeps a reader's anchor when a live activity row is appended below it", () => {
const oldRows = [row("prompt", "prompt"), row("anchor", "the row in view"), row("reply", "reply")];
const activity = row("subagent-done", "Subagent finished");
activity.kind = "activity";
const newRows = [...oldRows, activity];
const oldLayout = layoutConversation(oldRows, 420);
const newLayout = layoutConversation(newRows, 420);
const oldTop = oldLayout.tops[1]! + 9;
const next = preserveScrollAnchorTop({
oldLayout,
newLayout,
oldRows,
newRows,
liveTop: oldTop,
anchorTop: oldTop,
});
expect(next! - newLayout.tops[1]!).toBe(9);
});

test("finds the same anchor after older history is inserted above it", () => {
const oldRows = [row("before", "before"), row("anchor", "the row in view"), row("tail", "tail")];
const inserted = Array.from({ length: 120 }, (_, index) => row(`old-${index}`, `old ${index}`));
Expand Down
8 changes: 4 additions & 4 deletions webviews/src/agent-session/acpmux/subagents/runningTray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
// (conversation/BackgroundTaskNote.tsx).
//
// Debug Settings `agentPane.debug.subagentTray` (AgentPaneTunables+SubagentTray.swift) turns it off:
// the transcript then draws the old group card where the subagents started (SubagentGroup.tsx),
// the agents pill sits above the composer (AgentsPill.tsx) and the running turn's line counts the
// work (conversation/WorkingFor.tsx). The host sends it with the theme (`subagentTray`); absent
// means on.
// the transcript then draws the old group card where the subagents started (SubagentGroup.tsx) and
// the running turn's line counts the work (conversation/WorkingFor.tsx). The separate running
// activity pill above the composer is intentionally not rendered. The host sends the setting with

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: Removing the pill leaves the subagents gallery claiming that its agents-pill variant renders it, although App.tsx no longer mounts AgentsPill. Update or remove that variant and coverage entry; if the component is deleted, move the shared nestAgents helper first.

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/runningTray.ts, line 13:

<comment>Removing the pill leaves the subagents gallery claiming that its `agents-pill` variant renders it, although `App.tsx` no longer mounts `AgentsPill`. Update or remove that variant and coverage entry; if the component is deleted, move the shared `nestAgents` helper first.</comment>

<file context>
@@ -8,10 +8,10 @@
-// means on.
+// the transcript then draws the old group card where the subagents started (SubagentGroup.tsx) and
+// the running turn's line counts the work (conversation/WorkingFor.tsx). The separate running
+// activity pill above the composer is intentionally not rendered. The host sends the setting with
+// the theme (`subagentTray`); absent means on.
 import { useSyncExternalStore } from "react";
</file context>

// the theme (`subagentTray`); absent means on.
import { useSyncExternalStore } from "react";
import type { AcpmuxRow } from "../model";
import type { StartedSession } from "../started/startedSessions";
Expand Down
27 changes: 13 additions & 14 deletions webviews/src/agent-session/acpmux/subagents/subagents.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/* Subagent groups (SubagentGroup.tsx), after the ChatGPT and T3 Code apps. Heights are fixed
and match model.ts: the group line is 48px with 8px above it, each listed subagent 48px, and
the list's last row adds 8px below. */
Expand Down Expand Up @@ -937,24 +937,25 @@

/* ---------- The tray's motion (trayMotion.tsx, Leo 2026-10-26: "following along with the exact
same things as everything else") ---------- */
/* The card's entrance and its kept copy's exit. Shipped: the new message's own (the gallery's other
arms, trayMotion.experiment.ts, live in gallery/fixtures/trayMotionArms.css only): a live transcript row's cv-enter (conversation.css) on the same
tokens, and what leaves on --agent-motion-out. */
/* The card's entrance and its kept copy's exit. Shipped: the new message's own opacity-only arrival
(the gallery's other arms, trayMotion.experiment.ts, live in gallery/fixtures/trayMotionArms.css
only), and what leaves on --agent-motion-out. */
.acpmux-running-tray {
--acpmux-tray-enter: cv-enter var(--agent-motion-in, 120ms) var(--agent-ease-out, ease-out) both;
/* The tray's box is part of the transcript's dock. Its arrival must not move visible rows; the
dock and VirtualTranscript settle their measured height in one frame, then the ink fades in. */
--acpmux-tray-enter: cv-enter-fade var(--agent-motion-in, 120ms) var(--agent-ease-out, ease-out) both;
--acpmux-tray-leave: acpmux-tray-leave var(--agent-motion-out, 80ms) var(--agent-ease-out, ease-out) both;
/* A line that joins the open list arrives as a live transcript row does (Leo 2026-10-27: "the
bottom running situation still not animating correctly"): the same cv-enter on the same tokens
as the card, not the popup's ui-enter (8px on --ui-enter-duration and --ui-enter-ease) that its
`data-enter` gives it in ui/popupSurface.css. The attribute stays: SubagentTray.tsx listens for
the line's animationend through it, and trayMotion.tsx's leaving copy holds it where it is drawn. */
--acpmux-tray-line-enter: cv-enter var(--agent-motion-in, 120ms) var(--agent-ease-out, ease-out) both;
/* A line that joins the open list arrives as a live transcript row does: opacity only, so the
dock can reserve its measured height without moving the rows already on screen. The attribute
stays: SubagentTray.tsx listens for the line's animationend through it, and trayMotion.tsx's
leaving copy holds it where it is drawn. */
--acpmux-tray-line-enter: cv-enter-fade var(--agent-motion-in, 120ms) var(--agent-ease-out, ease-out) both;
}
.acpmux-running-tray__list > li[data-enter] {
animation: var(--acpmux-tray-line-enter);
}
/* Reduce Motion keeps a transcript row's fade without its rise (conversation.css cv-enter-fade); the
tray marks no arrival then (SubagentTray.tsx), so this only keeps the two in step if one is marked. */
/* Reduce Motion keeps the same opacity-only arrival (conversation.css cv-enter-fade); the tray marks
no arrival then (SubagentTray.tsx), so this only keeps the two in step if one is marked. */
@media (prefers-reduced-motion: reduce) {
.acpmux-running-tray__list > li[data-enter] {
animation: cv-enter-fade var(--agent-motion-in, 120ms) var(--agent-ease-out, ease-out) both;
Expand Down Expand Up @@ -1009,13 +1010,11 @@
@keyframes acpmux-tick-in {
from {
opacity: 0;
translate: 0 0.4em;
}
}
@keyframes acpmux-tick-out {
to {
opacity: 0;
translate: 0 -0.4em;

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 tick keyframes no longer translate, but the comment above .acpmux-tick still says the old words 'leave up' and the new ones 'rise in'. Update that comment to describe the opacity-only crossfade. The Tick doc in trayMotion.tsx has the same stale wording.

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/subagents.css, line 1018:

<comment>The tick keyframes no longer translate, but the comment above `.acpmux-tick` still says the old words 'leave up' and the new ones 'rise in'. Update that comment to describe the opacity-only crossfade. The `Tick` doc in trayMotion.tsx has the same stale wording.</comment>

<file context>
@@ -1009,13 +1010,11 @@ button.cv-subagent > :last-child {
   to {
     opacity: 0;
-    translate: 0 -0.4em;
   }
 }
 
</file context>

}
}

Expand Down
10 changes: 5 additions & 5 deletions webviews/src/agent-session/acpmux/subagents/trayMotion.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// The tray's motion (SubagentTray.tsx), on the same train as the transcript's (Leo 2026-10-26: "Like
// why is this not animating and, like, following along with the exact same things as everything
// else?"). Its tokens are the new message's: a live row's entrance (conversation.css `cv-enter`, a
// fade and a 4px rise on `--agent-motion-in` and `--agent-ease-out`), and `--agent-motion-out` for
// what leaves; trayMotion.experiment.ts compares two other curves in the gallery.
// else?"). Its tokens are the new message's opacity-only entrance (conversation.css
// `cv-enter-fade`) and `--agent-motion-out` for what leaves; trayMotion.experiment.ts compares two
// other curves in the gallery.
// - The card eases in when it appears in a chat that already showed without it (the first subagent,
// command or chat starting), never when a chat opens with work already running.
// - It leaves on a kept snapshot: when the last line goes, the card's node is copied in place, over
// the bottom stack, and the copy fades and sinks while the real card is already gone, so nothing
// waits on the motion and the stack takes its new height at once. A chat switch plays nothing.
// - Its header's words tick (`Tick`): "2 running" turns to "3 running" with the old words leaving up
// and the new rising in, never a jump.
// - Its header's words tick (`Tick`): "2 running" turns to "3 running" with the old words fading out
// and the new fading in, never a positional jump.
// Reduce Motion, the host's motion switch (`data-agent-motion="off"`, a 0 ms token) and a page that
// is away apply every change at once, and no copy is made.
import { useCallback, useRef, useState, type ReactNode } from "react";
Expand Down
Loading
Loading