Skip to content

Split agent-card.tsx into header, status, details, and actions files - #803

Merged
selfcontained merged 2 commits into
mainfrom
componentizer/split-agent-card
Jul 20, 2026
Merged

Split agent-card.tsx into header, status, details, and actions files#803
selfcontained merged 2 commits into
mainfrom
componentizer/split-agent-card

Conversation

@selfcontained

Copy link
Copy Markdown
Owner

What

agent-card.tsx had grown to 829 lines. Its render tree contained four logically independent regions, each with its own props, state, and concerns. This splits them into sibling files, leaving the card as a composition shell.

Why it was a candidate

  • Well over the ~300-line threshold (829 lines, the top item in the componentizer backlog).
  • Four distinct UI regions in one JSX tree (header row, transient phase status, expanded details panel, expanded action footer).
  • Mixed concerns: the rename-prompt API call and copy-to-clipboard state lived at the top level even though each is used by exactly one region.

New file structure

FileLinesContents
agent-card.tsx829 → 267Composition shell: container, expansion animation, persona/sub-agent blocks, drag-handler effect, settings dialog
agent-card-header.tsx254Type icon, session name, rename prompt (state + handler moved with it), status badges, resume, expand toggle
agent-card-status.tsx160Setup/archive phase lines, latest-event summary, repo label
agent-card-details.tsx207Branch/worktree panel, diff stats, IDE launch, sandbox indicator (owns its own useCopyText state)
agent-card-actions.tsx123Persona launcher plus lifecycle controls (resume/pause, settings, archive)

Behavior

Purely structural. Markup, class names, data-testids, and handler wiring are unchanged. The only dedupe is the latest-event summary row, which the original repeated verbatim across its expanded/collapsed branches — both call sites still pass their exact original class strings.

Validation

  • pnpm run finalize:web — type check + production build pass
  • pnpm run test:e2e — 169 passed, 12 skipped
  • Playwright visual pass on an isolated dev stack: expanded card renders identically (header, event line, details panel, action row)

Next run

Queued: apps/web/src/components/app/jobs-pane.tsx (746 lines) — extract job list items, job detail panel, and data-fetching hooks.

🤖 Generated with Claude Code

selfcontainedand others added 2 commits July 20, 2026 02:06
agent-card.tsx had grown to 829 lines rendering four logically independent
regions in one render tree. Each region is now its own file, leaving the
card as a composition shell that owns only layout, expansion state, and
the drag-handler effect.
- agent-card-header.tsx (254) — type icon, session name, rename prompt
(state and handler moved along with it), status badges, resume, expand
toggle
- agent-card-status.tsx (160) — setup/archive phase lines, latest-event
summary, repo label
- agent-card-details.tsx (207) — branch/worktree panel, diff stats, IDE
launch, sandbox indicator (owns its own copy-to-clipboard state)
- agent-card-actions.tsx (123) — persona launcher plus lifecycle controls
- agent-card.tsx 829 → 267
Purely structural: markup, class names, and test IDs are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@selfcontained
selfcontained merged commit 83a5ca0 into mainJul 20, 2026
1 check passed
@selfcontained
selfcontained deleted the componentizer/split-agent-card branch July 20, 2026 08:14
selfcontained added a commit that referenced this pull request Jul 24, 2026
* Cover the agent-card split: header, status, details, and actions
PR #803 split agent-card.tsx into four presentational files, none of
which had direct unit coverage. Add agent-card.test.tsx, which renders
the real AgentCard (only /lib/api is mocked) and asserts the wiring that
a split like this is most likely to break:
- the connected-child override that neutralizes the parent's visual
state and drops its selection highlight
- header row click routing: attach, detach-and-collapse when already
connected, ignore clicks on controls, no-op while stopped
- the rename prompt's gating and its POST, plus the disabled window that
stops a double click firing twice
- setup/archive phase lines, and the latest-event line's terminal-agent
and expanded-message branches
- worktree vs plain working-dir details, full-access vs sandboxed, and
the copy confirmation surviving a collapse (it is owned by the card,
not the details panel, precisely so it does)
- pause vs archive routing to their own targets, and the persona card
hiding lifecycle actions
Verified non-vacuous: 17 source mutations, 17/17 caught.
framer-motion is mocked out so collapse/expand is synchronous — its exit
animation otherwise leaves the old subtree mounted for an indeterminate
number of frames.
Also gives the header's icon-only resume button an aria-label; it had no
accessible name at all, so assistive tech (and role-based queries) could
not reach it.
* Address review: fix a false green and make the timing explicit
The agentVisualState stub mapped `archiving` to "active", but the real
mapper in hooks/use-agents.ts treats anything that is not running or
creating as stopped. That made the `status !== "archiving"` half of both
resume guards untestable — deleting it left the suite green while the
UI would offer Resume on a card that is mid-teardown. The stub now
mirrors production, and two new mutants cover both guards.
Also from the review:
- drive the 1.5s rename re-arm and the 2s copy confirmation with fake
timers instead of racing the wall clock, and assert the re-arm rather
than only the initial disable
- install the jsdom globals through vi.stubGlobal / explicit teardown so
they cannot leak if per-file isolation is ever turned off
- spread the real framer-motion module into the mock, so a descendant
importing anything else from it fails loudly instead of opaquely
- drop a self-parenting persona fixture that silently tested the
fallback branch it appeared to be setting up
- scope the sub-agent count assertion to its own row
- correct a comment claiming the setup phase has a generic fallback
(it does not; the archive phase does, now asserted)
The header's resume button is labelled "Resume session" (matching its
tooltip) rather than "Resume", so it does not collide with the footer's
Resume button on an expanded stopped card.
Mutation battery re-run: 20/21 caught. The survivor is the unreachable
job- guard in hasDefaultSessionName, called out in the test file.
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