Skip to content

Fix sub-agent row clipping and add a connected-agent callout - #977

Merged
selfcontained merged 10 commits into
mainfrom
agt_a586213b000d/build-subagent-row-polish
Aug 19, 2026
Merged

Fix sub-agent row clipping and add a connected-agent callout#977
selfcontained merged 10 commits into
mainfrom
agt_a586213b000d/build-subagent-row-polish

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

What

Two related fixes to the Sub Agents section of the sidebar (child/review/persona agent rows under a parent's card):

  1. Fixed: right-edge clipping. Each row's right-side content (REVIEW badge, terminal icon, overflow menu) was getting clipped off past the sidebar's edge instead of fitting. Root cause: the sub-agents list used shadcn's ScrollArea, whose Radix viewport wraps its content in a display: table element that sizes to the content's natural (unshrunk) width instead of the container's — so a long child name never actually truncated via CSS ellipsis, it just grew the row wider until an ancestor's overflow-hidden cut the excess off entirely. Switched to a plain overflow-y-auto div, matching the pattern the outer agent list already uses for the exact same reason (agent-sidebar.tsx's agent-sidebar-scroll).

    Also moved the REVIEW badge out of the shrinking name label and into the row's right-side action cluster (grouped with the terminal/menu buttons), so the label is the only thing that gives way to a long name — it no longer competes with the badge for space.

  2. Added: a clearer connected-agent callout. There was no strong visual indicator for which sub agent is currently connected/viewed in the terminal (previously just a faint bg-muted tint). Now: a left accent bar + primary-tinted background/border on the row, plus a small presence dot on the corner of its AgentTypeIcon.

Verification

Reproduced the clipping bug live by seeding 4 sub agents (including long names and review-role agents) under a parent in an isolated dev stack, confirmed the terminal icon and overflow menu were entirely invisible past the 320px sidebar edge, applied the fix, and confirmed all rows now truncate/fit correctly. Verified the connected callout by attaching/detaching across multiple rows (including a review-role row) and confirmed the highlight moves correctly and clears on detach. Also checked the mobile layout (unaffected — full-width sidebar, same fix holds).

  • pnpm run check — pass
  • pnpm run finalize:web — pass
  • pnpm run test:e2e — 181 passed, 12 skipped (pre-existing, tmux-dependent tests skipped in inert dev mode)

🤖 Generated with Claude Code

The Sub Agents list used shadcn's ScrollArea, whose Radix viewport
wraps content in a display:table element that sizes to the content's
natural (unshrunk) width instead of the container's. A long child
name never actually truncated — it just grew the row until the
sidebar's overflow-hidden clipped the REVIEW badge, terminal icon,
and overflow menu off past the right edge entirely. Switch to a plain
overflow-y-auto div, matching the pattern the outer agent list already
uses for the same reason.
Also move the REVIEW badge out of the shrinking name label and into
the row's right-side action cluster (grouped with the terminal/menu
buttons) so the label is the only thing that gives way to a long name.
Add a clearer visual callout for the sub agent currently connected in
the terminal: a left accent bar + primary-tinted background/border on
the row, plus a small presence dot on its AgentTypeIcon. Previously
this was only a faint bg-muted tint, easy to miss at a glance.
Per Brad's feedback, the connected sub-agent indicator is now a solid
right-edge border (border-r-4, mirroring the top-level agent card's
own borderForAgentState treatment) instead of a left border + presence
dot: the dot was easy to miss, the thin border got visually lost
against the animated review-active gradient, and toggling the border
on/off shifted the row. Width is now reserved unconditionally via
border-r-4/border-r-transparent so only the color ever changes.
Also addresses persona review findings:
- The right-side action cluster (REVIEW badge, terminal button,
overflow menu) had default pointer-events, creating dead click
zones over the row-wide "open submitted review" overlay button on
ready-to-open rows. Cluster is now pointer-events-none with
[&_button]:pointer-events-auto so only real controls are clickable.
- Moved the ScrollArea display:table workaround into a shared
`fitContentWidth` prop on components/ui/scroll-area.tsx (with the
root-cause comment there) instead of a hand-rolled div, since the
same workaround already existed ad hoc in activity-pane.tsx and the
bug likely affects other ScrollArea call sites with shrinking
content.
- Removed now-inert `relative z-20` / redundant `shrink-0` left over
from the badge/action-cluster restructuring.
- Test coverage: badge groups with the action cluster (not the
truncating label), the cluster's pointer-events guard, the
mutually-exclusive ready-vs-connected border treatment, and that
toggling isConnected never changes border-r-width.
Per Brad's live feedback on the screenshots:
- Ready-to-open rows were showing a muted version of the same right-edge
border treatment as connected rows, reading as a confusing partial
"connected" signal on a row that wasn't. The ready branch now reserves
the same border-r-4 width as every row (so it never mis-aligns with or
jumps against its neighbors — caught live by both reviewers) but
explicitly keeps that edge transparent, so only the actually-connected
row ever shows color there.
- Squared off the row's right corners (rounded-l-lg instead of rounded-lg)
so the connected accent reads as one continuous bar instead of being
interrupted by a rounded corner partway through.
Also fixes two more review findings:
- The connected border now keys off `state === "active"` (matching the
top-level card's own agentVisualState condition) instead of the bare
isConnected prop, so a paused-but-still-attached agent or a dropped
terminal socket don't disagree with the top-level card about whether
the same agent is "connected."
- Fixed a rerenderWith test helper bug that silently dropped props set at
initial render time on rerender.
Brad's feedback on a screenshot: the ready-to-open row's right edge
had no border (it was intentionally transparent, to avoid echoing the
connected accent's color) — but that reads as the border looking cut
off / broken, not as "correctly not connected."
Both concerns turn out to be solvable at once: give every row's right
edge a real color, always, matching its own other three sides (neutral
border-border/60 by default, primary/45 when ready-to-open) — so every
row's border closes into a complete shape. The connected state is the
one exception: it colors ONLY the right edge, differently from the
other three, which is what makes it read as "this edge means
something" — and now it's the ONLY state that does that, since ready's
border is uniform on all sides instead of singling out the right edge.
Width (border-r-4) stays constant in every state as before, so
alignment and no-layout-shift both still hold.
…menu
Per Brad's feedback: the row itself is now click-to-attach/detach,
mirroring the top-level agent card's own row-click behavior
(agent-card-header.tsx) exactly — same data-agent-control="true" +
closest() opt-out convention for interactive descendants. The
dedicated terminal/detach icon buttons are gone; a stopped agent still
gets a dedicated Resume control since it isn't click-to-attach either
(matches the top-level card there too).
Opening a submitted review moves from a full-row overlay button into
its own "Open review" item at the top of the overflow menu. This was
originally implemented as "click connects AND opens the review" (one
combined action), but that races: attachToAgent's and
openSubmittedReview's navigate() calls target different routes (the
agent's own page vs. its parent's page with ?expandReview=), and
whichever fires last silently wins, stripping the other's URL state.
Separating them avoids the race entirely and gives each action its own
clear trigger — confirmed no live call site in this app actually
chains attach->navigate together for this reason.
Also:
- Removed the pointer-events-none/z-index scaffolding that existed
only to let clicks pass through decorative content to the old
absolutely-positioned overlay button — now unnecessary, since normal
DOM bubbling reaches the row's own onClick directly. DropdownMenuContent
is portaled, but React's synthetic events bubble through the
*component* tree regardless, so it gets its own data-agent-control
marker (verified live: without it, opening the menu also fired the
row's attach/detach).
- Updated the "review-row-open" tip copy and moved its TipSpot to wrap
the badge specifically, since that's the row's only always-visible
"this review is ready" affordance now.
- e2e: the "open submitted review" flow now opens the row's overflow
menu first.
Addresses 5 findings from a fresh frontend-ux-review round on the
click-to-connect/menu-based-review redesign:
- The row's data-agent-control guard only checked closest() from the
click target, which walks the real (portaled) DOM — but React's
synthetic events bubble through the *component* tree regardless of
where content is portaled to. TipSpot's popover (wrapping the REVIEW
badge) is a portal too, and wasn't marked, so dismissing the tip or
clicking "Learn more" also fired the row's attach/detach — the exact
navigate() race this PR was built to eliminate, just via a different
path. Fixed with one general guard (event.currentTarget.contains())
that covers every portal, not just the ones explicitly marked;
data-agent-control on DropdownMenuContent stays as a second,
redundant-but-harmless guard.
- Removing the dedicated terminal/detach buttons left no keyboard or
screen-reader path to a sub agent's terminal at all — the row's
click-to-connect has no non-mouse equivalent, and nothing replaced
the old buttons' accessible names. Added a "View terminal"/"Detach"
item at the top of the overflow menu, restoring that access.
- Clicking the ready-to-open REVIEW badge attached the terminal
instead of opening the review — confusing, since the badge is the
one element that visibly lights up for exactly this action, and the
worst case for a stopped reviewer (the common end state), whose row
click is otherwise a dead end. The badge is now its own <button>
when ready, with its own trigger — no race with the row's click,
and no menu detour required for the primary "review is ready" case.
- The click handler's detach branch used the raw isConnected prop,
which stays true through a mid-reconnect or dropped socket — exactly
what the connected-accent's stricter state === "active" condition
was written to exclude. A row that visually reads "not connected"
would still detach on click. Both the row's click and the new menu
item now use the same isConnectedActive condition the accent paints
with, so what the row shows and what a click does always agree.
isConnected is no longer read anywhere in this file (kept in the
prop type only, since agent-card.tsx still passes it).
Per review follow-up: isConnected is no longer read anywhere in
child-agent-row.tsx (state === "active" replaced it entirely). Removed
from ChildAgentRowProps and its only call site (agent-card.tsx).
…view status; Session details
Three changes from live feedback:
1. Rounded corners are back on every sub-agent row. The thick right-edge
accent is now exclusive to the connected row — unconnected rows get an
ordinary matching 1px border on all four sides (not a permanently
reserved thick-but-transparent or thick-but-muted edge, both of which
read as visually broken). The outer row footprint never changes size
either way; only the connected row's own border-r width/color differ
from the rest.
2. The REVIEW text badge is replaced with themed iconography: a muted
ClipboardList icon while a review is still in progress, swapping to a
colored ClipboardCheck once it's ready to open. The ready color is
status-working (green) specifically, not status-done/primary (blue) —
deliberately a different family from the connected accent so the two
signals never compete, the same concern that shaped the earlier border
redesign.
3. "Session settings" becomes "Session details" everywhere (the dialog
title, both its trigger labels, and the docs), and the dialog now shows
the same read-only info a parent agent's own expanded card already
shows in the sidebar — branch/worktree, IDE launch links, sandbox
state, and the latest event — by reusing the exact same components
(AgentCardDetails, AgentCardLatestEvent, AgentCardPhaseStatus) rather
than a second implementation. Previously a sub agent's row showed only
name/status/time with no way to see any of this. The supporting hooks
(useAgentDiffStats, useCopyText) and the isFullAccessEnabled pure
function are all agent-id/agent-object-only, so the dialog stands them
up itself instead of threading more props through agent-card.tsx.
- Session details dialog: force focus onto the Name input via
onOpenAutoFocus instead of Radix's default first-tabbable target
(was landing on "Copy worktree path" and popping its tooltip over
the branch info).
- Extract describeAgentStatus() (shared with ChildAgentRow) and use
it in the dialog instead of AgentCardLatestEvent's summary, so a
stopped/errored sub agent shows its real status instead of a stale
"Working".
- Give the review-pending indicator a context-aware aria-label
(paused/errored/in-progress) and switch it to the shared Button
ghost-primary variant, fixing a focus-ring inconsistency.
- Update docs-sections/agents.tsx and tips.ts, which still described
the removed REVIEW badge and attach/detach buttons.
Addresses review 762 findings #1543-#1546.
describeAgentStatus and reviewPendingLabel both tested isStopped
before status === "error", but isStopped is already true for errored
agents (agentVisualState returns "stopped" for anything that isn't
running/creating) — so the "Error" branches were unreachable and an
errored agent read "Stopped" everywhere instead.
Addresses review 762 finding #1547.
@selfcontained
selfcontained merged commit 093ff61 into mainAug 19, 2026
1 check passed
@selfcontained
selfcontained deleted the agt_a586213b000d/build-subagent-row-polish branch August 19, 2026 01:57
selfcontained added a commit that referenced this pull request Aug 19, 2026
Deep-dive of the docs-pane Status Events section against its two
sources of truth.
Verified clean: the five event types match dispatch_event's enum as a
set in both directions, done/waiting_user/blocked are exactly what
triggers browser + Slack notifications, the activity monitor's 3-minute
window and its "Activity detected" / "No recent activity detected"
strings are exact, and every Activity-page claim (year heatmap, active
hours, working-time stats, status breakdown) still renders.
Two drifts fixed:
- The sidebar claim only described top-level cards. #977 added
describeAgentStatus, which sub agent rows and the Session details
dialog use instead of the raw event label — a stopped or errored
agent reads Stopped/Error there rather than a stale "Working", and
one that hasn't reported yet reads "Running". None of those three
labels were in the documented set.
- "Startup rules tell the agent which event types to use" is only true
on the verbose branch of buildLaunchGuidance. With Settings → Agents
→ Launch guidance → Use short startup rules on, that rule keeps the
timing and the `blocked` distinction but deliberately leaves the type
list to the dispatch_event tool description.
Also adds an ambient tip for automatic status correction (since 0.21.9,
confirmed by ancestry against the v0.21.8/v0.21.9 release commits): the
Status Events section had no tip pointing at it, and a background loop
authoring "No recent activity detected" under an agent's name is
exactly the kind of thing a user reads as the agent's own report. Gave
that H3 an id so the tip can deep-link to it.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@selfcontained