The lane strip's tiles are now drag-reorderable (Alt+←/→ is the keyboard
path), and a lane's name can be set in place from the detail header. The
"Lane 23 ·" prefix is gone from both — the bare `#23` stays on the tile,
because that number is how the CLI addresses a lane.
Card order is a per-viewer preference, not a lane fact: it lives in
localStorage (`agenthub.workspace.laneOrder`) beside the split-view layout
and writes nothing server-side, so `GET /api/lanes` and `agenthub lanes`
keep answering in id order. Renaming is the opposite — it is PATCH
/api/lanes/:id with {title}, the one lane column a user now types into
directly.
That made a patch value reachable by hand for the first time, so
`bindable` now rejects any non-scalar for every PATCHABLE column with
EBADFIELD, and the route answers 400 for that and for the
SQLITE_CONSTRAINT family, rather than handing better-sqlite3 a datatype
throw and reporting 500 on plain bad input.
The uncommitted-files tooltip became a real panel. `GET /api/lanes/:id/git`
now returns `files` as {path, status, added, deleted} records — deltas from
one `git diff --numstat HEAD`, null where git has none to report (untracked,
binary, renamed) — and the card renders them portalled to the body, so the
Workspace's own scroll box cannot clip the list.
GET /api/lanes/:id/git now returns `files`, the raw `git status --porcelain`
lines the dirty/untracked counts were already parsed from, capped at 20 so a
lane mid-`npm install` cannot ship thousands of paths through a polled
endpoint. The lane card hovers them as the tooltip on its
"N modified · N untracked" text, appending "… +N" when the cap bit.
No new subprocess and no new endpoint — statusCounts was counting those lines
and throwing them away.
Four more spots that spent height saying what was already visible or
saying too much of it.
**LaneCard** drops the progress bar and percent entirely — the pipeline
map right below the card already draws the same number as named,
coloured nodes, and a bar beside it was a worse second telling of it.
**SplitLayoutToggle** (Workspace) swaps "1 pane" / "2 pane" / "4 pane"
text buttons for the conventional square / two-column / 2x2-grid icons
(`Square`, `Columns2`, `Grid2x2`) most apps use for this. The words move
to `aria-label` and `title` rather than disappearing.
**RunSetup** merges the fresh/resume source picker onto the same row as
the Working directory / Model / Permission mode / Thinking effort fields
instead of a second row above them — settings left, New/Resume session
right. The session picker for an active resume still gets its own row
below, since it only appears in that one state.
**LaneConsolePane**'s "Run Claude" header goes from a two-row title +
wrapped blurb to one row (smaller icon and title), and the blurb itself
is trimmed to one short sentence, hidden below `sm` where it would wrap
again anyway.
`docs/LANES.md` updated to match (split-view description, lane-card
field list, and `agenthub stage`'s progress-bar wording).
Verification: `tsc --noEmit` clean, `npx vitest run` 508/508 (2 stale
progress-bar assertions in LaneCard.test.tsx replaced with one asserting
the bar is gone), `npm run build` clean, screens.snapshot diff reviewed
before regenerating (icon buttons + new blurb + reordered DOM only), file
headers audit clean.
The Workspace lane panel spent four rows saying what fits in two.
Strip tile: the stage badges move onto the id row and the progress bar
and percent go away — the detail panel's pipeline map right below shows
the same progress with the stage names attached.
Lane detail: the pipeline picker, stage badges, feature picker, router
select and the remove button merge into the single header row instead of
a second row under it, and the pipeline's name is no longer interpolated
into `laneHeader` — the picker beside it already says it. The git block
collapses from four lines to one and drops the cwd, which the header
above it already spells out.
The router select is relabelled Model, moves out of LaneCard into that
header row (`LaneRouterRow` is now exported), and reports its apply
outcome as a ✓/⚠ marker with the message in a tooltip rather than a
wrapped sentence that pushes every row below it down.
Drops the tracker / dev QC / CI wait badge row and everything feeding it
(`useLaneIntegrations`, `api.lanes.integration`, the `integrations.*`
strings): two of the three toggles have no consumer at all, and a badge
reporting a toggle nothing acts on costs a row of height to say nothing.
`agenthub lanes integration` and the API route are untouched.
Two gaps left by the AI Router feature: a profile could be saved without ever
being reachable, and a lane's router could only be chosen at creation.
**POST /api/ai-routers/test** answers the question saving cannot. It sends one
Anthropic-style message capped at a single output token to `<baseUrl>/v1/messages`
with a 15s timeout — the wire shape a claude lane's eight env keys point at, so
what is tested is what will actually run. A router that answers is a 200 here
whatever it replies: `{ ok, status, message }` carries the router's own words,
and `status` is null when nothing answered at all. Only bad input is a 4xx.
The stored key is only ever sent to the endpoint it was stored against. A blank
`apiKey` falls back to the key of the profile named by `name`, but only while
the submitted `baseUrl` still equals the saved one — so testing a retyped
endpoint needs a retyped key (`EBADKEY`). Without that rule this route would be
a way to make the server POST a saved API key to any URL a browser names.
Network failures report `error.cause`, not `error.message`: node's fetch reports
every transport failure as the same useless "fetch failed" and hides the part
the user can act on (ECONNREFUSED, ENOTFOUND, a TLS error) one level down.
**The lane card carries an AI Router select.** The Add-lane modal was the only
place a router was chosen, and a lane outlives its creation. Switching goes
through `POST /api/lanes/:id/router/apply` — never a lane PATCH — because the
DB column and the lane's own config on disk may only move together. A failed
apply puts the select back rather than leaving the card claiming a router the
lane did not get. Hidden when `native` is the only profile: a select with one
option is a control that cannot do anything.
Adds `Lane.router_profile` to the client type (the payload already carried it).
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).
The Workspace page rendered `ConsoleArea` from two sites, picked by
`currentLane`. Every flip of that value unmounted the whole console
subtree and mounted it again at the other site: the terminal died, the
run history went with it, and `LaneConsolePane`'s lane-switch effect
wiped a half-typed prompt. It flipped on every page load (lanes arrive,
`refreshLanes` selects `lanes[0]`), on a lane being removed, and on any
refetch that re-picked the selection.
`lane-detail` is now always rendered and holds the only `ConsoleArea`;
the header and info block are the conditional parts. Same rule the
RunConsole already followed one level down — collapse with CSS, never by
unmounting — now enforced above it too.
Second half: every poller fed `setState` a brand-new object graph each
tick, re-rendering the lane strip, the card and the console area several
times a minute on a completely idle dashboard. `keepIfSame` keeps the
previous reference when the payload is value-identical, applied to the
5 s run/session poll, the lane list and counts, the `lane_update` WS
branch, and LaneCard's git/runtime/locks pollers.
Verified: npm run test:client (44 files / 454 tests), tsc --noEmit
clean, check-headers.sh clean. The Run screen snapshot was regenerated —
the diff is the added `<section>` wrapper and one indent level, no
content change.
Move the rare destructive lane actions behind a LaneToolsMenu next to
the lane header, and show a liveness dot + status label + stage age
inline in the header instead of only in the card grid.
Adopting the main repo as its own lane now gets stage detection
(cwd matches, same as any other lane), and its card lists every
managed-worktree lane provisioned from it with a jump-to link.
Also add the two missing Skill-tool detect rules (implement, ship)
so detection covers all four Superpowers workflow phases, not just
plan/review.
Client-side rendering for the per-lane runtime facts (slot, ports,
database, Redis index, service liveness) added in the server-side
A1/A2 work, plus the doc updates (README, CLAUDE.md, docs/API.md,
client/server READMEs) describing the new profile.env keys, hook
environment contract, and REST endpoints.
Adds a working Dark/Light toggle (next to the language switcher, same row
as EN/VI) and re-themes the whole dashboard, not just the handful of
components that already used semantic tokens.
- Tailwind darkMode:"class" + CSS-variable color tokens (client/src/index.css,
tailwind.config.js): surface.0-5, border/border-light, accent/accent-hover,
fg.primary/secondary/muted, status.success/danger/warning. One class flip
on <html> re-themes everything — no per-element dark: variant pairs.
- useTheme() hook: localStorage-persisted, defaults to dark, no
prefers-color-scheme fallback (client/src/hooks/useTheme.ts).
- Mechanical, table-driven migration (scripts/migrate-color-tokens.mjs,
scripts/tokenize-status-colors.mjs, scripts/darken-status-colors.mjs) of
every raw neutral/gray/slate + emerald/red/amber Tailwind utility across
client/src onto the new tokens, so every badge/button/component pulls the
same shade per status/role instead of each picking its own.
- Palette values are the literal Radix Colors (radix-ui.com/colors) scale
constants — slate/blue/green/red/amber steps 1-12 — adopted after three
rounds of hand-picked values that kept overshooting (flat, then too dark,
then glaring); see docs/superpowers/specs/2026-07-31-color-redesign-
dark-light-mode-design.md for the full history and role mapping.
- PipelineMap: done/current/failed/passed-no-evidence/detected share one
visual language (border + text + translucent wash of the same status
color); `current` alone stays a solid accent fill, the one state that
gets to look bolder ("you are here").
- LaneCard: removed the stage/kind/auto-stage chips that duplicated the
Workspace lane-detail header already showing them.
Categorical/decorative hues (violet, indigo, cyan, teal, sky, rose, pink,
orange, yellow, and blue where it plays a role-coloring part e.g. message
bubbles) are deliberately out of scope — collapsing those onto shared
tokens would erase the distinction between different kinds of thing, not
a status.
Internal SmartGift build of a Claude Code monitoring dashboard.
Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.
Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.
Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.