Fix sub-agent row clipping and add a connected-agent callout - #977
Merged
Conversation
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.
Uh oh!
There was an error while loading. Please reload this page.
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two related fixes to the Sub Agents section of the sidebar (child/review/persona agent rows under a parent's card):
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 adisplay: tableelement 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'soverflow-hiddencut the excess off entirely. Switched to a plainoverflow-y-autodiv, matching the pattern the outer agent list already uses for the exact same reason (agent-sidebar.tsx'sagent-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.
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-mutedtint). Now: a left accent bar + primary-tinted background/border on the row, plus a small presence dot on the corner of itsAgentTypeIcon.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— passpnpm run finalize:web— passpnpm run test:e2e— 181 passed, 12 skipped (pre-existing, tmux-dependent tests skipped in inert dev mode)🤖 Generated with Claude Code