Skip to content

feat(web): make the conductor and its workers legible in the session list - #320

Merged
saucam merged 4 commits into
mainfrom
feat/p5-1-conductor-legibility
Sep 5, 2026
Merged

feat(web): make the conductor and its workers legible in the session list#320
saucam merged 4 commits into
mainfrom
feat/p5-1-conductor-legibility

Conversation

@saucam

@saucam saucam commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

First slice of P5.1 (conductor-frontends-design.md §12).

Why

The conductor has been drivable since P3 — codeoid attach conductor works — but in the web UI it renders as an ordinary row called "conductor", and its dispatch workers appear as unrelated top-level sessions named worker-scout-c0234348. Nothing on screen says the fleet is a fleet.

What

Workers group under the conductor. They carry no parent pointer — the conductor→worker link lives in the dispatch queue, not on SessionInfo — but the daemon enforces one conductor per tenant, so it's an unambiguous parent.

This reuses the existing collaboration grouping rather than adding a second mechanism, keeping groupFleet's three properties: pure and testable without a reactive root; uniform shape so the renderer never branches on "is this a fleet"; and orphans promoted, not dropped — a worker whose conductor isn't in the list still renders at top level rather than vanishing.

isConductor is kept separate from isFleet rather than folded into one "has children" flag, because the two group kinds differ in what their children are: a collaboration's role-children are long-lived sessions you drive; dispatch workers are disposable and die with their task.

Two badges. cond marks the conductor in the product accent rather than a semantic colour — the status vocabulary (§6) owns warn/danger/success, and "this row is the conductor" is an identity, not a state, so a conductor in trouble must still read as error from its StatusDot. ship/scout marks a worker's contract, scout quiet and ship warn-coloured for the same reason WriteBadge flags a writing role.

Worker shape is parsed from the name (worker-<shape>-<task>) because SessionInfo carries no shape field — the ship/scout contract lives on the dispatch task, which the session list doesn't join against. Guarded on role === "worker" so a user-named session starting with worker- is never mislabelled; only the daemon sets that role.

Two smaller judgment calls:

  • Workers sort oldest-first with a name tiebreak, not createdAt alone — several can spawn in the same millisecond, and a list that reorders while you read it is worse than one that looks stale.
  • Filtering matches role and shape, so scout or conductor finds these rows.

Verification

  • 22 fleet unit tests (7 new), 35 across both lib suites
  • tsc -b, eslint, and the production vite build all clean
  • Name parsing checked against real daemon outputworker-scout-c0234348 with role: "worker", observed driving an actual dispatch

⚠️ Pre-existing gap this surfaced

No component test in web/ can run locally. The jsdom environment is broken: html-encoding-sniffer requires @exodus/bytes as CJS, but it resolves ESM-only —

Error: require() of ES Module .../@exodus/bytes/encoding-lite.js
from .../html-encoding-sniffer/lib/html-encoding-sniffer.js not supported.

That silently excludes 6 test files, including SessionListPane.test.tsx — which is exactly the file that would cover the badge rendering here. npm run test still reports green ("12 passed") with the failure demoted to an unhandled error, so it's easy to miss.

jsdom was also simply not installed despite being in package.json; installing it moved the failure from "package missing" to the ESM/CJS conflict above. bun.lock and package.json are untouched by this PR.

The badge rendering is therefore covered only by typecheck and build. Worth its own fix before P5.2 adds real components.

Next

P5.2 — the docked conductor surface: CenterPane branches on role === "conductor" with the state-grouped fleet rail, backed by a new state/fleet.ts over the fleet.subscribe contract (verified working end-to-end in #319's testing).

🤖 Generated with Claude Code

…list

First slice of P5.1 (docs/conductor-frontends-design.md §12). The
conductor has been drivable since P3 — `codeoid attach conductor` works —
but in the web UI it renders as an ordinary row called "conductor", and
its dispatch workers appear as unrelated top-level sessions with opaque
names like `worker-scout-c0234348`. Nothing on screen says the fleet is a
fleet.

Groups dispatch workers under the conductor. They carry no parent pointer
— the conductor→worker link lives in the dispatch queue, not on
SessionInfo — but the daemon enforces one conductor per tenant, so it is
an unambiguous parent. Reuses the existing collaboration grouping rather
than adding a second mechanism, keeping `groupFleet`'s three properties:
pure and testable without a reactive root, uniform shape so the renderer
never branches on "is this a fleet", and orphans promoted rather than
dropped (a worker whose conductor is not in the list still renders, at
top level, instead of vanishing).

`isConductor` is kept separate from `isFleet` rather than folded into one
"has children" flag, because the two group kinds differ in what their
children ARE: a collaboration's role-children are long-lived sessions you
drive, while dispatch workers are disposable and die with their task.

Adds two badges. `cond` marks the conductor, in the product accent rather
than a semantic colour: the status vocabulary owns warn/danger/success,
and "this row is the conductor" is an identity, not a state — a conductor
in trouble must still read as `error` from its StatusDot. `ship`/`scout`
marks a worker's contract, with scout quiet and ship warn-coloured for
the same reason WriteBadge flags a writing role.

Worker shape is read from the daemon's `worker-<shape>-<task>` naming
because SessionInfo carries no shape field — the ship/scout contract
lives on the dispatch task, which the session list does not join against.
Guarded on `role === "worker"` so a user-named session starting with
`worker-` is never mislabelled; only the daemon sets that role. Verified
against real daemon output (`worker-scout-c0234348`, role `worker`).

Workers sort oldest-first with a name tiebreak, not by createdAt alone:
several can spawn in the same millisecond, and a list that reorders while
you read it is worse than one that looks stale.

Filtering now matches on role and shape, so "scout" or "conductor" finds
these rows — the name alone only carries the shape by convention.

Verification: 22 fleet unit tests (7 new), 35 across both lib suites,
tsc + eslint + production build clean.

Known gap, pre-existing: the jsdom test environment is broken locally
(html-encoding-sniffer requires @exodus/bytes as CJS, but it resolves
ESM-only), so no component test in web/ can run — 6 files including
SessionListPane.test.tsx. The badge rendering is therefore covered only
by typecheck and build here. Untouched lockfile; needs its own fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@highflame-oracle highflame-oracle Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔮 Oracle Review

🎯 Start Here

web/src/components/SessionListPane.tsx (~20 min) — Security changes in SessionListPane.tsx


📋 PR Summary

What this PR does: Implements visual grouping and differentiation for conductors and their dispatch workers in the web session list UI, allowing workers to appear as children under their conductor row with role-specific badges.

Key changes:

  • Workers now group under conductor rows using existing collaboration grouping logic
  • Added 'cond' badge for conductors and 'ship'/'scout' badges for workers based on parsed session names
  • Implemented stable sorting (oldest-first with name tiebreak) and role-based filtering

Areas affected: SessionListPane.tsx (UI rendering and badges), fleet.ts (grouping and sorting logic), Session filtering behavior

Testing notes: 22 fleet unit tests (7 new), typecheck and build verified; component tests blocked by pre-existing jsdom/ESM compatibility issue unrelated to this PR


🔍 Code Review

This is a well-architected feature that thoughtfully reuses existing patterns rather than introducing parallel mechanisms. The implementation demonstrates strong attention to edge cases with proper orphan handling, stable sorting to prevent UI jitter, and appropriate role-based guards against spoofing.

What's good:

  • ✨ Reuses existing groupFleet mechanism instead of adding a second grouping path, maintaining architectural simplicity
  • ✨ Orphan promotion ensures workers render at top level if their conductor is missing, rather than vanishing
  • ✨ Role-guards prevent user-named sessions starting with 'worker-' from being mislabeled as workers

Review Stats: suggestion:2


Generated by Oracle - Highflame's AI Code Reviewer

Comment thread web/src/components/SessionListPane.tsx
Comment thread web/src/lib/fleet.ts
saucam and others added 3 commits September 5, 2026 22:32
Review follow-up on #320 (Oracle suggestion 2).

`worker-<shape>-<task>` knowledge was split across two functions:
`workerShape` matched the prefix, and `roleLabel` re-derived the same
prefix to slice the task tail. A change to the daemon's naming had two
call sites to find, and only one of them would have failed loudly.

Folds both into `parseWorkerName`, returning `{ shape, task }`. The
prefix and the shape list are named constants, and the two public
readers are thin accessors over it.

Puts the `role === "worker"` guard where it cannot be skipped. It was
already correct and tested (suggestion 1 asked only that it be
verified), but it lived in `workerShape` while `roleLabel` reached the
same name format independently. With one guarded parser, a caller cannot
opt out of the check by choosing the other entry point — asserted by a
test that spoofs `worker-ship-nope` through BOTH readers.

Two behaviours the consolidation made worth pinning: a daemon name with
no task tail renders as the bare shape rather than a trailing separator,
and an unrecognised shape (`worker-courier-…`) is not labelled a worker
at all rather than guessed at.

24 fleet tests (2 new), typecheck, lint and build clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@saucam
saucam merged commit 9d8d944 into main Sep 5, 2026
4 checks passed
Sign up for free to 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.

2 participants