Skip to content

Latest commit

 

History

History
1389 lines (1050 loc) · 55.9 KB

File metadata and controls

1389 lines (1050 loc) · 55.9 KB

Component API Reference

anentrypoint-design v0.0.272 — Standardized component prop naming and API surface.

Overview

This document describes all exported components, their prop signatures, and standardized naming conventions. All components are pure factories that return webjsx vnodes.

The freddie.js re-exports (FREDDIE_PAGES, home/chat/voice/sessions/projects/agents/analytics/models/cron/skills/config/env/tools/batch/gateway/chains, skillLabel, getRecentPaths, saveRecentPath, renderChatMessages) are the kit's own docs/marketing demo-site page builders, not general-purpose components — not intended for external composition.

Prop Naming Standards

Boolean props follow one of two patterns:

  • State props (indicate component state): use state enum pattern or explicit state prop
    • Examples: state: 'active' | 'default', expanded: true | false, typing: true | false
  • Semantic boolean props: use is* prefix for clarity
    • Examples: isLoading, isOpen, isDisabled

Callback props always use on* camelCase:

  • onClick, onInput, onSubmit, onChange, etc.

onAction is prop-name-only, not a shared contract — its call signature varies by component and each is correct for its own use, not a bug to unify: Banner's onAction(rawEvent) is a generic action-button click handler; FileRow's onAction(action) is package-internal (FileGrid wraps it into its own public onAction(action, file), the two-arg shape external consumers should wire against); FileViewer/FilePreviewPane's onAction('download') is a fixed single-purpose download trigger since the file is already available via the file prop. Check the specific component's signature below rather than assuming a shared shape.

Variant/mode props use enum values, never multiple boolean flags:

  • Old: primary={true}, ghost={true} [ ]
  • New: variant: 'primary' | 'ghost' | 'default' [x]

Multi-select contract (shared by FileGrid and SessionDashboard):

  • selectable (bool), selected (Set keyed by entity id/path), onToggleSelect(item, { range }), onSelectAll(), onClearSelection()
  • FileGrid also accepts marked/onMark as aliases of selected/onToggleSelect.

Workspace Surfaces

The flagship application surfaces (an agent-chat product is composed from these).

WorkspaceShell

A Claude-Desktop-style three-(or four-)column app shell.

WorkspaceShell({
  rail,                  // left nav vnode (usually WorkspaceRail())
  sessions,              // OPTIONAL second column (ConversationList); null hides it
  main,                  // primary content column (vnode or array)
  pane,                  // OPTIONAL right context pane; null hides it
  crumb,                 // optional thin top chrome bar over the content column
  status,                // optional footer (Status())
  narrow,                // caller's isNarrow() - drives drawer auto-close behavior
  railCollapsed, paneCollapsed,  // initial collapse (persisted state wins)
  stableFrame,           // keep the pane grid TRACK at width 0 when this tab has
                         // no pane, so the column count never re-flows on tab switch
  mainFlush,             // remove .ws-main's content gutter (chat self-gutters)
  railLabel, paneLabel,  // aria labels
})

Responsive: the pane track yields at <=1480px (right overlay drawer via .ws-pane-open), the sessions track at <=1100px (left drawer via .ws-sessions-open), and below 900px the rail drops to a fixed icon strip with a single content column. The crumb bar hosts the drawer toggles.

WorkspaceRail

WorkspaceRail({ brand, action: {label, icon, onClick}, items: [{key, label, icon, active, count, onClick}], footer })

ConversationList

The persistent conversation rail ("Chats" column).

ConversationList({ sessions: [{sid, title, project, agent, time, running, unread, rail}], selected, groups: [{label, sids}], search: {value, placeholder, onInput}, caption, onSelect(session), onNew(), newLabel, emptyText, loading, loadingText, error })

Loading renders shape-matched skeleton rows. The body is one stable keyed wrapper across loading/empty/populated states (webjsx applyDiff requirement).

SessionDashboard

The live multi-session command center.

SessionDashboard({ sessions, onStop, onOpen, onView, onStopAll, onStopSelected, onArmStopAll, onArmStopSelected, confirmingStopAll, confirmingStopSelected, sort: {value, onChange}, filter: {value, placeholder, onInput}, errorsOnly, onErrorsOnly, selectable, selected: Set, onToggleSelect, onSelectAll, onClearSelection, activeSid, streamState: 'connected'|'connecting'|'lost'|'offline', emptyText, emptyAction: {label, onClick}, offline })

Session shape: { sid, realSid, title, agent, model, cwd, elapsedMs, counter, lastActivity, currentTool, status: 'running'|'stale'|'error', stopping, external, isNew, cost, tokens }. Status-sorted renders bucketed groups (Errored/Running/Idle/External); other sorts render flat.

SessionMeta

A middot-separated metadata strip for a session detail surface (compact fact display, e.g. version/uptime/roots).

SessionMeta({ items: [{ label, value, title, onCopy }] })

Each item renders as a span with a dimmed label + mono value; passing onCopy adds a per-item copy button. Returns null when items is empty. Class .ds-session-meta-strip (the bare .ds-session-meta class is already used by ConversationList row meta).

SessionCard

One running session. Same session shape as above; external suppresses stop and renders a read-only card.

ContextPane

ContextPane({ agent, model, cwd, toolCount, usage: {inputTokens, outputTokens, costUsd, turns, durationMs}, session: {turns, cost}, onSetCwd }) The change-cwd affordance rides ON the working-dir row; all-zero session totals hide the conversation block.

AgentChat

The reusable multi-agent chat surface (pure component, host wires transport).

AgentChat({
  agents, selectedAgent, models, selectedModel, modelsLoading,
  messages,              // [{id, role, content, time, parts}] - parts may be
                         // strings or {kind:'tool'|'md'|...} structured parts
  busy, draft, status, banners,
  cwd, cwdEditing, cwdDraft, cwdError, cwdChecking,
  agentName, placeholder, canSend,
  suggestions, onSuggestionClick,
  onSelectAgent, onSelectModel, onSend, onStop, onNewChat, onInput,
  onCwdEdit, onCwdSave, onCwdCancel, onCwdClear, onCwdDraft,
  onCopyMessage, onRetryMessage, onEditMessage, confirmEdit, onArmEdit,
  avatar, composerContext,   // {bits: [string | {text|label, title, onClick}]}
  followups, onFollowupClick,
  installHint, exportActions,
  onPasteFiles(files), onDropFiles(files),   // NOTE the names - not onPasteImage
  shownMessages, onShowEarlier,              // windowed thread (default cap 100)
})

FileGrid (selection + density props)

Beyond the base listing props:

FileGrid({ ..., selectable, selected: Set (keyed by path), onToggleSelect(f, {range}), onSelectAll(keys), onClearSelection, density: 'list'|'compact'|'thumb', onDensity, thumbUrl(f) }) marked/onMark are accepted aliases for selected/onToggleSelect. loading with rows present dims the grid in place (is-refreshing); skeleton renders only on a cold load. FileRow row actions: onAction(action, file) fires with action in ['download','rename','move','delete']; 'move' opens the host's bulk-move flow (agentgui seeds a single-path selection into its existing multi-select move dialog).

BulkBar

BulkBar({ count, noun, nounPlural, actions: [{label, danger, onClick}], onClear, busy }) - pluralizes -y nouns.

FileToolbar / RootsPicker / DropZone

FileToolbar({ left, right }); RootsPicker({ roots: [{id, label}], selected, onSelect, label }); DropZone({ children, dragover, onDrop(files), onDragOver, onDragLeave, label, onPick }) - with children it is a passive wrapper whose dashed affordance overlays ONLY during a drag; without children it is an explicit picker block.

FilePreviewPane

Inline (non-modal) split-view file preview; the modal FileViewer remains the <900px fallback.


Shell Components

Brand

Creates a branded heading with optional leaf text.

Brand({ name = '247420', leaf })
Prop Type Default Description
name string '247420' Brand name
leaf string - Optional suffix (appears after slash)

Example:

Brand({ name: 'acme', leaf: 'dashboard' })
// -> "acme / dashboard"

Chip

Inline colored badge/label. size: 'sm' | 'md' | 'lg' scales it; tag: true renders a rectangular sentence-case variant (--r-0, no caps) for dense data like table cells, in place of the default all-caps pill.

Chip({ tone = '', size = 'md', tag = false, children })
Prop Type Default Description
tone string '' CSS tone class (e.g., 'live', 'idle')
children ReactNode - Chip label/content

Example:

Chip({ tone: 'live', children: 'online' })

Pill

Plain non-interactive label chip — tag-like annotation (a phase name, an id, a subsystem tag). Distinct from Chip (status-tone indicator), Badge (count/variant marker), and FilterPills (interactive toggle-group): Pill renders no button and carries no pressed/active state.

Pill({ tone = '', children })
Prop Type Default Description
tone string '' '' | 'accent' | 'muted'
children ReactNode - Pill label/content

Example:

Pill({ tone: 'accent', children: 'PLAN' })

Btn (Button)

Flexible button component with support for multiple variants. size: 'sm' | 'md' | 'lg' snaps the height to the --ctl-sm/md/lg ladder (28/34/42px, density-scaled) with horizontal-only padding — md is the base.

Btn({ href, variant = 'default', size = 'md', children, onClick, 'aria-label': ariaLabel, primary, ghost, danger, disabled, class: className, key })
Prop Type Default Description
variant 'primary' | 'ghost' | 'danger' | 'default' 'default' Button style variant. 'danger' renders btn-primary danger. If both variant and a legacy boolean (primary/ghost/danger) are passed, variant wins.
href string undefined Navigation link. A real navigational value renders <a>; omitted, '', or literal '#' renders a native <button> (not a link)
children ReactNode - Button label/content
onClick function - Click handler callback
aria-label string - Accessible label (auto-filled from children if string)
disabled boolean - Disables click/keyboard activation; adds is-disabled, aria-disabled, and (for links) tabindex="-1"
class string - Extra class(es) appended to the resolved variant class (matches Panel/Heading's class prop naming)
key string - webjsx diff key
primary boolean - Deprecated: use variant="primary"
ghost boolean - Deprecated: use variant="ghost"
danger boolean - Deprecated: use variant="danger"

Example:

// New way
Btn({ variant: 'primary', children: 'save', onClick: handleSave })

// Legacy (still supported)
Btn({ primary: true, children: 'save', onClick: handleSave })

Glyph

Colored icon/symbol with optional custom color.

Glyph({ children, color })
Prop Type Default Description
children string - Icon/symbol (emoji or Unicode)
color string - CSS color value

Icon

Monochrome inline-SVG line icon (stroke = currentColor). Both call forms are accepted:

Icon('folder', { size: 16 })    // positional (original)
Icon({ name: 'folder', size: 16 })  // props-object (matches every sibling component)

Unknown names render a blank decorative span (no throw). The canonical name list is the ICON_PATHS table in src/components/shell.js. iconMarkup(name|{name,size}) returns the same SVG as a markup string for raw-DOM consumers.

Topbar

Application header with brand, search, and nav items.

Topbar({ brand = '247420', leaf = '', items = [], active = '', onNav, search })
Prop Type Default Description
brand string '247420' Brand name
leaf string '' Brand suffix
items [[label, href], ...] [] Navigation items
active string '' Currently active nav item label
onNav function - Nav click callback (label passed)
search string - Search placeholder (shows search input if set)

Example:

Topbar({
    brand: 'myapp',
    items: [['home', '#/'], ['docs', '#/docs']],
    active: 'home',
    onNav: (label) => navigate(label),
    search: 'search…'
})

Side

Sidebar navigation with grouped items.

Side({ sections = [] })
Prop Type Default Description
sections array [] Group objects with { group, items: [] }

Section item shape:

{
    group: 'group label',
    items: [
        { label, glyph, href, active, count, color, onClick },
        ...
    ]
}

Example:

Side({
    sections: [
        {
            group: 'main',
            items: [
                { label: 'home', glyph: '⌂', href: '#/', active: true },
                { label: 'settings', glyph: '⚙', href: '#/settings', count: 3 }
            ]
        }
    ]
})

Status

Footer status bar with left/right content.

Status({ left = [], right = [] })
Prop Type Default Description
left ReactNode[] [] Left-aligned items
right ReactNode[] [] Right-aligned items

AppShell

Complete app layout with header, sidebar, main, and footer.

AppShell({ topbar, crumb, side, main, status, narrow })
Prop Type Default Description
topbar ReactNode - Header component
crumb ReactNode - Breadcrumb component
side ReactNode - Sidebar component
main ReactNode - Main content area
status ReactNode - Footer status bar
narrow boolean - Narrow main width layout

Heading

Semantic heading with configurable level.

Heading({ level = 1, children, style = '' })
Prop Type Default Description
level 1-6 1 Heading level (h1-h6)
children ReactNode - Heading text
style string '' CSS style string

Lede

Lead paragraph (large introductory text).

Lede({ children })

Dot

Status indicator (live/idle).

Dot({ tone = 'live' })
Prop Type Default Description
tone `'live' 'idle'` 'live'

Rail

Colored accent bar (decorative).

Rail({ tone = 'green' })
Prop Type Default Description
tone string 'green' CSS tone class

Content Components

Panel

Container with optional header and content.

Panel({ title, count, right, style = '', children, kind })
Prop Type Default Description
title string - Panel header
count number - Right-align count
right ReactNode - Right header content
style string '' CSS style
children ReactNode - Panel content
kind string - Panel style variant

Row

List item with optional link/click handler.

Row({ code, rank, title, sub, meta, state = 'default', onClick, href, kind, cols, leading, trailing, target, selected, active, rail, expanded, highlight, actions, detail, key, style })
Prop Type Default Description
state 'active' | 'default' | 'disabled' | 'error' 'default' Row state; 'disabled' strips onClick/role=button/tab-stop and sets aria-disabled
title string - Primary text
sub string - Secondary text
code ReactNode - Leading code/glyph
rank ReactNode - Alias for code (the leading monospace index)
meta string - Trailing meta text
href string - Link destination
onClick function - Click handler
kind string - Row style variant
cols string - CSS grid-template-columns
leading ReactNode - Custom leading content
trailing ReactNode - Custom trailing content
target string - Link target (_blank, etc.)
active boolean - Deprecated: use state="active"
selected boolean - Deprecated: use state="active"
rail string - Leading status-bar tone: 'green' (ok/selected) | 'purple' (subagent) | 'flame' (error/unavailable) | any CSS color token
expanded boolean - Disclosure-toggle state; sets aria-expanded and gates actions rendering. Omit entirely for plain action rows (not a toggle)
highlight string - Case-insensitive substring to wrap in <mark class="ds-hl"> within title
actions array - Action-button specs rendered as a sibling strip; only shown when expanded === true
detail ReactNode - Sibling block rendered after title/meta on its own line (.ds-row-detail)

Example:

// New way
Row({ title: 'item', state: 'active' })

// Legacy
Row({ title: 'item', active: true })

RowLink

Link-only row (shorthand for Row with href and kind='link').

RowLink({ code, title, sub, meta, href = '#', key, target })

Section

Semantic section with eyebrow and title.

Section({ title, eyebrow, children })

Hero

Large introductory section with title, body, accent, and actions.

Hero({ eyebrow, title, body, accent, badge, badgeCount, actions })

Install

Code snippet with copy button.

Install({ cmd, copied, onCopy })

Receipt

Key-value table (invoice-like display).

Receipt({ rows = [] })

Changelog

Version history table.

Changelog({ entries = [] })

WorksList

Expandable work items list.

WorksList({ works = [], openedIndex = -1, onToggle })

WritingList

Blog post/article list.

WritingList({ posts = [] })

Manifesto

Prose paragraphs with optional dim styling.

Manifesto({ paragraphs = [], maxWidth })

Kpi

Key performance indicators grid.

Kpi({ items = [] })

Items shape:

[
    [value, label],
    [value, label, { delta: '+12.4%', tone: 'up' | 'down', spark: [8, 11, 9, 14, 16] }],
    ...
]

The third element is optional and additive — a bare [value, label] tuple is unchanged. When present, delta renders a toned trend pill (--success/--danger) with an up/down arrow, and spark renders an inline Sparkline.

Sparkline

Minimal inline SVG trend line — no chart library, token-stroke only.

Sparkline({ values = [], width = 72, height = 24, tone })

tone: 'up' | 'down' colors the stroke via --success/--danger (default up). Returns null for an empty values array. Decorative (aria-hidden) — pair with adjacent text for the actual value.

BarChart

Horizontal token-only progress-bar breakdown — category/channel comparisons.

BarChart({ items = [{ label, value, display }], emptyText = 'no data yet' })

Bars scale relative to the largest value in the set; display overrides the trailing numeric label (defaults to String(value)). Fill width is set via a --bar-pct custom-property write (never a raw inline width:), keeping it clear of the inline-styles lint gate.

Table

Data table with optional row click handler. striped/compact are opt-in density modifiers (default false, existing calls byte-unchanged) — striped alternates row background on even rows, compact tightens header/cell padding onto the --space-1-5 token.

Table({ headers = [], rows = [], onRowClick, emptyText = 'nothing here yet', rowLabels, striped = false, compact = false })

Example:

Table({ headers: ['id', 'status'], rows: [['a1', 'live'], ['a2', 'idle']], striped: true, compact: true })

SearchInput

Search input field with submit callback.

SearchInput({ value = '', placeholder = 'search…', onInput, onSubmit, name = 'q', key })

TextField

Labeled input or textarea. size: 'sm' | 'md' | 'lg' snaps the control height to the --ctl-* ladder (via .ds-field--sm/--lg).

TextField({ label, value = '', type = 'text', placeholder = '', onInput, onChange, name, key, hint, multiline, rows = 4, size = 'md' })

Select

Dropdown select input. size: 'sm' | 'md' | 'lg' snaps the control height to the --ctl-* ladder (via .ds-field--sm/--lg).

Select({ label, value = '', options = [], onChange, name, key, placeholder, hint, size = 'md' })

EventList

Numbered event list (alias for multi-row display).

EventList({ items, events, emptyText = 'no events', rankPad = 3 })

PageHeader

Page section header with title, lede, and right content.

PageHeader({ title, lede, eyebrow, right, compact, dense, id })
Prop Type Description
compact boolean Strips the section's leading/trailing margin (for a header used as a page's first element). Keeps the display H1-over-paragraph layout.
dense boolean Content-first single-row form (small heading + inline lede) instead of the display H1-over-paragraph layout. When dense is true, compact is ignored — the dense row always renders margin-stripped.
id string Placed on the outer section as a deep-link anchor.

Form

Simple form with fields and submit button.

Form({ fields = [], submit = 'submit', onSubmit })

HomeView

Complete home page layout.

HomeView({ state = {}, onNav, onToggleWork, works = [], posts = [], manifesto = [], currentlyShipping })

ProjectView

Project detail view.

ProjectView({ project = {}, copied, onCopy })

Chat Components

ChatMessage

Single chat message with avatar, content, reactions, and receipts.

ChatMessage({ role, who = 'them', avatar, text, parts, time, typing, key, aicat, reactions, receipt, name })
Prop Type Default Description
role `'user' 'assistant' 'them'
who `'you' 'them'` 'them'
avatar string - Avatar character/emoji
text string - Plain text message
parts array - Rich message parts (text, md, code, image, etc.)
time string - Timestamp
typing boolean - Show typing indicator
aicat boolean - AI cat styling
reactions array - Emoji reactions
receipt `'sent' 'read'` -
name string - Sender name (for 'them')
key string - React key

Example:

// New way
ChatMessage({ role: 'user', text: 'hello!' })
ChatMessage({ role: 'assistant', text: 'hi there!' })

// Legacy
ChatMessage({ who: 'you', text: 'hello!' })
ChatMessage({ who: 'them', text: 'hi there!' })

Message parts shape:

{
    kind: 'text' | 'md' | 'code' | 'image' | 'pdf' | 'file' | 'link',
    text?, code?, lang?, filename?,
    src?, alt?, caption?, href?, title?, desc?, thumb?,
    name?, size?, kindLabel?
}

ChatComposer

Text input for composing messages.

ChatComposer({ value, onInput, onSend, placeholder = 'message…', disabled })

Chat

Complete chat container with header and message thread.

Chat({ title = 'chat', sub, messages = [], composer, header })

AICat

AI assistant chat interface with typing indicator.

AICat({ name = 'aicat', messages = [], thinking, composer, status = 'online · purring' })

AICatPortrait

AI cat avatar display.

AICatPortrait({ name = 'aicat', status = 'idle', face })

Editor Primitives

Toolbar

Flexible toolbar with leading, trailing, and center sections.

Toolbar({ leading = [], trailing = [], dense = false, children })

ToolbarRow

Flat, wrapping row of arbitrary action nodes (buttons/inputs/chips) with no leading/center/trailing slot structure — the shape to reach for when a caller just wants "this row of controls, left to right, wrapping on narrow viewports" and Toolbar's three-slot split is unwanted overhead. Accepts children as varargs or a single array.

ToolbarRow(...actions)

Example:

ToolbarRow(searchInput, filterSelect, refreshButton)
// or
ToolbarRow([searchInput, filterSelect, refreshButton])

Tabs

Tabbed interface.

Tabs({ items = [], active, onChange, children })

Item shape:

{ id, label }

TreeView

Hierarchical tree navigation container.

TreeView({ children })

TreeItem

Single expandable tree node.

TreeItem({ label, glyph, tag, depth = 0, selected = false, expanded = false, onSelect, onToggle, children, hasChildren })
Prop Type Default Description
label string - Node label
glyph ReactNode - Leading icon
tag ReactNode - Trailing tag
depth number 0 Indentation level
selected boolean false Selection state
expanded boolean false Expansion state
onSelect function - Selection callback
onToggle function - Expand/collapse callback
children ReactNode - Child nodes
hasChildren boolean - Explicitly mark as having children (inferred from children prop)

PropertyGrid

Property inspector grid container.

PropertyGrid({ children })

PropertyField

Property grid field item.

PropertyField({ label, hint, inline = false, children })

PropertyGridRow

A PropertyGrid row wrapper with a bottom-border divider (last-child border suppressed) — for editors that need stronger per-row visual separation than the default PropertyGrid gap gives (e.g. a list of independently-editable records like PRD/mutable rows).

PropertyGridRow({ children })

InlineEditableField

Borderless-until-focus text input inheriting surrounding font, with an explicit error state (aria-invalid + danger-token border) for live per-field validation. Renders a <textarea> when multiline is set, else a single-line <input>.

InlineEditableField({ value = '', placeholder, onInput, onChange, error, multiline = false, rows = 3, ariaLabel, disabled = false })
Prop Type Default Description
value string '' Current field value
placeholder string - Placeholder text
onInput function - (value, event) => void, fires on every keystroke
onChange function - (value, event) => void, fires on commit (blur/change)
error boolean - Sets aria-invalid="true" + .has-error class
multiline boolean false Renders a <textarea> instead of <input>
rows number 3 Textarea row count (multiline only)
ariaLabel string - Accessible name
disabled boolean false Disables the field

Pager

Prev/next paginator with a page label. page is 1-indexed; pageCount<=1 disables both buttons (no divide-by-zero, no dead-end enabled control). total (optional) renders an item-count suffix.

numbered: true switches to a compact numbered-button row (screen-real-estate dense mode) instead of the prev/next label — always shows first, last, current page, and up to siblingCount (default 1) neighbors either side, collapsing gaps into an ellipsis. Falls back to prev/next automatically when pageCount<=1. The default prev/next contract is unchanged.

Pager({ page = 1, pageCount = 1, onPage, total, itemLabel = 'items', numbered = false, siblingCount = 1 })

Example:

Pager({ page: 2, pageCount: 5, total: 42, onPage: (p) => setPage(p) })
Pager({ page: 5, pageCount: 40, numbered: true, onPage: (p) => setPage(p) })

Grid / GridItem

24-column responsive layout primitive (screen-real-estate density: dense multi-column panels without hand-rolled grid-template-columns per consumer). Grid is a flex-row wrapper; GridItem's column-span props are integers 1-24 evaluated at four tiers mirroring BP_SM/BP_MD/BP_LG/BP_XL (480/768/1024/1440) via CSS custom properties + media queries — no JS-side matchMedia, degrades gracefully without hydration. A span of true means full-width/auto-grow at that tier; 0 hides the item at that tier.

Grid({ gap, justify, align, children })
GridItem({ xs, sm, md, lg, xl, children })

Example:

Grid({ children: [
  GridItem({ xs: true, sm: 6, md: 4, children: PanelA() }),
  GridItem({ xs: true, sm: 6, md: 8, children: PanelB() }),
]})

Collapse / CollapseGroup

Progressive-disclosure toggle panel (screen-real-estate density: property inspectors, nested settings, FAQ-style panels). Controlled component — caller owns expanded state and passes onToggle, same pattern as Drawer/Dialog's open/onClose.

CollapseGroup lays out a list of {id, title, children} items. accordion: true enforces single-open-at-a-time via openId/onOpenChange(id); without it, each item manages its own expanded flag and onOpenChange(id, next) fires per-item.

Collapse({ title, expanded = false, onToggle, children })
CollapseGroup({ items = [], openId, onOpenChange, accordion = false })

Example:

CollapseGroup({
  accordion: true,
  openId: state.openId,
  onOpenChange: (id) => setState({ openId: id }),
  items: [
    { id: 'general', title: 'General', children: GeneralSettings() },
    { id: 'advanced', title: 'Advanced', children: AdvancedSettings() },
  ],
})

Divider

Plain rule, optional centered text label, optional vertical orientation — for segmenting dense panels without a full Section wrapper.

Divider({ label, vertical = false })

Example:

Divider()
Divider({ label: 'OR' })
Divider({ vertical: true })

JsonViewer

Monospace data preview (max-height + scroll). Accepts a pre-stringified string OR any value — objects/arrays get JSON.stringify(v, null, 2); null/undefined render emptyText rather than the literal "undefined"/"null".

mode selects rendering. 'plain' (default) is the historical contract — flat <pre>, raw text, string input verbatim. 'highlight' tokenizes the JSON into ds-ep-json-k/s/n/b/z spans (key/string/number/boolean/null; linear single-pass scan, no regex); a string that does not parse as JSON falls back to plain text, so arbitrary prose is never falsely tokenized. 'tree' renders nested objects/arrays as native collapsible <details> nodes with child-count tags, open above treeDepth (default 2); scalars fall back to highlight. copyable: true wraps the viewer with a copy-to-clipboard button (transient copied/failed feedback).

JsonViewer({ value, emptyText = 'no data', maxHeight, mode = 'plain', copyable = false, treeDepth = 2 })

Dock

Five-panel layout (top, left, center, right, bottom).

Dock({ top, left, right, bottom, center })

IconButtonGroup

Toggle button group.

IconButtonGroup({ items = [], value, onChange, dense = false })

Item shape:

{ id, glyph?, label?, title?, disabled? }

Form Primitives

src/components/form-primitives.js — standard form controls (previously undocumented).

Checkbox({ checked, indeterminate, disabled, label, hint, onChange, ariaLabel, key, name, id })
Radio({ name, value, checked, disabled, label, hint, onChange, ariaLabel, key, id })
RadioGroup({ legend, name, value, options = [], onChange, orientation = 'vertical', key })
Toggle({ checked, disabled, label, hint, onChange, ariaLabel, kind = 'switch', key, id })
Field({ label, hint, error, required, requiredMarker = '*', htmlFor, children, key })
useFormValidation(schema = {})

RadioGroup's options items are { value, label, hint?, disabled? }. Field wraps any input with a label/hint/required-marker/error row. useFormValidation returns a validator driven by a { field: (value) => errorString|null } schema map.


Interaction Primitives

src/components/interaction-primitives.js — drag/drop, scrub, reorder, and keyboard-shortcut helpers (previously undocumented).

useDraggable(el, { data, kind, onDragStart, onDragEnd })
useDropTarget(el, { accepts = [], onDrop, onDragOver })
useNumberScrub(el, { getValue, onChange, step = 0.01, threshold = 3 })
usePointerDrag(el, { onStart, onMove, onEnd, button = 0 })
Reorderable({ items = [], getKey, renderItem, onReorder, axis = 'vertical', kind = 'reorder' })
useKeyboardShortcut(map = {}, { scope = 'global', enabled = true })
formatShortcut(combo)
ShortcutHint({ combo, kind = 'kbd' })
ShortcutList({ shortcuts = [] })
useKeyboardShortcutHelp()
ShortcutHelpDialog({ open = false, onClose, registry })

useDraggable/useDropTarget/usePointerDrag are imperative hooks over a DOM element ref, not vnode factories — call them in an effect, not inside render. useKeyboardShortcut registers combos into a module-level registry that useKeyboardShortcutHelp/ShortcutHelpDialog read back to render a live shortcuts-overlay (the pattern agentgui's ?-overlay uses).


Overlay Primitives

src/components/overlay-primitives.js — floating UI, focus-trap, and modal/popover surfaces (previously undocumented).

useFloating(anchorEl, contentEl, { placement = 'bottom-start', offset = 8 })
useLongPress(targetEl, callback, { ms = 500 })
Tooltip({ children, label, placement = 'top', delay = 350, kind = 'default' })
Popover({ open, anchorEl, onClose, placement = 'bottom-start', children, ariaLabel })
Dropdown({ trigger, items = [], onSelect, placement = 'bottom-start', ariaLabel })
trapTab(el, e)
CommandPalette({ open, items = [], onSelect, onClose })
EmojiPicker({ open, anchorX = 0, anchorY = 0, onSelect, onClose, query = '' })
BootOverlay({ progress = 0, phase = '', errored = false, visible = false })
SettingsPopover({ title = 'Settings', open, anchorX = 0, anchorY = 0, sections = [], onClose })
AuthModal({ mode = 'extension', error = '', busy = false, open = false, onModeChange, onConnectExtension, onGenerate, onImport, onClose })
VideoLightbox({ src, label = '', open = false, onClose })

trapTab is the Tab-focus-trap primitive exported for reuse by any modal/drawer (agentgui's mobile drawer and files-modals both wire it directly — see AGENTS.md's webjsx/focus-trap notes). useFloating computes anchored position for Popover/Dropdown/Tooltip; call it with live DOM refs, not vnodes.


Community Components

ServerIcon

Server/guild icon button.

ServerIcon({ id, name, icon, active, badge, onClick })

ServerRail

Vertical server icon rail.

ServerRail({ servers = [], activeId, onSelect, onAdd })

ChannelItem

Channel list item with optional voice state.

ChannelItem({ id, name, type = 'text', active, voiceActive, voiceConnecting, badge, draggable, actions = [], participants = [], onClick, onContext })
Prop Type Default Description
type `'text' 'voice' 'forum'
active boolean - Active state
voiceActive boolean - Voice channel active
voiceConnecting boolean - Voice connection pending
badge number - Unread count badge
draggable boolean - Enable drag-and-drop
actions array - Action button array
participants array - Voice participants
onClick function - Click handler
onContext function - Right-click handler

ChannelCategory

Collapsible channel group.

ChannelCategory({ id, name, channels = [], collapsed, activeId, onToggle, onAddChannel, onChannelClick, onChannelContext, onContextMenu, extraButton, channelDraggable })

VoiceUser

Voice channel participant.

VoiceUser({ identity, speaking, color })

UserPanel

User profile panel.

UserPanel({ identity, name, color, status = 'online' })

VoiceStrip

Voice channel toolbar.

VoiceStrip({ channelName, status, muted, deafened, onMute, onDeafen, onLeave, open })

ChannelSidebar

Complete channel sidebar with categories.

ChannelSidebar({ categories = [], open })

MemberItem

Member list item.

MemberItem({ id, name, channels = [], collapsed, activeId, onToggle, onAddChannel, onChannelClick, onChannelContext, onContextMenu, extraButton, channelDraggable })

MemberList

Members list container.

MemberList({ members = [], onMemberClick })

ChatHeader

Chat header (community).

ChatHeader({ channelName, status, muted, deafened, onMute, onDeafen, onLeave, open })

CommunityShell

Complete community layout.

CommunityShell({ topbar, crumb, side, main, status, narrow })

Additional Exports Quick Reference

Exports from already-documented modules above that previously had zero mention here. Signatures only (see the module's own source comments for behavior detail); expand into a full section if a consumer needs deeper docs for one of these.

Shell (shell.js): IconButton({ icon, onClick, title, size = 'base', variant = 'ghost', disabled = false }), Badge({ children, variant = 'default', tone = 'neutral', size = 'md' }) (size 'sm' | 'md' | 'lg'), Crumb({ trail = [], leaf = '', right }).

Content (content.js): Card, PanelFromItems({ heading, items = [], keyPrefix = 'i', count, style, kind, emptyText }), HeroFromPageData(hero), Marquee({ items = [], sep = '/' }), CliBlock({ lines = [], heading = 'quick start', className = '' }), Spinner({ size = 'base', tone = 'accent', label = 'loading', key }), Skeleton({ height = '1em', width = '100%', count = 1, label = 'loading content', key }), Alert({ kind = 'info', children, onDismiss, title, key }), FilterPills({ options = [], selected, onSelect, label = 'filters' }).

Editor primitives (editor-primitives.js): Drawer({ side = 'left', open = false, onClose, children, ariaLabel }), Dialog({ title, open = false, onClose, children, actions = [], dismissible = false, ariaLabel }), FocusTrap({ children }), Toast({ message, kind = 'info', duration = 3000, onClose }) / toast({ message, kind = 'info', duration = 3000 }) (imperative fire-and-forget variant), ContextMenu({ items = [], anchor = { x: 0, y: 0 }, onClose }) / useContextMenu(targetEl, items, openCb), ResizeHandle({ axis = 'horizontal', onResize, ariaLabel }), SplitPanel({ orientation = 'horizontal', initial = '50%', min = 80, max = Infinity, children }), useMediaQuery(query), BP_SM/BP_MD/BP_LG/BP_XL (480/768/1024/1440 breakpoint constants).

Files (files.js): FileRow({ name, type = 'other', size, modified, code, onOpen, onAction, active, key, permissions, locked, selected, onToggleSelect, ... }) (see files-modals section above for the full multi-select contract), FileIcon({ type = 'other' }), FileSkeleton({ rows = 12 }), EmptyState({ text = 'nothing here', glyph = Icon('circle'), action }), BreadcrumbPath({ segments = [], onNav, root = 'root' }), sortFiles(files = [], sort = 'name', dir = 'asc'), fileGlyph(type).

Files modals (files-modals.js): ConfirmDialog({ title = 'Are you sure?', message, confirmLabel = 'confirm', cancelLabel = 'cancel', destructive, onConfirm, onCancel, error, busy = false, busyLabel = 'working…' }), PromptDialog({ title = 'Enter a name', value = '', placeholder = '', confirmLabel = 'ok', cancelLabel = 'cancel', onConfirm, onCancel, onInput, error, busy = false, busyLabel = 'working…' }), FileViewer({ file, body, onClose, onAction, onPrev, onNext }), FilePreviewMedia({ src, type = 'other', name }), FilePreviewCode({ content = '', lang, filename }), FilePreviewText({ content = '', truncated }).

Community (community.js): MobileHeader({ title, channelType, channelName, onMenu, onMembers }), ReplyBar({ quotedMessage, quotedAuthor, onCancel }), Banner({ tone = 'info', message, visible, actionLabel, onAction, onClick }), ThreadPanel({ threads = [], activeId = null, title = 'Threads', onSelect, onCreate, onClose, loading = false }), ForumView({ posts = [], onSearch, onSort, onSelect, onNewPost, loading = false }), PageView({ title = '', html = '', isAdmin = false, onEdit }).


Utility Helpers

fmtBytes

Format byte size to human-readable string.

fmtBytes(n: number): string

Example:

fmtBytes(1024) // '1.0 KB'
fmtBytes(1048576) // '1.0 MB'

renderInline

Render inline markdown subset (bold, italic, code, links).

renderInline(text: string): ReactNode[]

Migration Guide: Breaking Changes

Btn Component

Status: Backward compatible (v0.0.127+)

Old API:

Btn({ primary: true, children: 'Save' })
Btn({ ghost: true, children: 'Cancel' })
Btn({ children: 'Default' })

New API:

Btn({ variant: 'primary', children: 'Save' })
Btn({ variant: 'ghost', children: 'Cancel' })
Btn({ variant: 'default', children: 'Default' })

Migration Path:

  • Old primary and ghost props still work (resolved silently; variant wins when both are present)
  • Update gradually per codebase
  • Full removal planned for v1.0

Row Component

Status: Backward compatible (v0.0.127+)

Old API:

Row({ title: 'item', active: true })

New API:

Row({ title: 'item', state: 'active' })

Migration Path:

  • active: true -> state: 'active'
  • active: false -> state: 'default'
  • selected: true -> state: 'active'

ChatMessage Component

Status: Backward compatible (v0.0.127+)

Old API:

ChatMessage({ who: 'you', text: 'hello' })
ChatMessage({ who: 'them', text: 'hi' })

New API:

ChatMessage({ role: 'user', text: 'hello' })
ChatMessage({ role: 'assistant', text: 'hi' })

Migration Path:

  • who: 'you' -> role: 'user'
  • who: 'them' -> role: 'assistant'
  • Both still supported; role takes precedence if both provided

TreeItem Component

Status: Backward compatible (v0.0.127+)

Internal logic:

  • hasKids computed from children presence
  • Optional hasChildren prop available for explicit control
  • No breaking changes needed (internal only)

CSS Classes Reference

The real class taxonomy (enforced by scripts/lint-classes.mjs at build time):

  • ds-* — scoped primitives (rows, grids, dialogs, dashboards, previews)
  • app-* — AppShell chrome (topbar, status bar, crumb)
  • ws-* — WorkspaceShell chrome (rail, sessions, content, pane, drawers, scrim)
  • chat-* / agentchat-* / aicat-* — chat surfaces
  • cm-* — community surfaces; ov-* — overlay primitives; vx-* — voice; fd-* — freddie
  • Public utility classes (intentional, stable API): .btn, .btn-primary, .btn-ghost, .row, .panel, .seg, .crumb, .glyph, .status-dot-disc
  • Legacy bare internals (frozen — always styled under a prefixed parent; do NOT add new ones, and avoid global consumer rules on these words inside the mount root): name, size, icon, count, sep, leaf, cap, thumb, desc, tick, meta, sub, title, spread, and the rest of the allowlist in scripts/lint-classes.mjs.

State modifiers use is-* / .active / .show; rail tones use rail-*; badge tones use tone-*.

Bundled stylesheets

scripts/build.mjs concatenates the root CSS files into the scoped dist/247420.css: colors_and_type.css (tokens) -> app-shell.css -> community.css -> chat.css -> editor-primitives.css -> community-app.css -> app-surfaces.css -> the spoint kit sheets. app-surfaces.css holds consumer-app application-surface styling (the agentgui pills, cwd bar, resume banner, health chips, settings grid, history empty state, boot splash, scrollbar theming, focus rings, print) so a consuming app keeps NO design content of its own; its selectors are written pre-scoped .ds-247420 ... (the build prefixer leaves already-scoped selectors untouched). lint-glyphs scans these root CSS files (via SCAN_ROOT_FILES) in addition to src/, so a decorative glyph in shipped CSS fails the build.

Focus + multi-select tokens/classes

  • Unified focus tokens (colors_and_type.css): --focus-color / --focus-w / --focus-offset for outset rings, and --focus-ring-inset for bordered text fields. Keyboard rings use :focus-visible (never bare :focus, so a mouse click draws no ring).
  • .ds-check-box — a CSS-drawn multi-select checkbox (bordered box, accent fill + border-drawn tick on .is-marked or [aria-checked="true"], dash on [aria-checked="mixed"]). Used by FileRow/FileCell, the file/session select-all, and SessionCard select. Replaces [x]/[ ] text; AT keeps the role=checkbox + aria-checked name/state.
  • Dashboard card status rails (chat.css): is-error (flame inset, strongest) > is-stale (amber inset) > is-active/is-new (accent inset). Tool-card status pills: tool-running (accent) / tool-error (flame) / tool-done (success). Row() rail tones differentiate by SHAPE (taller bar = error, gapped fill = subagent) plus an sr-only status word, not hue alone.

Voice Surfaces (src/components/voice.js)

PTT/VAD/webcam/voice-settings/queue primitives for a voice-enabled chat surface. Class prefix vx-*. Pure factories, no transport — the host wires media streams and device enumeration.

PttButton

Push-to-talk button; hold-to-talk (pointer/touch) or click, depending on mode.

Prop Type Description
state 'idle'|'live'|'recording'|'vad' Visual/announced state; anything but idle is treated as active.
mode 'ptt'|'vad'|'live' Drives the vx-ptt-mode-* class only (interaction wiring is the same).
onHoldStart (e) => void Fired on pointerdown/touchstart.
onHoldEnd (e) => void Fired on pointerup/pointerleave/touchend.
onClick (e) => void Fired on click (for non-hold flows).
label string Button label + aria-label (default 'Hold to talk').

VadMeter

Voice-activity level meter with a draggable threshold marker.

Prop Type Description
level number 0-1 Current input level (clamped).
threshold number 0-1 VAD trigger threshold (clamped); levels >= threshold render the "over" fill state.
onThresholdChange (value: number) => void Fired from the underlying <input type=range>.

WebcamPreview

Live video preview with resolution/fps selects and an enable/disable toggle.

Prop Type Description
videoStream MediaStream|null Bound to the <video> srcObject via ref.
resolution string Current resolution value (e.g. '640x480'), shown in the select.
fps number Current frame rate, shown in the select.
enabled bool When false, shows a "Camera off" placeholder instead of <video>.
resolutions string[] Options for the resolution select; falls back to [resolution].
fpsOptions number[] Options for the fps select; falls back to [fps].
onResolutionChange (value: string) => void
onFpsChange (value: number) => void
onToggle () => void Enable/Disable button.

VoiceSettingsModal

Modal dialog for voice mode, devices, VAD threshold, processing toggles, bitrate, and volume. Returns null when open is false.

Prop Type Description
open bool Gate; component renders nothing when false.
mode 'ptt'|'vad'|'live' Current mode segmented control selection.
inputId / outputId string Selected input/output device id.
inputDevices / outputDevices [{value, label}] Device select options.
vadThreshold number 0-1 Only shown when mode === 'vad'.
rnnoise / autoGain / forceTurn bool Processing toggle rows.
bitrate number kbps range (8-256).
volume number 0-1 Master volume range; defaults to 1 when null.
onChange (patch: object) => void Fired with a partial-state patch from every control.
onSave / onCancel / onClose () => void Footer/close actions; onClose also fires on backdrop click and Escape.

VoiceControls

Toolbar of call controls: mic, deafen, camera, screen share, settings, leave.

Prop Type Description
muted / deafened / cameraOn / screenShareOn bool Toggle states driving each button's pressed/on class.
onMic / onDeafen / onCamera / onScreenShare / onSettings (e) => void A button is disabled when its handler is not supplied.
onLeave (e) => void Leave-voice button.

AudioQueue

Horizontal strip of queued/replayable audio segments with pause/resume/skip transport controls. Renders an empty state ("No audio queued") when segments is empty.

Prop Type Description
segments [{id, speaker, duration, color, isLive}] Queue items rendered as chips; isLive shows a LIVE tag instead of a duration.
currentSegmentId string Highlights the matching chip as current.
paused bool Drives the transport button's play/pause icon and label.
onReplay (id) => void Fired on chip click.
onSkip () => void Skip-forward button.
onResume / onPause () => void Transport button, dispatched based on paused.

ThemeToggle (src/components/theme-toggle.js)

Segmented auto/paper/ink theme switch bound to src/theme.js; reads current mode via getTheme() and applies changes via applyTheme() (which persists, sets <html data-theme>, and notifies listeners).

ThemeToggle({ compact = false, onChange } = {})

Prop Type Description
compact bool false (default) renders a 3-way role=radiogroup segmented control (auto/paper/ink). true renders a single icon-only cycling button (auto -> paper -> ink -> auto) with a CSS-drawn half-disc glyph and a live label, for icon-only rail contexts.
onChange (mode: 'auto'|'paper'|'ink') => void Called after applyTheme(), in addition to the theme-change side effect.

The compact variant auto-refreshes its glyph when the OS theme changes while in auto mode (subscribes to onThemeChange once per module).


Data-Density Components (src/components/data-density.js)

Dense observability/dashboard primitives (ported from the gmsniff GUI): phase-progress, tree/timeline entries, bar charts, KPI tiles, sub-nav grids, session rows, deviation callouts, and a live log stream. All theme-aware (colors ride var(--token), never raw hex). CSS lives in app-shell.css under "data density" (ds- prefix).

PhaseWalk

Compact horizontal phase-progress indicator.

PhaseWalk({ phases = DEFAULT_PHASES, reached = [], gapKinds = [] })DEFAULT_PHASES is ['PLAN','EXECUTE','EMIT','VERIFY','CONSOLIDATE','COMPLETE']. reached[i] (bool) marks a phase already hit; gapKinds names phases that are a known gap (rendered red, overrides reached).

TreeNode

Indented timeline/tree entry with left-border variant coloring.

TreeNode({ ts, kind, variant = '', phase, id, keyLabel, reason, deviationLabel, residuals })variant is one of ''|'phase'|'deviation'|'mutable-resolve'|'prd-add'; phase/id/keyLabel render as pills; residuals (string array) is joined with , when present.

BarRow

Inline horizontal bar-chart row (label + track + value).

BarRow({ label, value, pct = 0, tone })pct is clamped to 0-100; tone is a CSS color value (var(--token) or color-mix(...)), never a bare hex string.

StatTile / StatsGrid

Compact KPI tile and its grid wrapper (denser than .kpi).

StatTile({ val, lbl, cls = '' })cls selects an accent variant: ''|'rate-big'|'err-rate'. StatsGrid({ items = [] }) — renders items.map(StatTile); shows an empty state ("no stats") when items is empty.

SubGrid

Small button grid: big number + label, for category navigation.

SubGrid({ items: [{ key, count, label, onClick }] }) — renders an empty state ("no items") when items is empty.

SessionRow

Compact single-line session summary row with an inline phase strip.

SessionRow({ sessId, phaseWalkProps, events, verbs, prd, muts, resid, deviations, firstTs, lastTs, onClick })events/verbs/prd/muts/resid are counts joined middot-separated; phaseWalkProps is forwarded to PhaseWalk; onClick makes the row a keyboard-activatable button (role=button, Enter/Space).

DevRow

Deviation/error callout row. Uses the danger-surface token, never a bare hex background.

DevRow({ ts, event, sess, operation, residuals })sess/operation render as pills; residuals (string array) joined with , .

LiveLogEntry / LiveLog

Scrollable dense log stream: colored subsystem tag + bold event name + muted payload preview.

LiveLogEntry({ ts, sub, tone, event, preview })tone is a CSS color value; the tag background derives from it via color-mix at render time (no hex-alpha-suffix hack). LiveLog({ entries = [], autoScroll = true }) — renders an empty state ("no log entries") when entries is empty; autoScroll pins scrollTop to scrollHeight on mount/update via ref.


Utilities

Non-component function exports useful to hosts wiring the kit's chat/session/file surfaces.

  • fmtDuration(ms) (sessions.js) — one duration format for every surface (live cards, running panel, session meta, context pane): <60s -> 'Ns', <1h -> 'Nm Ss', else 'Nh Nm'. Returns '' for null/non-finite/negative input.
  • fmtFileSize(bytes) (files.js) — canonical byte formatter (chat.js re-exports it as fmtBytes). Returns '—' for null bytes, '0 B' for zero, else 'N.N UNIT' scaled through B/KB/MB/GB/TB.
  • safeUrl(url) (chat.js) — sanitizes a URL for use in an inline (non-DOMPurify-passed) markdown link or image src. Allows relative/anchor/protocol-relative links and http(s)/mailto/tel schemes; rejects everything else (e.g. javascript:, data:, vbscript:, file:) by returning null.
  • makeThreadAutoScroll(getCount) (chat.js) — returns a ref callback that pins a scroll container to the bottom when new messages arrive and the user is already at the bottom (via an IntersectionObserver on a bottom sentinel). getCount is a function returning the current message count. Auto-scroll pauses while hasSelectionInside reports an active text selection inside the container, and resumes once it collapses. Shared by Chat, AICat, and AgentChat.
  • injectCodeCopy(container) (chat.js) — walks every <pre> inside a rendered-markdown container and wraps it with a hover-revealed per-block "copy" button (plus a language tab when the highlighter set a language-xx/lang-xx class). Idempotent via a data-copy-wired marker so re-renders don't stack buttons; the button label flips to "copied" for ~1.6s after a successful clipboard write (falls back to a hidden-textarea execCommand('copy') when the Clipboard API is unavailable).
  • hasSelectionInside(el) (chat.js) — returns true when the document has a non-collapsed text selection anchored inside el. Used to pause auto-scroll and streaming re-renders so a user's select-and-copy from a still-streaming message isn't wiped every frame.

Accessibility Standards

All components include ARIA attributes where applicable:

  • Buttons: role="button", aria-label, aria-pressed
  • Navigation: aria-current="page", aria-label
  • Trees: role="treeitem", aria-selected, aria-expanded
  • Tabs: role="tablist", role="tab", aria-selected
  • Chat: role="log", aria-live="polite"

Version History

  • v0.0.127 (2026-05-21): Standardized prop naming with backward compatibility
    • Btn: Added variant prop (old primary/ghost still work)
    • Row: Added state prop (old active/selected still work)
    • ChatMessage: Added role prop (old who still works)
    • TreeItem: Added optional hasChildren prop
    • Full accessibility audit complete