@volter/supercode-ui
v0.1.120
Published
Composable default UI kit for Volter Harness-powered coding-agent experiences
Readme
@volter/supercode-ui
Repository reference: UI inventory ·
feature inventory ·
ideal Storybook first.
These repository guides describe current coverage and proposed components; the API below
describes this package. Workflow/task and job/run/delivery components are public, with native projections and a host
binding at /supervision. Routing and the other remaining families are tracked in the inventory.
supercode-ui is the composable default interface for Volter Harness's glue workflows: inspect a
native coding-agent session, continue it safely, switch harnesses, answer requests, hand it to a
terminal, and export it back without hiding fidelity or residue.
It is deliberately separate from both the headless supercode-client and any embedding shell.
The package does not inject iframes, open sockets, read native session files, persist credentials,
or assume Volter Browsers. A host supplies bounded public state and receives typed intents. This lets a
browser widget, editor panel, desktop app, or ordinary page share one capability-honest UI.
Compose a host layout with shared behavior
MessengerProvider owns shared navigation and conversation state. Put
MessengerSessions in a persistent sidebar and MessengerContent beside it; the
content retains the complete messenger's actions, receipts, recovery, settings,
and child-session inspector. useMessenger() exposes navigation and isInspecting/isOpening flags to host controls.
The responsive composition story demonstrates
single-pane mobile navigation and a persistent sidebar in wider containers.
<MessengerProvider state={state} adapter={adapter} initialView="new">
<aside className="scui-root"><MessengerSessions /></aside>
<section className="scui-root">
<MessengerContent showBack={false} starter={{
variant: 'starter', environmentLabel: 'This workspace',
suggestions: ['Explain this workspace', 'Fix the failing test'],
initialDraft: storedNewDraft, onDraftChange: saveNewDraft,
}} />
</section>
</MessengerProvider>Import these from /preact or /react (or their /messenger subpath).
The host supplies layout sizing. The optional centered starter uses the same
harness readiness, attachments, execution modes, and submission logic as the
complete messenger. Its persisted draft is separate from state.savedDraft,
which belongs to the attached conversation. An asynchronous start must settle
successfully before the starter clears its draft; merely becoming busy or
allocating a new runtime is insufficient.
SessionNavigation and SessionSearchDialog are standalone exports from /preact/sessions
and /react/sessions (also included in the main visual entry points). They need no provider
or router. Bind their callbacks to useMessenger() when composing a messenger, or to your
own navigation. The dialog filters the supplied inventory by default; a host may instead
supply results, onQueryChange, loading, error, and onRetry for remote search.
<SessionNavigation canStart={canStart} onNew={startConversation} onSearch={() => setSearchOpen(true)} />
<SessionSearchDialog open={searchOpen} state={state}
onClose={() => setSearchOpen(false)} onOpen={openConversation} />The dialog owns Escape handling, focus entry/return, empty results, and reuse of SessionRow.
See the navigation stories for complete interactions.
Storybook is the component contract
The package's complete development, documentation, and acceptance surface lives in Storybook:
Install its locked development dependencies with npm ci --prefix sdk/ui --ignore-scripts.
Mailbox integration stories use the public @volter/teams/session-follow export; the package
keeps that dependency in its own manifest so clean release installs can compile the stories.
npm run storybook --prefix sdk/ui
npm run build:storybook --prefix sdk/uiAcceptance stories import the same built public artifacts that consumers install; workflow/orchestration
compositions use native-shaped synthetic fixtures with those public exports. They cover every canonical
harness identity, loading phase, transcript primitive, native request, tool lifecycle, long plan,
session inventory, composer/continuation state, receipt, narrow messenger layout, and modular
override seam. The Chromium interaction and axe accessibility configuration covers stories in light and
dark consumer themes; the dated UI review
records what was actually executed and the remaining device/integration gaps. The static build is available through build:storybook; see the
validation guide for current automation.
The older browser fixture remains a low-level embed smoke test; it is not a parallel design system. New component behavior and hard-to-reach states belong in Storybook first.
Use the whole messenger
import { mountSupercodeMessenger } from '@volter/supercode-ui/embed';
import '@volter/supercode-ui/styles.css';
const mounted = mountSupercodeMessenger(document.querySelector('#agent'), {
state: initialState,
adapter: {
onIntent(intent) {
host.send(intent);
},
async pickContext() {
return host.pickFilesAndImages();
},
openKeyboardShortcuts() {
panel.showKeyboardShortcuts();
},
onClose() {
panel.close();
},
},
});
host.onState((state) => mounted.update(state));Take only the parts you need
import {
Conversation,
SessionList,
} from '@volter/supercode-ui/preact';
import { HarnessLogo, harnessLogoDataUrl } from '@volter/supercode-ui/preact/logo';
import { HarnessAdvisory, HarnessPicker, HarnessSettingsPanel } from '@volter/supercode-ui/preact/settings';
import { groupConversation } from '@volter/supercode-ui/core';The UI inventory lists all 58 public components,
including complete and composable messenger surfaces, transcript primitives, session inventory,
composer/context/images, child inspection, settings/readiness, and identity/activity components.
Pure state readers, selectors, formatters, and intent constructors live at supercode-ui/core
and have no DOM or Preact imports.
For genuine partial delivery, the preact/logo, preact/icon, preact/conversation, preact/sessions,
preact/composer, preact/settings, and preact/messenger subpaths are independently tree-shaken artifacts; taking
the logo does not pull in Markdown, the messenger, or session-list code.
harnessLogoDataUrl(id) gives non-Preact launchers the same canonical mark: a self-contained SVG data
URL, or the brand's URL for Volter Harness's own ids (supercode, orchestrator); it returns null for an unknown harness rather than inventing a fallback identity.
React and Preact use the same component sources
React applications import the native React build instead of isolating a Preact root or aliasing their application runtime:
import { SupercodeMessenger } from '@volter/supercode-ui/react/messenger';
import type { MessengerComponents } from '@volter/supercode-ui/react';
import '@volter/supercode-ui/styles.css';
const components: MessengerComponents = { SessionRow: ProductSessionRow };
return <SupercodeMessenger state={state} adapter={adapter} components={components} />;The react, react/logo, react/icon, react/conversation, react/sessions,
react/composer, react/settings, and react/messenger exports are generated from the exact same
JSX sources as their preact/* counterparts. Consumers install only the renderer they use; both
are optional peers, and a React host never loads Preact through the React entry points.
Compact host launcher
Overlay extensions and host applications can use the canonical activity projection without recreating Volter Harness's attention rules:
import { AgentActivityLauncher } from '@volter/supercode-ui/react/activity';
<AgentActivityLauncher state={snapshot} showSummary onOpen={() => openMessenger()} />projectAgentActivity() is also available from the renderer-free core entry. It selects the
highest-priority session (needs-input → failure → working → unread), reports one aggregate unread
count, and exposes the selected opaque session key. The default launcher is themeable and allows
its badge to overflow without being clipped.
Host navigation and context
Hosts do not need to fork the messenger to connect their own object model:
<SupercodeMessenger
state={state}
adapter={{
onIntent,
confirmIntent: (intent) => showTakeoverConfirmation(intent),
}}
navigation={{
id: selectedObject.revision,
view: 'new',
harness: 'claude-code',
draft: 'Inspect this selection',
context: [selectedObject.asTranscriptContext()],
}}
composerCommand={{
id: browserCapture.revision,
action: 'attach',
attachments: browserCapture.asTranscriptAttachment(),
}}
contextCandidates={visibleObjects.map(asTranscriptContext)}
components={{ ContextCandidate: ObjectPreview }}
slots={{
listHeader: () => null,
listToolbarActions: NewConversationButton,
beforeSessions: ProjectThreads,
}}
/>navigation is an event, not duplicated router state: change its id to open a conversation or
prefill either an existing or new one, while onViewChange observes user navigation.
composerCommand is a separate event-like host seam: focus focuses the active composer and
attach appends bounded context without replacing its draft or existing attachments. On New Chat,
an attachment selects the compatible headless lane when the remembered terminal lane cannot carry
structured context. Context candidates use the exact
bounded TranscriptAttachment envelope sent to the harness; a custom candidate component changes
presentation without inventing a second context protocol. beforeSessions and afterSessions
place host-owned rows inside the same searchable scroller. confirmIntent runs before resume,
join, branch, reduction, or export so a host can explain ownership and destination before action.
Harness inventory also retains normalized auth, runtime, protocol, repair, and action-capability
evidence. The new-chat view renders unavailable harnesses in a compact HarnessReadiness section;
hosts can replace that component while continuing to consume the same readiness contract. For an
installed Claude Code or Codex that needs authentication, the default component emits the single
authenticateHarness intent. A trusted host constructs the client's
HarnessAuthenticationController with a visible terminal/process adapter and supplies it to the
controller binding as authentication. The shared controller selects the native method, handles
timeout/cancellation/verification, refreshes inventory after success, and exposes only sanitized
progress to the default component. The browser-only UI never receives a launch command, spawns a
local process, or handles credentials itself. onAuthenticateHarness remains a legacy escape hatch
for hosts that have not adopted the shared lifecycle.
When a controlled runtime truthfully advertises native steering, the composer sends a plain-text
message into the active turn and keeps a separate secondary action for queueing a follow-up. With
attachments—or with a harness that cannot steer—the same input remains queued for the next turn.
The distinction is capability-gated from the harness adapter through availableActions.steer; the
UI never infers steering support from a generic send method.
Bind directly to the headless controller
Trusted desktop, editor, and Node hosts can avoid rewriting ordinary snapshot and action glue:
import { createControllerBinding } from '@volter/supercode-ui/controller';
const binding = createControllerBinding(controller, {
authentication,
authenticationRequest: () => ({
environment: isRemoteClient ? 'headless' : 'local_browser',
cwd: projectRoot,
}),
onDraft: saveDraft,
onAcknowledge: clearAttention,
onArtifact: materializeArtifactInTrustedHost,
});
binding.subscribe((state) => mounted.update(state));
mounted.update(binding.getState());The binding maps standard controller operations, including verified reduce-and-continue, and projects its durable reduction receipt directly. Host-owned session-list pagination, durable attention, drafts, and artifact materialization remain explicit callbacks. Transcript pagination dispatches to the controller by default. A browser should receive projected state from a trusted host rather than instantiate a local controller or gain filesystem authority.
Bind across a process boundary
Browser-hosted products use the transport-neutral host binding instead of recreating snapshot ordering and intent dispatch around the same controller:
import { createRemoteControllerHost, createRemoteUiBinding } from '@volter/supercode-ui/host';
// Trusted process
const host = createRemoteControllerHost(controller);
events.send(host.getFrame());
host.subscribe((frame) => events.send(frame));
http.onIntent((intent) => host.dispatch(intent));
// Browser
const binding = createRemoteUiBinding({
initialFrame,
dispatch: (intent) => http.postIntent(intent),
attention: {
state: loadAttention(),
onChange: saveAttention,
},
});
events.onFrame((frame) => binding.receive(frame));Each serializable frame carries a host-process identity, workspace generation, monotonic sequence, controller revision, canonical UI state, and stable opaque reconnect identities. The browser store rejects duplicate and stale frames, including delayed action responses from a retired host process. The optional attention tracker baselines initial inventory without inventing unread dots, persists by stable opaque identity, and marks only newer conversation evidence or a proven runtime completion. HTTP, SSE, WebSocket, authentication, and product shell behavior remain host-owned transports.
A native-store inbox can use createNativeSessionAttentionTracker instead. It consumes projected
session rows plus their native descriptors, persists opaque message cursors, ignores tool and
heartbeat churn, treats compaction as a new baseline, and returns the delay for one host-owned
settlement timer. This keeps unread and finished semantics identical in an editor, extension,
desktop app, or mobile companion without moving file persistence into the UI package.
createNativeMessengerState wraps that ledger with bounded per-conversation drafts and per-harness
Terminal/Headless preferences. It emits one serializable snapshot, so native hosts do not need to
duplicate state parsing or coordinate several independent maps. The host still owns where that
snapshot is stored, its debounce and encryption policy, and any multi-device synchronization.
Native continuation exposes one action and a quiet execution-transport selector. A host with a real
terminal provider can add terminal to continuationModes and handle onResumeTerminal; Terminal
is then the initial choice and Headless remains available from the selector. The UI never infers
terminal support from a generic runtime handoff, and never presents a terminal transport when the
host cannot create one.
New-session execution is advertised per harness. Set launchModes: ['headless', 'terminal'] and
preferredLaunchMode on a HarnessOption; the complete messenger renders the same compact Terminal
versus Headless selector in the composer footer and emits that mode on the new intent. Direct
controller bindings delegate Terminal only through onStartTerminal, so a terminal selection can
never accidentally create a second headless runtime. Hosts may remember the preference per harness;
the messenger also retains the current browser draft choice while its New Chat view is open.
The default controller projection is display-bounded: 120 visible transcript rows, at most 480
native entries inspected to fill that tail, 16,000 characters per independent entry field, 100
session rows, 20 fidelity-residue details, and 50 subagents. It filters harness-injected context
before counting visible rows, retains native timestamps and typed tool/request lifecycles, and keeps
the uncapped residue count truthful. Hosts can narrow those limits with ClientProjectionOptions
and can overlay a machine-wide session inventory, pagination state, attention, drafts, attachment
errors, and owned/attached identities without reimplementing transcript or capability semantics.
projectSessionInventory performs the corresponding title, latest-preview, activity, path, age,
sorting, and row projection for raw machine-wide descriptors; the trusted host supplies only its
opaque-key callback and retains the reversible locator map. Its isWritable capability callback
is fail-closed: unread badges remain neutral until the host proves a real send or terminal-control
path. Runtime activity remains independent, so a read-only channel can still truthfully show that
its external agent is working or needs input.
matchesSessionRef, projectAttachedSession, and formatWorkspacePath complete that trusted-host
adapter so products do not need local copies of active-session matching, fallback header identity,
or home-relative path formatting.
Modularity contract
- Every component accepts data and callbacks. No component reaches into a global controller.
- The complete messenger accepts
slotsfor high-level replacement andcomponentsfor row-level replacement. Replacements receive the same typed, capability-filtered props as defaults. slots.headerActionsadds compact product controls to the default list, chat, and new-chat headers without replacing their navigation, status, or accessibility behavior.slots.listHeadercan delegate list chrome to the host, whileslots.listToolbarActionsadds controls beside the package-owned search field without duplicating search state.- Every styled element carries a stable
scui-<part>class and states asdata-*attributes; CSS custom properties are the theme API andclassName/style/classNamesthe styling API (see Styling). styles.csscontains both neutral tokens and default component rules. Teams may load it whole, override tokens (see Theme tokens), or omit it and style the stable markup themselves.- Component state is local presentation state only (navigation, disclosure, search, drafts and
scroll position). Machine/session truth always arrives through
state. - Unknown harness marks are a contract error.
HarnessLogorenders nothing and callsonMissingLogo; it never invents initials that disguise an unsupported harness. - Expensive transcript details are mounted only when disclosed. The host remains responsible for bounding and paging transcript/session state.
Styling
The package follows the conventions of modern component libraries, so the usual tools work without fighting it.
Cascade layer. Every rule in styles.css sits in @layer scui (scui.tokens,
scui.components). Any unlayered CSS of yours wins over it regardless of specificity. With
Tailwind v4 (or any layered setup), declare the order once, before your imports, so utilities
beat component defaults:
@layer theme, base, scui, components, utilities;
@import "tailwindcss";
@import "@volter/supercode-ui/styles.css";Keep global element resets layered or scoped: an unlayered button { color: inherit } would
override the package's buttons too.
Anatomy. Every styled element carries one part class, scui-<part>, and its state as
data-*/aria-* attributes (.scui-session[data-active="true"],
.scui-tool[data-status="error"]). Every package rule is one part class plus state, so one class
of yours overrides it. parts.d.ts exports ScuiPart, the generated list of every part name.
Props. Every visual component accepts className and style (applied to its root) and
classNames, a map from part name to your classes. A map given to MessengerProvider,
SupercodeMessenger or any outer component reaches every part rendered beneath it:
<MessengerProvider state={state} adapter={adapter}
classNames={{ 'message': 'rounded-3xl', 'tool-head': 'text-sm text-zinc-500', 'send': 'bg-black' }}>Copy and host regions. Every component also accepts labels and slots, and like
classNames a map given to an outer component reaches every part beneath it. Labels given to
MessengerProvider also reach the settings and subagent parts inside chat. labels overrides
any key of a family's defaults, each exported from that family's entry: SUPERVISION_LABELS
and NATIVE_MANAGEMENT_LABELS and INVENTORY_LABELS (/preact/supervision),
SETTINGS_LABELS (/preact/settings), TEAM_LABELS (/preact/teams), SUBAGENT_LABELS
(/preact/subagents), ACTIVITY_LABELS (/preact/activity), and the messenger's
DEFAULT_LABELS (the root entry; typed as MessengerLabels), which covers every string of the
chat, transcript, session list, composer, image viewer and code blocks, and the error and
untitled-chat fallbacks core supplies (presentError, sessionDisplayName, activitySummary
and relativeAge take the same labels). The same paths exist under /react. {name} placeholders are filled in, so
a translation is plain JSON. Optional copy (eyebrows, subtitles, notes, footnotes) is not
rendered when empty. Prefixed keys rename a native word wherever it appears: status:<word> in
workflow, jobs and inventory, runtime:<state> and auth:<state> in settings, activity:<state>
in subagents, and in chat toolRunning:, toolDone: and toolFailed:<category>,
activityOne: and activityMany:<category>, argument:<name>, language:<fence id>,
fidelity:<level>, mode:<terminal|headless>, plus the override-only operation:<name>,
action:<text>, toolStatus:<status>, trigger:<kind> and crossSurface:<state>. slots adds host content at named regions. Each region is a component of
{ value }, where value is that region's model:
<WorkflowBoard board={board} layout="board"
labels={{ searchTasks: 'Filter issues', boardSubtitle: '', 'status:running': 'In progress' }}
slots={{ laneStart: ({ value }) => <StatusIcon lane={value.lane} />,
taskEnd: ({ value }) => <Assignee name={value.assignee} /> }} />Board regions are headerActions, toolbarActions, laneStart and laneEnd. Cards have
taskStart and taskEnd, task details have taskDetailsActions and taskDetailsEnd, and each
attempt has attemptEnd. Jobs have jobStart, jobEnd, headerActions and toolbarActions.
Runs have runStart and runEnd, and run details have runDetailsActions and
runDetailsEnd. A host whose slots carry a card's id or status hides the package's rows with
--scui-task-meta-display: none and --scui-task-status-display: none.
The other families' regions:
- Settings:
managementHeaderActions(value: the harness list),harnessCardActionsandharnessCardEnd(value: a harness),configurationHeaderActionsandconfigurationEnd(value: the report),controlEndandsettingEnd(value: a control),harnessOptionEndandreadinessItemEnd(value: a harness option). - Native management forms:
formActionsandformEnd. - Teams:
sessionStartandsessionEnd(value: a session),machineStartandmachineEnd(value: a machine),machineDetailActionsandmachineDetailEnd,transcriptHeaderActions,paneActions(value:{ machine, pane }),viewHeaderActions(value:{ me, team }) andtabsEnd. - Source inventory:
rowStartandrowEndon every list,detailActionsanddetailEndon every detail view, andapprovalActions. - Subagents:
subagentHeaderActionsandsubagentEnd.
The messenger's own regions are its slots (including sessionStart and sessionEnd inside each session row) and components.
Tokens. Colors, type, radii, shadows, controls and per-component values are custom
properties; see Theme tokens. --scui-scaling multiplies all spacing and control
sizes (0.85 is IDE-dense, 1 the default, 1.15 roomy).
Containers. Each .scui-root is the named container scui: components adapt to the width
they are given, not the viewport, and your CSS can do the same with @container scui (...). A size container takes its width from its parent, never its content: the root's width is
--scui-width (420px by default) capped at 100%; set --scui-width: 100% to fill a pane, and
give a shrink-to-fit parent (a centered flex item, width: fit-content) a width of its own.
Unstyled. Omit styles.css and style the part classes yourself; markup and behavior do not
depend on it.
examples/web restyles one shell as Claude,
ChatGPT and VS Code's Chat view, and frames every family as GitHub, Linear, Jira and Vercel, with token files only; Storybook's Look toolbar renders every
story in each.
Upgrading a host to the styling anatomy
- Element resets. Package rules are now layered, so an unlayered
button { … }or.shell button { … }in your CSS overrides package controls. Move element-level defaults into a layer declared before the package:@layer app-base, scui;then@layer app-base { button { … } }. Your class rules can stay unlayered. - Modifier classes became attributes:
scui-starter→.scui-chat[data-variant="starter"],scui-new-starter→.scui-new[data-variant="starter"],scui-drop-target→.scui-envelope[data-dropping="true"],scui-pending→.scui-message[data-pending="true"],scui-loading-compact→.scui-loading[data-compact],scui-domain-positive|danger|warning→.scui-domain-badge[data-tone],scui-domain-has-detail→.scui-domain-workspace[data-detail]. - Palette. Tokens inherit from any ancestor; setting them on each
.scui-rootstill works but is no longer needed. - Root width.
.scui-rootis a size container with width--scui-width(420px) capped at 100%; set--scui-width: 100%to fill a pane. - Header actions. Buttons you pass through
slots.headerActionstake the package look withclassName="scui-icon-button".
Theme tokens
A theme is a set of custom-property assignments on any ancestor of the components (or on the
.scui-root itself). No token is re-declared inside the package, so a value set anywhere up the
tree reaches every component, and one theme can restyle a whole product without a selector
override. examples/web restyles the same shell as
Claude (/claude) and ChatGPT (/chatgpt) with token files only.
Defaults are the Volter brand's roles. Every colour, shadow and font default is one of the
Volter brand's roles, read as var(--volter-<role>, <brand value>):
--scui-bg is var(--volter-surface-page, light-dark(…)), --scui-accent is
--volter-action-default, --scui-font is --volter-font-ui. A host that sets --volter-* (a
Volter product loading the brand's tokens.css) governs every default at once, a host that sets
--scui-* overrides the one it sets, and a page that sets neither gets the brand's own values.
The brand values are resolved when the package is built; the stylesheet fetches nothing.
| Primitive | Brand role |
|---|---|
| --scui-bg, -bg-raised, -fill | surface.page, surface.raised, surface.subtle |
| --scui-fg, -muted | text.primary, text.muted |
| --scui-border, -border-strong | border.default, border.strong |
| --scui-accent, -accent-fg | action.default, text.onStrong |
| --scui-positive, -warning, -danger | status.healthy.base, status.attention.base, status.danger.base |
| --scui-shadow-sm, -md, -lg | shadow.floating, shadow.floating, shadow.dialog |
| --scui-font, -font-mono | font.ui, font.data |
Harness logos for third-party agents keep their vendors' colours.
Primitives (declared on :root, override any of them):
- Palette:
--scui-bg,--scui-bg-raised,--scui-fill,--scui-fg,--scui-muted,--scui-border,--scui-border-strong,--scui-accent,--scui-accent-fg,--scui-positive,--scui-warning,--scui-danger. Defaults uselight-dark(), so one declaration covers both schemes. - Scheme: components use
color-scheme: var(--scui-color-scheme, light dark), following the OS by default. Set--scui-color-scheme: dark(orlight) to force one, orinheritto follow the host's owncolor-scheme. - Type:
--scui-font,--scui-font-mono,--scui-line-height, and the scale--scui-text-2xs,-xs,-sm,-md,-lg,-base(the root size),-xl,-2xl,-3xl,-4xl,-5xl. - Shape:
--scui-radius-xs,-sm,-md,-lg,-xl,-2xl,-pill;--scui-shadow-sm,-md,-lg. - Spacing:
--scui-scalingmultiplies every padding, gap and control size (default 1). - Frame:
--scui-width,--scui-height,--scui-radius,--scui-root-border,--scui-head-height,--scui-intrinsic-width(the width a root measures inside a shrink-to-fit parent).
Component tokens are never declared; each is read with its default as a fallback where it is used, so an unset one tracks the primitives wherever those are overridden:
| Surface | Tokens |
|---|---|
| Controls | --scui-button-bg, -fg, -border, -radius; --scui-icon-button-radius; --scui-press-transform (buttons, icon buttons and session rows while pressed, e.g. translateY(1px)); --scui-input-bg, -border, -radius; --scui-primary-bg, -fg (shared parts scui-button[data-variant], scui-icon-button, scui-input) |
| Page titles | --scui-heading-font, --scui-heading-weight (workflow, orchestration, source, agent and harness views) |
| Surfaces | --scui-card-radius (cards, request, tool detail, plan, domain panels); --scui-popover-radius, -shadow (menus, pickers, image viewer) |
| Header | --scui-head-bg, -border, -padding, -title-size, -title-weight, -logo-display, -subtitle-display, --scui-head-display (none hides a header, e.g. a session list set inside a host panel) |
| Conversation | --scui-conversation-width (column max width), --scui-conversation-padding, --scui-turn-gap |
| Assistant prose | --scui-prose-font, -size, -line-height, -gap, -fg, -heading-font, -heading-weight |
| User message | --scui-user-bg, -fg, -border, -radius, -padding, -max-width, -align, -font, -size, -line-height |
| Timeline | --scui-message-display (grid lays each message out as columns), --scui-message-columns, -column-gap; --scui-message-actor-display (shows the time-and-speaker cell, worded by the actor:user and actor:assistant labels), -actor-columns, -actor-dot, -actor-dot-bg, -actor-user-dot-bg; --scui-entry-inset (tool groups, reasoning and plans line up under the content column) |
| Tool rows | --scui-tool-head-padding, --scui-tool-actor-width, -actor-gap (the tool's timeline cell, shown with --scui-message-actor-display and worded by the actor:tool label), --scui-message-actor-tool-dot-bg; --scui-tool-icon-display, -status-display, -chevron-display; --scui-tool-detail-inset, -detail-border, -detail-max-height; --scui-tool-metrics-display, -fields-display, -actions-display, -technical-display (each none hides that part of an opened tool); --scui-tool-output-bg, -output-padding, -output-max-height; --scui-terminal-bg, -gap, -head-bg, -head-border, -head-padding, -lights-display, -command-fg. With groupTools={false} on the messenger each tool call is its own row, and expandTools opens their details. |
| Code | --scui-inline-code-bg, -fg, -border, -radius, -padding; --scui-code-bg, -fg, -border, -radius, -size, -head-bg, -head-border |
| Requests | --scui-request-bg, -border, -padding, -shadow, -allow-bg, -allow-fg (options carry data-kind) |
| Reply actions | --scui-reply-actions-position (static puts copy/time as a row under each assistant reply), -top, -opacity, -pointer-events, -bg, -border, -shadow, -padding, -gap, -justify, -width; --scui-message-time-display, --scui-message-action-size, --scui-message-action-icon-size |
| Tools | --scui-tool-size, -weight, -fg; --scui-tool-target-size, -bg, -fg, -padding, -radius (the command or path chip) |
| Composer | --scui-compose-bg, -border, -padding, -width; --scui-composer-size; --scui-envelope-bg, -border, -radius, -padding, -shadow; --scui-send-bg, -fg, -radius, -size; --scui-stop-bg |
| Starter | --scui-starter-flow (row wrap puts the mark beside the title), --scui-starter-mark-gap, --scui-starter-hint-basis, --scui-starter-bg, -title-font, -title-size, -title-weight, -title-tracking, -mark-display, -mark-color, -mark-size, -hint-display |
| Session rows | --scui-list-toolbar-display (none hides the search bar), -gap, -padding; --scui-search-height, -padding, -gap, --scui-search-flex (0 0 34px collapses search to its icon until focused), --scui-session-radius, -bg, -border, -spacing (the gap below each row), -shadow (e.g. an inset rule between rows), -padding, -align, -title-size, -title-weight, -active-bg, -hover-bg, -list-padding, -logo-display, -logo-size, -path-display, -preview-display, -rail-display |
| Workflow and jobs | --scui-domain-bg, -border, -radius, -title-size, -header-display, -header-padding, -schedule-padding (a job's schedule strip), -toolbar-display, -toolbar-padding, -toolbar-border, -toolbar-align, -search-columns (auto minmax(0,1fr) sets the search label beside its field), -search-size (search and filter labels), -control-height (buttons, selects and search fields), -meta-size (subtitles, card meta and footnotes), -heading-size (task, job and run titles in their details), --scui-focus-target-outline (the ring on a heading, list or panel focused as it opens), -switch-display, -collection-padding, -detail-border, -detail-border-top, -detail-bg, -detail-columns (minmax(0,1fr) puts task and run details below the list); --scui-eyebrow-transform, -tracking; --scui-board-gap; --scui-lane-bg, -border, -radius, -padding, -min-height, -title-font, -title-size, -title-weight, -title-fg, -title-transform, -title-tracking, -title-justify, -title-gap, -title-padding; --scui-lane-count-bg, -fg, -radius, -padding, -size, -weight; --scui-task-bg, -border, -radius, -shadow, -padding, -gap, -spacing, -hover-bg, -flow (column lays a card out as one row), -columns, -align, -title-order (-1 puts the title first), -title-size, -title-weight, -title-fg, -meta-display, -status-display; --scui-badge-bg, -border, -radius, -padding, -size, -weight, -transform, -dot-display; --scui-run-padding, -border |
| Settings panels | --scui-panel-width (100% stretches them in a flex host), -padding, -max-width, -margin; --scui-harness-card-bg, -border, -radius, -padding, -gap; --scui-harness-management-title-size; --scui-configuration-form-bg, -form-border, -form-radius, -form-padding, -title-size, -label-font, -label-size, -label-weight, -label-tracking, -label-transform; --scui-settings-header-bg, -header-border |
| Subagents | --scui-subagent-bg, -row-bg, -row-radius, -row-hover-bg, -history-border |
| Source inventory | --scui-inventory-row-bg, -border, -radius, -padding, -gap, -title-size; --scui-inventory-nav-gap, -padding, -border, -bg, -active-bg, -active-weight |
| Teams | --scui-team-* for every surface of the team view: bar (-bar-height, -bar-bg, -bar-border, -title-size, -bar-copy-display: none hides the team name and role for a host that shows them), tabs (-tab-height, -tab-radius, -tab-active-bg, -tab-active-color), rows (-session-padding, -machine-padding, -row-radius, -row-hover-bg, -row-active-bg), presence dots (-dot-size, -dot-online-bg), transcript badges (-live-bg, -coverage-bg), detail panels (-panel-bg, -panel-border, -panel-radius), sign-in (-sign-in-width, -sign-in-radius) and the pane terminal (-terminal-bg, -terminal-border, -terminal-radius); the full list is in src/styles/teams.css |
Starter copy is a label: labels.starterMark (the glyph; '' shows none), labels.starterTitle, labels.starterHint and labels.startPlaceholder
join askAgent and the other MessengerLabels.
Host boundary
SupercodeUiState is a transport-safe view model, not a duplicate controller. A trusted host maps
SupercodeController snapshots and persisted inventory into it, then handles SupercodeUiIntent.
An iframe or extension host can pass unknown payloads through parseSupercodeUiIntent before using
dispatchControllerIntent; transport-specific operations can be intercepted with handleIntent
while ordinary controller semantics continue through the shared dispatcher.
The browser cannot supply locators, credentials, policy, environment variables, or arbitrary
materialization paths. Session keys and target harnesses must be revalidated by the host.
Harness configuration is similarly narrow: the UI can only submit a choice from the revisioned
interoperability-control report it received. The headless controller rejects configuration unless
the trusted embedding host explicitly opts into allowHarnessConfiguration, revalidates the
active harness, control key, declared choice, reset capability, and revision, then asks the local
service to update the native file atomically. The UI shows the scope, precedence uncertainty, and
security consequence before it emits that intent; it is not a generic preferences editor.
pickContext is optional and host-owned: the default composer shows one attachment control when
this callback exists and accepts either typed text context (detail) or native image inputs
(url). Images can also be pasted or dropped directly, and an image-only turn is sent without
inventing fallback prompt text. The composer bounds images to four supported browser formats at
5 MB each, previews them locally, and keeps both attachment kinds associated with queued
messages, retry, edit, and new-chat recovery. The host still chooses how explicit file selection
is exposed and must revalidate every returned item.
openKeyboardShortcuts is also optional and host-owned. When present, the default conversation
overflow includes a Keyboard shortcuts item without consuming permanent header width.
Transcript images with a browser-safe URL open in a keyboard-accessible, viewport-bounded viewer;
remote images expose copy-link and open-original actions, while local data images can be downloaded.
createClientProjection keeps large historical data URLs in a projection-scoped host registry and
puts only stable metadata plus an opaque reference in SupercodeUiState.
RemoteControllerHost uses that same projection and exposes host.resolveImage(reference)
for the current frame; a new projection or closing the host retires the previous registry.
An embedding host may
implement adapter.resolveImage to fetch that reference only after a click and return a bounded
Blob; the viewer owns and revokes the resulting object URL and presents loading, failure, and
retry states. Without that adapter—or when native data is incomplete—the attachment remains an
honest unavailable-preview state rather than a broken thumbnail or deceptive action.
An embedding product such as Vibewaiting should therefore be small: Volter Browsers owns its iframe and launcher lifecycle, Volter Harness owns this UI and the controller semantics, and Vibewaiting only bridges state/intents plus host-specific theme and persistence policy.
Native supervision (0.1.72)
Independent workflow, job/run, configuration inventory and approval compositions
are available from ./preact/supervision and ./react/supervision. Their bounded
projections and native host adapters live in ./supervision. These do not need
a conversation controller. A host supplies source scopes and native SDK clients.
createSupervisionHost binds job discovery, profile-scoped run history and
confirmed pause, resume, run requests and deletion for Hermes, OpenClaw and the orchestrator. createSourceInventoryHost
reads profiles, channels, routes, triggers, skills and memory; it does not turn
instance-wide native readers into per-profile readers. createApprovalHost
requires the existing native connection and session, re-reads a pending choice,
and requires a matching decision receipt. Configuration inventories are read-only. JobControls requires explicit host capabilities and keeps run-request receipts distinct from completed runs.
The public parts include JobList, OrchestrationJobs, SourceInspector and
ApprovalInbox; see the source inventory and supervision parts stories. Native
unknowns, discovery failures and delivery failures remain distinct states.
Validation: package build, TypeScript checks and 49 headless tests pass, including colliding job IDs across orchestrator profiles and confirmation of a paused job. Volter Desktop separately exercised these components in normal Chrome and native job creation/history/pause against a disposable orchestrator home. This does not claim full scheduling migration, write editors, or live permission round-trip coverage.
Standalone agent configuration
AgentConfigurationPanel from /react/settings or /preact/settings renders
reports from the SDK's Node-only /configuration host. Supply report, loading,
saving, error, onRefresh and onChange({ key, value }). The host owns paths,
revision checks and native commands; the component owns field types, read-only
states, per-control Save/Reset, scope and verified-save feedback. It needs no
chat controller or open conversation. Its Storybook stories cover shared settings,
unavailable adapters and failed saves.
Harness readiness and native sign-in
HarnessManagement from /react/settings or /preact/settings renders the
existing picker sign-in controls as a standalone view. Pass the shared controller
UI state, redacted native authentication reports, optional selected-harness
label, and host callbacks for refresh, cancellation and opening the sign-in
terminal. Sign-in emits the existing authenticateHarness intent. Native reports
control availability; unsupported adapters never receive an invented sign-in flow.
The host runs HarnessAuthenticationController with its own terminal execution
adapter. Neither native launch plans nor terminal output belong in browser state.
Native management components
The Preact and React supervision entries export NativeSetupControls,
SkillInstall, MemoryEditor and JobScheduleForm. Hosts pass trusted scoped
callbacks; the components render native receipts, failures and conflicts.
AgentConfigurationPanel accepts guarded profile deletion, SkillDetails accepts
confirmed removal, and MemoryBrowser can open a full native document.
createSourceInventoryHost supplies native skill install/remove and full memory
access; writable memory is an explicit host adapter. Skill operations allow up to
two minutes for native fetch/scan/readback and retain errors when not confirmed.
Schedule forms emit native cron/time-zone, interval or one-time definitions.
Run models can include a separate completion receipt so callback execution and
agent turn completion remain distinct. The messenger header reports connection
or error state before presenting a ready label. Unsupported native capabilities
remain explicit; these components do not implement a harness or scheduler.
Workflow, orchestration and the example app
Import WorkflowBoard, TaskDetails, OrchestrationJobs, JobList, RunDetails and their
standalone parts from /react/supervision or /preact/supervision. The renderer-free
/supervision entry exports projectWorkflow, projectJobs, projectRuns and
createSupervisionHost. Projections preserve native status words, separate execution from
delivery, omit absent session links, and bound data. The trusted host owns native homes and ids;
components receive opaque keys and callbacks. The workflow read door supplies no mutation API.
A pause result must include the matching, authoritatively disabled job; consumers update their
job state from that receipt.
WorkflowBoard starts in initialLayout (list by default). A host whose own view tabs choose
board or list passes layout and hears onLayoutChange, and usually hides the board's header and
switch with --scui-domain-header-display: none and --scui-domain-switch-display: none.
The example webapp imports these public components and the session messenger. Its shell supplies routing, responsive pane placement, browser history, themes and connection adapters. Demo data is explicitly labeled. Connected mode observes native sessions and supports opt-in job pause; it is not a runtime-control or remote-hosting product.
SourceInspector composes ProfileList/ProfileDetails, ChannelList, RouteList, TriggerList,
SkillList/SkillDetails and MemoryBrowser. createSourceInventoryHost binds configured native
sources to independent read outcomes and literal memory search. A failed section does not become an
empty success. Counts and previews are bounded; native filesystem locations stay on the host.
Use section, selectedKey and onNavigate to connect an app router, or let the component own selection.
ApprovalInbox/ApprovalDetails accept native request options. createApprovalHost requires a
caller-owned client plus an explicit harness/session, rereads the request before resolving, and
validates the returned native decision. Resolution is opt-in. This does not edit an access policy.
OrchestrationOverview and AdapterStatus render projectRuntimeState output from a supplied
orchestrator state observation. They neither start a runtime nor infer live health from config.
All these exports are available through the same React/Preact supervision entry points.
