Clean break, no compatibility alias. Renames the product name, the CLI binary (`ccam` -> `agenthub`), the env var prefix (`CCAM_` -> `AGENTHUB_`), the Prometheus metric namespace (`ccam_*` -> `agenthub_*`), the tmux session prefix (`ccam-lane-<id>` -> `agenthub-lane-<id>`), the per-repo runtime profile directory (`.ccam/profile` -> `.agenthub/profile`), the lanes state root (`~/.claude/ccam-lanes` -> `~/.claude/agenthub-lanes`), the bundled plugins (`plugins/ccam*` -> `plugins/agenthub*`) and their slash commands (`/ccam-doctor` -> `/agenthub-doctor`, `/ccam:*` -> `/agenthub:*`). Because there is no alias, an existing install has to be updated by hand: rename its `.ccam/profile` directory and any `CCAM_*` variables, and reinstall the plugin under its new name. Two other name families are deliberately left alone here, since they are not just strings: the `Claude-Code-Monitor` repository/marketplace name, and the `agent-dashboard` npm package plus its `~/.claude/agent-dashboard` runtime directory, which holds the live database and needs a migration rather than a rename. Verified: server 1406/1406, client 459/459, `grep -i ccam` clean across tracked files, `openapi.yaml` regenerated (95 paths, unchanged count).
6.5 KiB
Workspace UI rebuild — design
Status: approved 2026-07-29. Sub-project D, built on top of A (merged Workspace page). Reference: the Shipyard "Feature Harness" screen the user supplied.
Problem
The merged Workspace page shipped with the right information and the wrong shape.
Everything a lane knows — declared stage, inferred stage, progress, liveness,
needs-you — is already on the card (client/src/components/lanes/LaneCard.tsx)
and already correct on the wire. None of it is legible: the lane strip is a
horizontal scroller of cramped cards, the pipeline sits above a console that
dominates the viewport, and the auto: <stage> chip that proves detection works
is 10px of amber text nobody sees.
Measured on the live install while writing this: lane 5 carried
detected_stage: "tests" with a real signal, and the user's report was
"the lane does not auto-detect". Detection was never broken. The display was.
Two facts also make lanes look emptier than they are:
branchandci_statusare columns nobody writes, so those rows are always blank even for a managed worktree sitting on a real branch.- Detection is forward-only with no expiry, so a lane parks at the highest stage
it ever touched. Lane 5 reached
testsand can never showimplementagain, even while the agent is editing code.
Goal
The reference screen's legibility, on AgentHub's real data: a lane's state readable from across the room, the pipeline large enough to trace, and the console present but out of the way until wanted.
Decisions taken
- Card grid, not a strip. Responsive 1 / 2 / 3 columns.
- The console collapses. It keeps every capability from A; it starts collapsed and opens for the selected lane. Watching lanes is the default posture, driving one is the exception.
- Only real data. No placeholder tiles for facts AgentHub does not have (tickets, preview ports, per-lane credentials). Branch/commit/CI are added because they can be read for real — see below.
- Detection expires. A detection older than a TTL stops holding the floor.
Layout
Top to bottom, one column:
header: title · [N lanes][N running][N need you][N dead] · [+ Add lane]
detail: selected lane · declared + inferred headline · large PipelineMap · legend
console: collapsed by default; expands to RunSetup + RunConsole + RunHistory
grid: lane cards, 1/2/3 columns
Selecting a card switches the detail panel and the console together, exactly as
A wired it. The console is unchanged behind its new disclosure — no prop of
RunConsole, RunSetup or RunHistory moves.
The card
Reference layout, AgentHub's fields, nothing invented:
| Row | Content | Source |
|---|---|---|
| header | LANE <id> · liveness dot · status |
id, liveness, status |
| title | title, falling back to cwd |
existing |
| progress | declared stage chip · bar · % · time on stage |
stage, progress, stage_seconds |
| inferred | dashed amber auto: <stage> with the signal as tooltip |
detected_stage, detected_signal |
| tags | kind (adopted/managed), CI when known |
kind, ci_status |
| git | branch · short head · last commit subject · dirty/untracked counts | new, see below |
| alert | needs-you banner | needs_action |
| actions | start · stop · clear · reset · remove | existing lane actions |
reset and remove keep their preflight + expect echo through
DestructiveLaneModal. This redesign does not touch the destroy guard.
Git facts
A new read-only endpoint, GET /api/lanes/:id/git, returning
{branch, head, subject, dirty, untracked} or {available: false} when the
lane's cwd is not a git repo or is unreadable.
Deliberately not folded into GET /api/lanes: that payload is polled and
broadcast, and shelling out to git once per lane on the hot path would put a
subprocess burst behind every hook-driven lane_update. The card fetches its
own facts when it mounts and on a slow interval, and renders without them until
they arrive.
server/lib/worktree.js already has statusCounts(dir) returning
{dirty, untracked, head} and a git() wrapper that scrubs the inherited
GIT_* environment. Both are reused as-is; the endpoint adds only the branch
name and the commit subject. No second git helper, no shell strings.
Detection expiry
recordDetection gains one rule: a detected_stage whose detected_at is
older than DETECTION_TTL_MS (default 30 minutes) no longer blocks a new
detection — the forward-only comparison is skipped and the fresh signal wins.
Within the window nothing changes: forward-only and declared-wins hold exactly
as they do today.
This keeps the anti-flapping property that motivated forward-only (a Read
right after an Edit must not drag the lane backwards) while admitting the
thing it got wrong: a work session ends, and the next one starts somewhere else
in the pipeline.
Unchanged, and not negotiable: detection still never writes lanes.stage,
and an inferred node still never renders done.
Signal legibility
detected_signal currently captures the whole flattened tool input, so the chip's
tooltip reads cd /very/long/path && npm run test:server 2>&1 | grep …. The
matcher already knows which regex fired; the signal becomes the matched span plus
a little context rather than the entire command. Cosmetic, but it is the text the
tooltip exists to show.
Risks
- The console's disclosure is the only structural risk. Mounting it inside a
collapsed container must not unmount
useRunStreamand lose a live stream. The subscription stays mounted; only the visual container collapses. - Git calls per card. Bounded by the number of lanes on screen and a slow refresh; failure is silent and the card renders without those rows.
- The screens snapshot over
/runwill change. It is read, not regenerated blindly.
Testing
- The card renders every field from a fixture lane, and renders without the git
block when the endpoint reports
available: false. - A detected node still never carries
data-state="done"— the premise guard from sub-project B is re-asserted at the new layout. - Collapsing and expanding the console does not tear down the run subscription: a stream envelope delivered while collapsed is present when it re-expands.
GET /api/lanes/:id/gitreturns the facts for a real repo fixture andavailable: falsefor a plain directory, and never shells out through a shell.- A detection older than the TTL is accepted even when it is behind the current
detected_stage; one inside the window is still refused.