Skip to content

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:

PanelTagWhat 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:

TabSource projection
overviewgraph.issue-inspector(N) — title, body, labels, blocker count, depender count
constraintsgraph.constraints-for(N) — the gathered ConstraintProjection
promptsThe constraint-gathered prompt that will be (or was) sent to the agent
reviewssmithers.review-queue for that issue — gathered review fan-in
diffThe agent's working diff (file changes captured via code.event
sessionThe 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, 60000ms

Default 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 stateWorkspaceActor at smithers.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 actionsSmithersSupervisor at smithers. 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.