@volter-ai-dev/supercode-ui
v0.1.42
Published
Composable default UI kit for Supercode-powered coding-agent experiences
Readme
@volter-ai-dev/supercode-ui
supercode-ui is the composable default interface for Supercode'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 Lucarne. 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.
Storybook is the component contract
The package's complete development, documentation, and acceptance surface lives in Storybook:
npm run storybook --prefix sdk/ui
npm run build:storybook --prefix sdk/ui
npm run test:storybook --prefix sdk/uiStories import the same built public artifacts that consumers install. 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. Chromium interaction and axe accessibility tests run every story in both light and dark consumer themes. The static Storybook build is part of the repository SDK gate.
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-ai-dev/supercode-ui/embed';
import '@volter-ai-dev/supercode-ui/styles.css';
const mounted = mountSupercodeMessenger(document.querySelector('#agent'), {
state: initialState,
adapter: {
onIntent(intent) {
host.send(intent);
},
async pickContext() {
return host.pickFilesAndImages();
},
onClose() {
panel.close();
},
},
});
host.onState((state) => mounted.update(state));Take only the parts you need
import {
Conversation,
SessionList,
} from '@volter-ai-dev/supercode-ui/preact';
import { HarnessLogo, harnessLogoDataUrl } from '@volter-ai-dev/supercode-ui/preact/logo';
import { HarnessAdvisory, HarnessPicker, HarnessSettingsPanel } from '@volter-ai-dev/supercode-ui/preact/settings';
import { groupConversation } from '@volter-ai-dev/supercode-ui/core';The public components are SupercodeMessenger, AgentActivityLauncher, Conversation, TranscriptEntry,
ActivityGroup, RequestCard, SessionList, SessionRow, Composer, ContinuationBar,
ContextCandidate, ContextCandidates, LoadingStatus, TaskPlan, SessionDetails, HarnessLogo, HarnessAdvisory,
HarnessPicker, and HarnessSettingsPanel. 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 self-contained canonical SVG; 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-ai-dev/supercode-ui/react/messenger';
import type { MessengerComponents } from '@volter-ai-dev/supercode-ui/react';
import '@volter-ai-dev/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 Supercode's attention rules:
import { AgentActivityLauncher } from '@volter-ai-dev/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()],
}}
contextCandidates={visibleObjects.map(asTranscriptContext)}
components={{ ContextCandidate: ObjectPreview }}
slots={{ 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. 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.
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-ai-dev/supercode-ui/controller';
const binding = createControllerBinding(controller, {
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 pagination, durable attention, drafts, and artifact materialization remain explicit callbacks. A browser should receive projected state from a trusted host rather than instantiate a local controller or gain filesystem authority.
Native continuation is headless by default. A host with a real terminal provider can add
terminal to continuationModes and handle onResumeTerminal; the continuation bar then exposes
that strategy beside “Continue here.” The UI never infers terminal support from a generic runtime
handoff, and never presents a terminal strategy 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 a compact “Chat” versus
“Terminal” choice and emits that mode on the new intent. Direct controller bindings keep Chat as
the standard path and 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.
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.- Stable
scui-*classes anddata-*attributes support additive styling; CSS custom properties are the supported theme API. styles.csscontains both neutral tokens and default component rules. Teams may load it whole, override 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.
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.
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. 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: Lucarne owns its iframe and launcher lifecycle, Supercode owns this UI and the controller semantics, and Vibewaiting only bridges state/intents plus host-specific theme and persistence policy.
