Appearance
Workbench (browser UI)
The Smithers workbench is what you see when you open http://127.0.0.1:3100/apps/smithers/. It is registered as <smithers-app> (projects/matrix-3/packages/smithers/src/browser/SmithersApp.ts:143) and uses Pattern A — the shell IS the component, extending MatrixActorHtmlElement directly. The whole page is itself a Matrix actor; every panel inside is a Matrix custom element.
Layout
The shell composes the page through nested <mx-split> containers. Splits are persisted via WorkspaceActor ops keyed by SmithersSplitConfig (SmithersApp.ts:29-34):
ts
export const SMITHERS_SPLITS: SmithersSplitConfig[] = [
{ childName: 'main-split', preferenceKey: 'ui.mainSplit.position', defaultPosition: 22, min: 12, max: 40 },
{ childName: 'source-split', preferenceKey: 'ui.sourceSplit.position', defaultPosition: 70, min: 30, max: 90 },
{ childName: 'work-split', preferenceKey: 'ui.workSplit.position', defaultPosition: 50, min: 20, max: 80 },
{ childName: 'chat-split', preferenceKey: 'ui.chatSplit.position', defaultPosition: 60, min: 20, max: 85 },
];Split positions are stored in SmithersUiPreferences and read on every page load — so the layout you choose persists across reloads.
The seven panels
Each panel is a registered custom element defined in src/browser/workbench/ and listed in src/browser/index.ts:7-49:
| Panel | Tag | What it shows |
|---|---|---|
| Topology rail | <smithers-topology-rail> | Compact rail along the left edge: queues (ready / blocked / active / escalated), critical-path issues, blocker badges, ratchet count. Sourced from graph.topology-rail. |
| Source graph | <smithers-source-graph> | Center panel: dependency forest / neighborhood / critical-path / blast-radius / dependency-delta views, switched by WorkspaceActor.workspace.set-graph-mode. |
| Inspector | <smithers-inspector> | Right-of-graph panel: tabs overview, constraints, prompts, reviews, diff, session. Each tab is a typed projection from a daemon op. |
| Control palette | <smithers-control-palette> | The action surface: start/stop/steer/sign-off/dispatch buttons. |
| Agent chat | <smithers-agent-chat> | Live $activity frame stream from the agent's session. Re-uses ActivityFrame from @open-matrix/core. |
| Runner panel | <smithers-runner-panel> | Per-runtime status (which workers are online, last heartbeat, container ID). |
| Status bar | <smithers-status-bar> | Top: theme toggle, poll-interval picker, workstream-request indicator, supervisor health pill. |
| Constraint collector | <smithers-constraint-collector> | Approve / dispatch chat constraints into the convergence prompt. |
Plus <mx-split> from @open-matrix/core/framework/components/MxSplit is registered for the layout.
Inspector tabs
InspectorTab is 'overview' | 'constraints' | 'prompts' | 'reviews' | 'diff' | 'session' (SmithersApp.ts:43). Switching tabs is a WorkspaceActor.workspace.show-tab op:
| Tab | Source projection |
|---|---|
overview | graph.issue-inspector(N) — title, body, labels, blocker count, depender count |
constraints | graph.constraints-for(N) — the gathered ConstraintProjection |
prompts | The constraint-gathered prompt that will be (or was) sent to the agent |
reviews | smithers.review-queue for that issue — gathered review fan-in |
diff | The agent's working diff (file changes captured via code.event |
session | The raw session transcript from system.agents |
Theme
SMITHERS_THEME_TOKENS (SmithersApp.ts:44-117) defines dark and light palettes — exhaustive token sets covering surfaces, text, info/success/warning/danger states, code background, and dividers. Theme is selected via the status bar; selection persists in SmithersUiPreferences (SmithersUiPreferences.theme).
Polling
Smithers does not subscribe to push streams for everything; it polls graph.topology-rail and graph.issue-inspector at user-selectable intervals. Allowed values (SmithersApp.ts:42):
5000ms, 10000ms, 15000ms, 30000ms, 60000msDefault is whichever the persisted SmithersUiPreferences.poll.intervalMs says; the in-status-bar dropdown lets the user change it. Polling stops when the tab is hidden (browser visibilitychange) and resumes when it returns.
Activity frame routing
Live agent activity arrives as ActivityFrame envelopes on the bus. SmithersApp subscribes to its own subjects (filtered by the active issue's activityTo list maintained server-side) and routes each frame to either:
<smithers-agent-chat>if the frame is text/tool-call/tool-result,<smithers-runner-panel>if it is runtime/status,<smithers-control-palette>for state-change frames.
This split is what makes the chat panel feel "live" while keeping runtime status separate from conversational content.
Address conventions
Every Smithers UI op routes to one of two daemon mounts:
- UI state —
WorkspaceActoratsmithers.workspace. Ops:workspace.navigate,workspace.set-queue,workspace.show-tab,workspace.set-graph-mode,workspace.toggle-pin,workspace.set-overlay,workspace.fork,workspace.attach. - Domain actions —
SmithersSupervisoratsmithers. Ops:smithers.start,smithers.stop,smithers.steer,smithers.sign-off, etc.
A user clicking a button never bypasses the bus; every click maps to a real op. This is what DESIGN.md §1 calls the Projection Principle in UI form.
See also
Source:
projects/matrix-3/packages/smithers/src/browser/SmithersApp.ts:1-200,projects/matrix-3/packages/smithers/src/browser/index.ts,projects/matrix-3/packages/smithers/SMITHERS-UI-WORKBENCH-SPEC.md.