| name | east-ui |
| description | Type-safe UI component library for the East language, authored as JSX tags. Use when writing East programs that define user interfaces. Triggers for: (1) Authoring `.tsx` component trees with `@elaraai/east-ui` tags, (2) Layout with <Box>, <Flex>, <Stack>/<VStack>/<HStack>, <Grid>, <Splitter>, <ScrollArea>, <Sticky>, <Expandable>, <Dock>, <AlignedStack>, (3) Forms with <Input>, <Textarea>, <Select>, <Combobox>, <Checkbox>, <Switch>, <Slider>, <RadioGroup>, <RadioCardGroup>, <TagsInput>, <FileUpload>, <Field>, <DateRangeInput>, <TimeRangeInput>, (4) Data display with <Table>, <TreeView>, <ValueTree>, <DataList>, <Deck>, <Gantt>, <Planner>, <Matrix>, <Calendar>, <Schematic>, <Map>, <Library>, <Roster>, <Board>, <Blend>, <Slice.Rail>, <Pagination>, <ChipRail>, <Trace>, (5) Charts with <Chart layers={Chart.Line/Column/Bar/Area/Scatter/Band(...)}/> (Column = vertical, Bar = horizontal) plus Chart.refLine/refBand/refDot, <Sparkline>, (6) Overlays with <Dialog>, <Drawer>, <Popover>, <Menu>, <Tooltip>, <HoverCard>, <ToggleTip>, <ActionBar>, <CommandPalette>, <Hotkey>, (7) Feedback with <Banner>, <Status>, <Progress>, <Skeleton>, <EmptyState>, (8) Disclosure with <Tabs>, <Accordion>, <Carousel>, <Collapsible>, <SegmentGroup>, <OptionList>, <Story>, (9) Navigation with <Breadcrumb>, <NavList>, route-stack page switching (Navigation.config / Navigation.bind / <Pages>, plus <Route> to host a remounting per-route slot anywhere), and <App> — the whole application shell (collapsible rail + breadcrumb + logo + routed body from one nav handle, with an east-ui-components AppProvider for host-injected app-bar chrome), (10) Reactive UI via <Reactive>{$ => …}</Reactive> + State.bind, and conditional hosting of stateful components via <Match on cases> (remounts the active variant case on tag change), (11) Shared value formatting — one Chart.format.* spec reused by chart axes, Slice fields, <Stat>, <Numeric> and Deck metrics, (12) Status colour vocabulary — the five status tokens, the Deck.statuses registry, Library.status, rowStatus tints and tone props. |
East UI
A type-safe UI component library for the East language. The public surface is
JSX tags — capitalized, React-style components that desugar to East IR. No
React at runtime: a <Button> evaluates to the identical
ExprType<UIComponentType> value, which serializes and renders anywhere.
Quick Start
import { East, IntegerType, NullType, example } from "@elaraai/east";
import { VStack, HStack, Text, Button, UIComponentType } from "@elaraai/east-ui";
const MyComponent = East.function([], UIComponentType, (_$) => (
<VStack gap="4" padding="6">
<Text textStyle="heading-md" fontWeight="bold">Hello, World!</Text>
<HStack gap="2">
<Button variant="outline">Cancel</Button>
<Button variant="solid" colorPalette="blue">Save</Button>
</HStack>
</VStack>
));
const ir = MyComponent.toIR();
Three things make a file JSX-capable:
- The per-file pragma
/** @jsxImportSource @elaraai/east-ui */ (first line).
- One tag import line from
@elaraai/east-ui.
- A
.tsx extension.
Tags vs factories. Each tag desugars 1:1 to a factory call —
<Button variant="solid">Save</Button> builds the same IR as
Button.Root("Save", { variant: "solid" }). The factories are an implementation
detail under @elaraai/east-ui/internal (used by renderers/tests). Author with
tags; props are exactly the factory's flat options bag.
Decision Tree: Which Tag to Use
Every public tag is listed with its purpose, its Props (each marked
required / optional) and its Factories — the Component.xyz(…) builders
that construct that component's config values. Props are the factory's flat
option bag — no nested style object. Children are always UI components;
non-UI sub-structures (columns, layers, cells, header fields) are config
props or typed callbacks, never child sub-tags.
Two shared prop bags recur; a Props list references them by name instead of
re-listing each member's description:
- BOX bag (all optional) —
width, height, minWidth, minHeight,
maxWidth, maxHeight, padding, margin, overflow, overflowX,
overflowY, opacity. CSS sizing/box-model pass-throughs (see the Sizing
pattern below for the string spellings).
- COLOR overrides (all optional) —
color, background, borderColor.
Explicit escape hatches over the palette / variant defaults; take semantic
tokens (fg.muted, bg.subtle) or CSS colours.
Task → Which tag?
│
├─ Layout (arrange content)
│ ├─ <Box> — generic block container
│ │ └─ Props:
│ │ ├─ children (required) — the boxed content
│ │ ├─ display (optional) — CSS display (block / flex / grid / …)
│ │ ├─ flexDirection / justifyContent / alignItems / gap (optional) — flex props when display is flex
│ │ ├─ flex / flexGrow / flexShrink (optional) — behaviour as a flex CHILD
│ │ ├─ fill (optional) — boolean shorthand for "fill remaining space" (flex:1 + min-height:0)
│ │ ├─ scroll / scrollX / scrollY (optional) — boolean shorthands for a styled scroll region
│ │ ├─ position / top / right / bottom / left / zIndex (optional) — positioning; zIndex is a token
│ │ ├─ borderRadius / border / borderColor / borderWidth (optional) — border styling
│ │ ├─ boxShadow / transform / transition / cursor / animation (optional) — presentation extras
│ │ ├─ fontFamily / fontVariantNumeric (optional) — inherited text styling for the subtree
│ │ ├─ density (optional) — provides "condensed"|"compact"|"comfortable" to densified descendants
│ │ └─ …plus BOX bag + COLOR overrides
│ ├─ <Flex> — flexbox container
│ │ └─ Props:
│ │ ├─ children (required) — flex items
│ │ ├─ direction (optional) — row | column (+reverse)
│ │ ├─ wrap (optional) — nowrap | wrap | wrap-reverse
│ │ ├─ justifyContent / alignItems / gap (optional) — main/cross alignment + spacing
│ │ ├─ density (optional) — density provider (as Box)
│ │ └─ …plus the same flex-child / position / border / presentation props as <Box>, BOX bag, COLOR overrides
│ ├─ <Stack> / <VStack> / <HStack> — flex with stacking defaults (VStack = column, HStack = row)
│ │ └─ Props:
│ │ ├─ children (required) — stacked items
│ │ ├─ direction (optional, <Stack> only) — stacking axis
│ │ ├─ gap (optional) — spacing token between items
│ │ ├─ align / justify (optional) — cross/main-axis alignment
│ │ ├─ wrap (optional) — allow wrapping
│ │ ├─ density (optional) — density provider (as Box)
│ │ └─ …plus fill/scroll shorthands, flex-child, position, border, presentation props, BOX bag, COLOR overrides
│ ├─ <Grid> — CSS grid container
│ │ ├─ Props:
│ │ │ ├─ children (required) — grid items (wrap one in Grid.Item to place it)
│ │ │ ├─ templateColumns / templateRows / templateAreas (optional) — grid templates ("repeat(auto-fit, minmax(240px, 1fr))" reflows to 1 column on phones)
│ │ │ ├─ gap / columnGap / rowGap (optional) — grid spacing
│ │ │ ├─ justifyItems / alignItems / justifyContent / alignContent (optional) — item + track alignment
│ │ │ ├─ autoColumns / autoRows / autoFlow (optional) — implicit-track sizing + placement
│ │ │ ├─ density (optional) — density provider
│ │ │ └─ …plus BOX bag (width/height/min*/max*/padding)
│ │ └─ Factories:
│ │ └─ Grid.Item(children, { colSpan?, rowSpan?, colStart?, colEnd?, rowStart?, rowEnd?, area? }) — explicit cell placement for one child
│ ├─ <Splitter> — resizable panel group
│ │ ├─ Props:
│ │ │ ├─ panels (required) — array of Splitter.Panel(…) values
│ │ │ ├─ defaultSize (required) — initial size percentages, one per panel
│ │ │ ├─ children (required) — one body per panel, in order
│ │ │ ├─ orientation (optional) — horizontal | vertical
│ │ │ ├─ collapseBelow (optional) — container px width below which the panels stack vertically
│ │ │ └─ onResize / onResizeStart / onResizeEnd (optional) — drag lifecycle callbacks (sizes payload)
│ │ └─ Factories:
│ │ └─ Splitter.Panel({ id, minSize?, maxSize?, collapsible?, defaultCollapsed? }) — one panel: id (required), min/max size as percentages, collapsibility
│ ├─ <Separator> — 1px rule
│ │ └─ Props:
│ │ ├─ orientation (optional) — horizontal | vertical
│ │ ├─ variant (optional) — subtle | brand | dashed | strong
│ │ ├─ label (optional) — inline label on the rule (string or UIComponent)
│ │ └─ align (optional) — label placement along the rule
│ ├─ <ScrollArea> — styled-scrollbar scroll container
│ │ └─ Props:
│ │ ├─ children (required) — scrollable content
│ │ ├─ orientation (optional) — vertical (default) | horizontal
│ │ ├─ scrollbarStyle (optional) — overlay (default) | classic
│ │ └─ thumbColor / trackColor / background (optional) — scrollbar + viewport colours
│ ├─ <Sticky> — position-sticky wrapper
│ │ └─ Props:
│ │ ├─ children (required) — the stuck content
│ │ ├─ offset (optional) — CSS length sticky offset (default "0")
│ │ ├─ boundary (optional) — stick to the parent scroll ancestor (default) or the viewport
│ │ └─ background / borderColor / shadowColor (optional) — styling applied while stuck
│ ├─ <Expandable> — region expands in place to fill the app container (CSS takeover, no remount); Esc collapses
│ │ └─ Props:
│ │ ├─ children (required) — the expandable region
│ │ ├─ expanded (optional) — controlled expanded state
│ │ ├─ onExpandedChange (optional) — fn(Boolean) => Null on user toggle
│ │ ├─ label (optional) — accessible toggle name ("Expand ‹label›")
│ │ ├─ zIndex (optional) — stacking level of the expanded surface (default 900, below Chakra floating tiers)
│ │ └─ background (optional) — expanded-surface background (default bg.canvas)
│ ├─ <Dock> — inline panel that collapses along an axis to an icon rail, staying in flow (siblings reflow; never overlays) — a source panel beside a drop target (Library beside Planner); Esc does NOT collapse
│ │ └─ Props:
│ │ ├─ children (required) — the docked panel body
│ │ ├─ orientation (optional) — collapse axis: horizontal (default) | vertical
│ │ ├─ side (optional) — edge the rail pins to: start (default) | end
│ │ ├─ expandedSize / railSize (optional) — size along the axis expanded (px or %) / collapsed (default 44px)
│ │ ├─ icon / label / badge (optional) — rail + header icon (FA name), title, count chip
│ │ ├─ collapsed (optional) — controlled collapsed state
│ │ ├─ defaultCollapsed (optional) — uncontrolled initial state (default false)
│ │ ├─ onCollapsedChange (optional) — fn(Boolean) => Null on user toggle
│ │ ├─ persist (optional) — where the uncontrolled state persists (default none)
│ │ ├─ keepMounted (optional) — keep the body mounted while collapsed (default true)
│ │ ├─ lazy (optional) — mount the body only on first expand (default false)
│ │ └─ animated (optional) — animate the rail↔expanded size change (default false)
│ └─ <AlignedStack> — vertical stack that pins every lane child to ONE shared plot gutter (#147) so a Chart over a Planner over a Trace line up on a common x-axis
│ └─ Props:
│ ├─ children (required) — the stacked, gutter-aware lanes (Chart / Planner / Gantt / Table / Calendar / Trace)
│ ├─ gutter (optional) — "auto" (measure the widest lane) or an explicit { left, right } px gutter
│ ├─ gap (optional) — vertical spacing between lanes
│ ├─ density (optional) — density imposed on every lane child (a child's own density wins)
│ └─ width / height / minHeight (optional) — stack sizing
│
├─ Typography (display text)
│ ├─ <Text> — inline/block text
│ │ ├─ Props:
│ │ │ ├─ children (required) — the text (an East string — see the Text pattern below)
│ │ │ ├─ textStyle (optional) — typographic preset token (body-md, heading-sm, …)
│ │ │ ├─ fontWeight / fontStyle / fontFamily (optional) — face overrides (fontFamily: sans | serif | mono)
│ │ │ ├─ fontVariantNumeric (optional) — e.g. tabular-nums for aligned digits
│ │ │ ├─ textAlign / textDecoration / textTransform (optional) — alignment + decoration + casing
│ │ │ ├─ textOverflow / whiteSpace (optional) — ellipsis / wrapping control
│ │ │ ├─ lineHeight / letterSpacing (optional) — rhythm overrides
│ │ │ ├─ borderWidth / borderStyle (optional) — text-block border (with borderColor)
│ │ │ └─ …plus BOX bag + COLOR overrides
│ │ └─ Factories:
│ │ ├─ Text.Presets.Eyebrow(text, style?) — mono uppercase eyebrow (section labels, status words)
│ │ ├─ Text.Presets.EyebrowSm(text, style?) — smaller eyebrow tier
│ │ ├─ Text.Presets.MonoLabel(text, style?) — mono label (sidebar items, dense frame headers)
│ │ ├─ Text.Presets.MonoSm(text, style?) — small mono annotation
│ │ ├─ Text.Presets.MetaSm(text, style?) — small muted meta line
│ │ └─ Text.Presets.MonoKpi(text, style?) — 24px mono tabular-nums KPI number
│ ├─ <Heading> — display heading
│ │ └─ Props:
│ │ ├─ children (required) — the heading text
│ │ ├─ as (optional) — rendered element h1…h6
│ │ ├─ textStyle (optional) — heading preset token
│ │ ├─ fontWeight / fontStyle / fontFamily (optional) — face overrides
│ │ ├─ textAlign / textDecoration / lineHeight / letterSpacing (optional) — alignment + rhythm
│ │ └─ …plus BOX bag + COLOR overrides
│ ├─ <Link> — hyperlink
│ │ └─ Props:
│ │ ├─ href (required) — target URL
│ │ ├─ children (required) — link text
│ │ ├─ external (optional) — open in a new tab
│ │ ├─ variant (optional) — underline-on-hover presets
│ │ ├─ colorPalette (optional) — hue theming
│ │ ├─ hoverColor / visitedColor (optional) — state colours
│ │ └─ …plus textDecoration / lineHeight / letterSpacing, BOX bag, COLOR overrides
│ ├─ <Code> — inline code token
│ │ └─ Props:
│ │ ├─ children (required) — the code text
│ │ ├─ variant (optional) — visual preset
│ │ ├─ colorPalette / size (optional) — hue + size
│ │ └─ …plus textDecoration / lineHeight / letterSpacing, BOX bag, COLOR overrides
│ ├─ <CodeBlock> — multi-line code block
│ │ └─ Props:
│ │ ├─ children (required) — the source text
│ │ ├─ language (optional) — syntax-highlight language
│ │ ├─ showLineNumbers / highlightLines (optional) — gutter numbers + highlighted line list
│ │ ├─ showCopyButton (optional) — corner copy affordance
│ │ ├─ wordWrap (optional) — soft-wrap long lines
│ │ ├─ title (optional) — header caption
│ │ ├─ headerBackground / lineNumberColor / highlightBackground (optional) — chrome colours
│ │ └─ …plus BOX bag + COLOR overrides
│ ├─ <List items={…}> — bulleted / numbered list
│ │ └─ Props:
│ │ ├─ items (required) — the entries (strings or nested components — a config prop, not JSX children)
│ │ ├─ variant (optional) — ordered | unordered | dot
│ │ ├─ marker / markerIcon / markerColor (optional) — marker glyph styling
│ │ ├─ colorPalette / gap (optional) — hue + item spacing
│ │ └─ …plus BOX bag + COLOR overrides
│ ├─ <Highlight> — tints query substrings inside its text
│ │ └─ Props:
│ │ ├─ children (required) — the full text
│ │ ├─ query (required) — substrings to highlight
│ │ └─ color / background (optional) — highlight ink + wash, plus BOX bag
│ ├─ <Mark> — semantic <mark> span
│ │ └─ Props:
│ │ ├─ children (required) — the marked text
│ │ ├─ variant (optional) — severity preset
│ │ └─ colorPalette + COLOR overrides + BOX bag (all optional)
│ ├─ <Note> — inset callout / quote
│ │ └─ Props:
│ │ ├─ children (required) — note content
│ │ ├─ variant (optional) — visual preset
│ │ ├─ emphasis (optional) — brand | warn | danger
│ │ ├─ accentColor (optional) — the callout stripe colour
│ │ └─ width / maxWidth / padding / margin / opacity + COLOR overrides (all optional)
│ └─ <Numeric> — tabular-num number with sentiment (shares the Formats vocabulary)
│ └─ Props:
│ ├─ value (required) — the raw number
│ ├─ format (optional) — a shared Chart.format.* spec (see the Formats branch)
│ ├─ sentiment (optional) — positive | negative | neutral colouring
│ ├─ showSign (optional) — always render the +/− sign
│ ├─ textStyle (optional) — typographic preset
│ └─ signColor / color / background / opacity (optional) — ink overrides
│
├─ Buttons (user actions)
│ ├─ <Button> — the standard action button
│ │ └─ Props:
│ │ ├─ children (required) — label text
│ │ ├─ variant (optional) — solid | subtle | outline | ghost | plain
│ │ ├─ colorPalette / size (optional) — hue + size token
│ │ ├─ onClick (optional) — East.function([], NullType) handler
│ │ ├─ startIcon / endIcon (optional) — leading / trailing icon ({ prefix, name })
│ │ ├─ loading / loadingText / loadingIcon (optional) — spinner state + swapped label/icon
│ │ ├─ disabled (optional) — blocks interaction
│ │ └─ hoverBackground + COLOR overrides (optional) — palette escape hatches
│ ├─ <IconButton> — icon-only button
│ │ └─ Props:
│ │ ├─ prefix / name (required) — Font Awesome icon ("fas", "chevron-right")
│ │ ├─ label (required) — accessible aria-label
│ │ ├─ variant / colorPalette / size (optional) — as Button
│ │ ├─ onClick / loading / loadingIcon / disabled (optional) — as Button
│ │ ├─ badge (optional) — superscript count text ("99+", "" = dot-only)
│ │ ├─ badgeColorPalette (optional) — badge hue (default red)
│ │ ├─ attention (optional) — "pulse" blinks the badge, "ring" rings the button
│ │ └─ hoverBackground + COLOR overrides (optional)
│ ├─ <CloseButton> — × dismiss button
│ │ └─ Props: variant / size / label (aria, default "Close") / disabled / onClick / hoverBackground + COLOR overrides (all optional)
│ ├─ <CopyButton> — copies to clipboard with ✓ feedback
│ │ └─ Props:
│ │ ├─ value (required) — the text copied
│ │ ├─ label (optional) — text next to the copy icon
│ │ ├─ timeout (optional) — "Copied!" duration ms
│ │ ├─ successColor (optional) — confirmation glyph tint
│ │ └─ variant / colorPalette / size / disabled / hoverBackground + COLOR overrides (optional)
│ ├─ <Toggle> — pressable on/off button (NOT a form toggle — see <Switch>)
│ │ └─ Props:
│ │ ├─ pressed (required) — current state (Toggle has no internal state)
│ │ ├─ children (required) — label
│ │ ├─ onChange (optional) — fn(Boolean) => Null
│ │ ├─ icon (optional) — leading icon
│ │ ├─ pressedBackground / pressedColor (optional) — pressed-state colours
│ │ └─ variant / size / disabled + COLOR overrides (optional)
│ └─ <ButtonGroup> — row/col cluster of buttons
│ └─ Props:
│ ├─ children (required) — the buttons
│ ├─ attached (optional) — join into one control with shared borders
│ └─ gap / borderColor (optional) — spacing when not attached; shared border colour when attached
│
├─ Forms (user input)
│ ├─ <Input> — typed text inputs (namespace tag)
│ │ ├─ Nested tags: <Input.String> <Input.Integer> <Input.Float> <Input.DateTime> — value-typed variants
│ │ └─ Props (shared unless noted):
│ │ ├─ value (required) — current value (String / Integer / Float / DateTime respectively)
│ │ ├─ onChange (optional) — fn(newValue) => Null (typed per variant)
│ │ ├─ onBlur / onFocus (optional) — focus lifecycle
│ │ ├─ variant (optional) — outline | subtle | flushed
│ │ ├─ size (optional) — xs | sm | md | lg
│ │ ├─ disabled (optional) — blocks input
│ │ ├─ autoFocus (optional) — focus on first mount
│ │ ├─ String: placeholder / maxLength / pattern (optional) — text constraints
│ │ ├─ Integer & Float: min / max / step (optional); Float: precision (decimal places)
│ │ ├─ DateTime: min / max / precision (date|time|datetime) / format (token list) (optional)
│ │ └─ focusBorderColor / placeholderColor + COLOR overrides (optional)
│ ├─ <Textarea> — multi-line input
│ │ └─ Props:
│ │ ├─ value (required) — current text
│ │ ├─ onChange / onBlur / onFocus / onValidate (optional) — edit + focus callbacks
│ │ ├─ placeholder / rows / maxLength / autoresize (optional) — sizing + constraints
│ │ ├─ resize (optional) — none | vertical | horizontal | both
│ │ ├─ disabled / readOnly / required / invalid (optional) — form state
│ │ └─ variant / size / focusBorderColor + COLOR overrides (optional)
│ ├─ <Select> — single/multi-select dropdown
│ │ ├─ Props:
│ │ │ ├─ value (required) — selected value ("" = none)
│ │ │ ├─ items (required) — array of Select.Item(…)
│ │ │ ├─ onChange (optional) — fn(String) => Null (single)
│ │ │ ├─ onChangeMultiple (optional) — fn(Array<String>) => Null (multi)
│ │ │ ├─ multiple / placeholder / disabled / size (optional) — behaviour + chrome
│ │ │ ├─ onOpenChange (optional) — dropdown open/close callback
│ │ │ └─ COLOR overrides (optional) — trigger colours
│ │ └─ Factories:
│ │ └─ Select.Item(value, label, { disabled? }) — one option
│ ├─ <Combobox> — typeahead / filter select
│ │ ├─ Props:
│ │ │ ├─ value (required) — current input / selected value
│ │ │ ├─ items (required) — array of Combobox.Item(…)
│ │ │ ├─ onChange / onChangeMultiple (optional) — selection callbacks (single / multi)
│ │ │ ├─ onInputValueChange (optional) — fires as the user types
│ │ │ ├─ allowCustomValue (optional) — accept values not in the list
│ │ │ ├─ multiple / placeholder / disabled / size / onOpenChange (optional) — as Select
│ │ │ └─ COLOR overrides (optional)
│ │ └─ Factories:
│ │ └─ Combobox.Item(value, label, { disabled? }) — one option
│ ├─ <Checkbox> — boolean checkbox
│ │ └─ Props:
│ │ ├─ checked (required) — current state
│ │ ├─ onChange (optional) — fn(Boolean) => Null
│ │ ├─ label (optional) — trailing text
│ │ ├─ indeterminate (optional) — partial-selection dash
│ │ ├─ disabled / colorPalette / size (optional)
│ │ └─ fillColor / checkColor / borderColor (optional) — slot colours
│ ├─ <Switch> — form on/off toggle (binds a boolean)
│ │ └─ Props:
│ │ ├─ checked (required) — current state
│ │ ├─ onChange (optional) — fn(Boolean) => Null
│ │ ├─ label / disabled / colorPalette / size (optional)
│ │ └─ onColor / offColor / thumbColor (optional) — track + knob colours
│ ├─ <Slider> — range slider
│ │ └─ Props:
│ │ ├─ value (required) — current value
│ │ ├─ onChange (optional) — fires during drag; onChangeEnd (optional) — fires on release
│ │ ├─ min / max / step (optional) — range (defaults 0–100)
│ │ ├─ orientation / variant / colorPalette / size / disabled (optional)
│ │ └─ trackColor / fillColor / thumbColor / markColor (optional) — slot colours
│ ├─ <RadioGroup> — single-select radio list
│ │ └─ Props:
│ │ ├─ value (required) — selected value ("" = none)
│ │ ├─ items (required) — [{ value, label?, disabled? }]
│ │ ├─ onChange (optional) — fn(String) => Null
│ │ ├─ orientation / name / disabled / required / colorPalette / size (optional)
│ │ └─ color / fillColor / borderColor (optional) — ink + radio colours
│ ├─ <RadioCardGroup> — radios rendered as picker cards
│ │ └─ Props:
│ │ ├─ value (required) — selected card value
│ │ ├─ items (required) — [{ value, label, description?, disabled? }]
│ │ ├─ onChange (optional) — fn(String) => Null
│ │ ├─ orientation / name / disabled / required / colorPalette / size (optional)
│ │ └─ color / descriptionColor / cardBackground / selectedCardBackground / selectedBorderColor (optional)
│ ├─ <TagsInput> — typeahead chip input
│ │ └─ Props:
│ │ ├─ defaultValue (optional) — initial tags
│ │ ├─ onChange (optional) — fn(Array<String>) => Null with the new tag set
│ │ ├─ suggestions (optional) — autocomplete list (free entry still allowed)
│ │ ├─ max / maxLength / allowOverflow (optional) — tag-count / length constraints
│ │ ├─ editable / delimiter / addOnPaste / blurBehavior (optional) — editing behaviours
│ │ ├─ onInputChange / onHighlightChange (optional) — typing + highlight callbacks
│ │ ├─ label / placeholder / disabled / readOnly / invalid (optional) — chrome + state
│ │ ├─ variant / size / colorPalette (optional)
│ │ └─ tagBackground / tagColor / tagBorderColor + COLOR overrides (optional) — per-chip colours
│ ├─ <FileUpload> — drop-zone file picker
│ │ └─ Props:
│ │ ├─ onFileAccept (optional) — fn(files) => Null; onFileReject (optional) — fn(rejections) => Null
│ │ ├─ accept / maxFiles / maxFileSize / minFileSize (optional) — acceptance constraints
│ │ ├─ directory / allowDrop / capture (optional) — folder upload, drag-drop, mobile camera
│ │ ├─ label / dropzoneText / triggerText / orientation (optional) — copy + layout
│ │ ├─ disabled / required / name (optional) — form state
│ │ └─ variant / size / dropzoneBackground / dropzoneBorderColor / activeBackground + COLOR overrides (optional)
│ ├─ <Field> — form-field wrapper (label + control + helper/error)
│ │ └─ Props:
│ │ ├─ label (required) — the field caption; children (required) — the control
│ │ ├─ helperText / errorText (optional) — descriptive + validation lines
│ │ ├─ required / disabled / invalid / readOnly (optional) — form state
│ │ ├─ orientation (optional) — label/control layout
│ │ └─ labelColor / helperTextColor / errorColor / warningColor / infoColor / requiredIndicatorColor (optional)
│ ├─ <DateRangeInput> — start–end date pair with preset chips
│ │ └─ Props:
│ │ ├─ startValue / endValue (required) — the range (UTC DateTime pair)
│ │ ├─ onChange (optional) — fn(start, end) => Null
│ │ ├─ min / max / precision (optional) — bounds + picker precision (date|minute|second)
│ │ ├─ presets (optional) — [{ label, start, end }] preset rows above the inputs
│ │ ├─ disabled (optional)
│ │ └─ variant / size / focusBorderColor + COLOR overrides (optional)
│ └─ <TimeRangeInput> — start–end time pair (minutes since midnight)
│ └─ Props:
│ ├─ startValue / endValue (required) — minutes 0–1439
│ ├─ onChange (optional) — fn(startMin, endMin) => Null
│ ├─ min / max / step (optional) — bounds + picker step (default 15)
│ ├─ presets (optional) — [{ label, start, end }]
│ ├─ disabled (optional)
│ └─ variant / size / focusBorderColor + COLOR overrides (optional)
│
├─ Collections (display data sets) — structured data on `data=` / `columns=` / `items=` props
│ ├─ <Table data={rows} columns={…} /> — sortable / pinnable / virtualized data grid; generic pass-through (column/cell inference preserved)
│ │ ├─ Props:
│ │ │ ├─ data (required) — array of row structs
│ │ │ ├─ columns (required) — keyed config: ["a","b"] or { a: { header, width, value?, render?, aggregate?, aggregateRender?, … } }; column `render` is an East fn ({rowIndex, columnKey, cellValue} → UIComponent) called per VISIBLE cell — full-row access = capture the data array + index it (($, ctx) => { const row = $.let(rows.get(ctx.rowIndex)); … }); render/on* fns may capture only data + bind-handles — never a UIComponentType value (beast2 can't serialize it)
│ │ │ ├─ groupBy (optional) — [accessor | { value, collapsed? }] nested collapsible group header rows (#317); groups keep first-appearance DATA order (never alphabetized), sort is group-scoped; columns with aggregate:"sum"|"mean"|"min"|"max"|"count" show subtotals ON the group row (a collapsed group reads as its subtotal line); aggregateRender formats them (East fn over the aggregated cell value — a group row has no rowIndex); grand totals stay in footerRows
│ │ │ ├─ columnGroups (optional) — column-group heading row (type-checked columnKeys)
│ │ │ ├─ footer / footerRows (optional) — one / many footer rows, keys narrowed to the table's columns
│ │ │ ├─ expandedContent (optional) — fn(rowIndex) => UIComponent expandable row detail (UNSLICED row index — stable under sorting AND pagination)
│ │ │ ├─ frozen (optional) — column keys pinned left (visible during horizontal scroll)
│ │ │ ├─ height / maxHeight (optional) — uniform sizing (#320): pin or cap the table; chrome-inclusive, rows scroll within
│ │ │ ├─ variant / size / striped / interactive / stickyHeader / showColumnBorder (optional) — grid chrome
│ │ │ ├─ density (optional) — row rhythm preset; rowHeight (optional) — explicit px override (fed to the virtualizer)
│ │ │ ├─ virtualization / columnResize (optional) — row virtualization + header drag-resize
│ │ │ ├─ selection (optional) — { mode, selected, onChange } embedded row-selection state
│ │ │ ├─ pagination (optional) — { pageSize, page, onPageChange } embedded pager
│ │ │ ├─ onCellClick / onCellDoubleClick / onRowClick / onRowDoubleClick / onRowSelectionChange / onSortChange (optional) — interaction callbacks
│ │ │ ├─ rowStatus (optional) — fn(rowIndex) => StatusToken row tint (see the Statuses branch)
│ │ │ ├─ review / reviewStatus / reviewApproval (optional) — pinned-right Decision column + commitBar foot BELOW the pager; rowIndex is the UNSLICED index
│ │ │ ├─ slice + affordances (optional) — bound slice chrome (default ["filter","search"]); filtering flows through the slice interface
│ │ │ ├─ plotGutter (optional) — shared plot gutter (#147); frozen columns fill `left` (usually supplied by <AlignedStack>)
│ │ │ └─ colorPalette + headerBackground / headerColor / zebraBackground / hoverBackground / selectedBackground / selectedBorderColor / footerBackground / borderColor (optional) — chrome colours
│ │ └─ Factories: (columns/footers are plain config objects; no builders)
│ ├─ <DataList items={…} /> — label/value pairs
│ │ └─ Props:
│ │ ├─ items (required) — [{ label: String, value: UIComponent }] (plain structs — no builder needed)
│ │ ├─ orientation / size / variant (optional) — pair layout + chrome
│ │ └─ labelColor / valueColor / background / borderColor (optional)
│ ├─ <TreeView nodes={…} /> — expandable hierarchical tree with selection
│ │ ├─ Props:
│ │ │ ├─ nodes (required) — array of TreeView.Item / TreeView.Branch values
│ │ │ ├─ selectionMode (optional) — selection cardinality
│ │ │ ├─ defaultExpandedValue / defaultSelectedValue (optional) — initial expansion / selection
│ │ │ ├─ onExpandedChange / onSelectionChange / onFocusChange (optional) — interaction callbacks
│ │ │ ├─ size / variant / animateContent / label (optional) — chrome
│ │ │ └─ itemColor / itemHoverBackground / selectedBackground / selectedColor / caretColor / connectorColor (optional)
│ │ └─ Factories:
│ │ ├─ TreeView.Item(value, label, indicator?) — leaf node (indicator = FA icon + style)
│ │ └─ TreeView.Branch(value, label, children, indicator?, disabled?) — expandable node
│ ├─ <Gantt data={rows} columns={…} rowSpec={row => ({ tasks: … })} /> — table + time-bar timeline
│ │ ├─ Props:
│ │ │ ├─ data / columns (required) — the left table (same column config family as <Table>); frozen (optional) pins keys left
│ │ │ ├─ rowSpec (required) — per-row accessor returning { tasks?: [Gantt.Task(…)], milestones?: [Gantt.Milestone(…)], status?, approval? } (status/approval feed the Decision column — only rendered when `review` is set)
│ │ │ ├─ axis (optional) — { range?: {min,max}, format?: date pattern ("MMM YYYY"), tier?: header granularity } — omit to fit the domain to the data
│ │ │ ├─ showToday (optional) — the now-line
│ │ │ ├─ dragStep / durationStep (optional) — drag / resize snapping steps
│ │ │ ├─ height / maxHeight (optional) — uniform sizing (#320)
│ │ │ ├─ variant / size / striped / stickyHeader / showColumnBorder (optional) — table chrome
│ │ │ ├─ density / rowHeight (optional) — rhythm (rowHeight overrides, flows to virtualizer + bars)
│ │ │ ├─ rowStatus (optional) — fn(rowIndex) => StatusToken row tint
│ │ │ ├─ review (optional) — shared Decision column + commitBar foot ({rowIndex} events, identical to Planner's)
│ │ │ ├─ onCellClick / onCellDoubleClick / onRowClick / onRowDoubleClick / onSortChange (optional) — table callbacks
│ │ │ ├─ onTaskClick / onTaskDoubleClick / onTaskProgressChange / onMilestoneClick / onMilestoneDoubleClick (optional) — timeline callbacks
│ │ │ ├─ id + sources + onDrag + canDrop (optional) — DnD target (ONE grammar funnel: Library `add` lands proposed(added) bars at the dragStep-snapped instant; task-body drags = `move`, edge drags = `resize`; row = row index key, slot = snapped ISO instant, event = t<i>/m<i>) + canDrop veto (⊘, pointer-resolved); progress-handle drag stays bespoke (onTaskProgressChange — not a spatial drag)
│ │ │ ├─ slice + affordances (optional) — bound slice chrome (default ["filter","search","range"]; the timeline header doubles as a brush)
│ │ │ └─ plotGutter (optional) — shared gutter (#147); frozen table columns fill `left`
│ │ └─ Factories:
│ │ ├─ Gantt.Task({ start, end, label?, progress?, state?, status?, popover? }) — one bar; `state` is the SHARED lifecycle (PlannerStateType via Gantt.Types.State: "committed"|"added"|"model"|"removed"|"rejected" — committed solid, proposals dashed/ghost/struck, rejected greyed; only proposed bars drag/resize); `status` is the orthogonal risk tint (Gantt.Types.Status — "danger" = the old atRisk); popover = click-triggered rich body
│ │ └─ Gantt.Milestone({ date, label?, kind?, popover? }) — diamond marker; kind: interim (amber) | release (brand teal, default)
│ ├─ <Planner.Point …> / <Planner.Span …> — discrete rows × ordered-slot scheduler (Point = instant events, Span = ranges)
│ │ ├─ Props:
│ │ │ ├─ data (required) — row structs; axis (required) — a Planner.axis.* declaration
│ │ │ ├─ columns (required) — [{ key, header?, width?, frozen?, align?, value, sublabel? }] left-side columns (value/sublabel are row accessors)
│ │ │ ├─ events (required) — per-row accessor returning Planner.event(…) values
│ │ │ ├─ markers (optional) — per-row accessor returning Planner.marker(…) values
│ │ │ ├─ groupBy (optional) — per-row group-head label accessor
│ │ │ ├─ now (optional) — explicit committed/proposed divider slot
│ │ │ ├─ height (optional) — pin the plan area (header pinned, body scrolls, like Table stickyHeader); maxHeight (optional) — cap; absent ⇒ content-sized; slotMinWidth (optional) — drives horizontal slot scroll
│ │ │ ├─ density (optional) — row/header rhythm
│ │ │ ├─ status / approval + review (optional) — per-row Approve/Reject Decision column + commitBar batch foot (clean ⇒ approved, flagged ⇒ pending via deriveApproval; {rowIndex} events)
│ │ │ ├─ onSelectRow / rowHover (optional) — selection callback + row hover affordance
│ │ │ ├─ id + sources + onDrag + canDrop (optional) — opt-in DnD target (#269): a Planner without onDrag is exactly click-only; PROPOSED tiles drag (committed history inert; tiles need an authored event key), drops land proposed(added); slot keys compose the bucket in ("wed" / "wed:am"); Span edges resize via the shared runtime
│ │ │ └─ plotGutter (optional) — shared gutter (#147); frozen channel columns fill `left`
│ │ └─ Factories:
│ │ ├─ Planner.axis.time({ resolution?, format?, range?, buckets? }) — calendar axis (#309): resolution "hour"|"day"|"week"|"month"|"quarter"|"year" sets the column unit; omitted, a PINNED range ≤ 14 days infers day columns (else month). A pinned range is half-open [min, max), interpreted in UTC (#326 — East DateTime is a UTC instant, so columns are timezone-independent) and authoritative (events outside are culled, never grow the axis). {Mar 30 … Apr 6} at day resolution = Mon 30 … Sun 05, and a sibling Chart pinning the same [min, max] time domain lines its day ticks up cell-for-cell under an AlignedStack gutter. format uses the Chart date tokens ("ddd DD" → Mon 30, in UTC); day default "ddd DD". Drag slot keys stay period-start ISO instants
│ │ ├─ Planner.axis.number({ range?, buckets?, format? }) — numeric slot axis
│ │ ├─ Planner.axis.ordinal({ range?, buckets?, format? }) — explicit ordered slot list
│ │ ├─ Planner.event({ key?, slot, endSlot?, bucket?, label, state, popover?, hovercard?, stretch?, content?, tone?, colorPalette?, color?, animation? }) — one tile; state = audit lifecycle ("committed"|"added"|"model"|"removed"|"rejected"); tone = semantic status tint; popover/hovercard = rich click / hover bodies
│ │ └─ Planner.marker({ slot, status?, message }) — cell status ring (status defaults "danger"; message surfaces as tooltip)
│ ├─ <Matrix data={…} columns={…} cell={(r, col) => Matrix.cell({…})} /> — rows × columns of status-coloured segment bars
│ │ ├─ Props:
│ │ │ ├─ data (required) — row structs; columns (required) — array of Matrix.column(…) (data-drivable with .map)
│ │ │ ├─ cell (required) — (row, column) => Matrix.cell(…) builder
│ │ │ ├─ rowHeader (optional) — header label for the left identity column
│ │ │ ├─ orientation (optional) — default segment orientation
│ │ │ └─ legend (optional) — explicit legend entries [{ fill, label }] (omitted ⇒ auto-derived)
│ │ └─ Factories:
│ │ ├─ Matrix.column({ key, label? }) — one x-axis column
│ │ ├─ Matrix.cell({ segments?, markers?, orientation?, slot?, popover? }) — one cell (slot = arbitrary UIComponent; popover = click body)
│ │ ├─ Matrix.segment({ fill?, weight, label?, color?, min?, max?, step? }) — one segment of the cell bar
│ │ └─ Matrix.marker({ status?, message, at?, label? }) — corner status marker
│ ├─ <Calendar data={days} cell={d => ({…})} /> — day-of-week × week HEATMAP (8-step teal ramp, theme-aware; cols always Mon–Sun); viz-only (no events / drag). Hover cross-highlights the row + column; click selects (footer + onSelect)
│ │ ├─ Props:
│ │ │ ├─ data (required) — day rows; cell (optional) — row mapper to { week, day, value, text?, compare?, summary? } (omit when data is already Calendar.Types.Cell). `compare` is the footer baseline (e.g. last year) → drives the delta chip
│ │ │ ├─ values (optional) — print the number in each cell (default true; false = pure heat read)
│ │ │ ├─ scale (optional) — Calendar.scale({…}) heatmap ramp / bucket count
│ │ │ ├─ domain (optional) — explicit intensity { min, max } (default observed)
│ │ │ ├─ totals (optional) — Calendar.totals({…}) the Σ-wk rail (per-WEEK aggregation)
│ │ │ ├─ aggregateRow (optional) — Calendar.aggregateRow({…}) the trailing row (per-WEEKDAY aggregation, e.g. mean)
│ │ │ ├─ footer (optional) — Calendar.footer({…}) the selection footer (value / compare / delta chip + gradient legend)
│ │ │ ├─ actionLabel + onAction (optional) — footer drill affordance (receives the selected cell)
│ │ │ ├─ onSelect (optional) — cell-click callback
│ │ │ ├─ density / height / maxHeight (optional) — rhythm (comfortable=large / compact / condensed=tight) + uniform sizing (#320)
│ │ │ └─ plotGutter (optional) — shared gutter (#147); `left` = the week-label column (drops the totals/mean/footer chrome to keep the day axis aligned)
│ │ └─ Factories:
│ │ ├─ Calendar.scale({ ramp?, steps? }) — heatmap colour scale (ramp = low→high CSS colours, absent = default teal ramp; steps = bucket count)
│ │ ├─ Calendar.totals({ aggregate?, label?, bar? }) — the weekly rail (aggregate sum/mean/min/max/count — SAME vocabulary as Table; label "Σ wk"; bar = proportion bar)
│ │ ├─ Calendar.aggregateRow({ aggregate?, label? }) — the per-weekday row (aggregate default "mean", label "mean")
│ │ └─ Calendar.footer({ valueLabel?, compareLabel?, legend? }) — selection footer labels + the low→high gradient legend (legend: true | { low, high })
│ ├─ <Schematic items={rows} extent={{width,height}} item={r => ({…})} /> — 2D world-coord canvas: items / zones / links / nets from flat tables
│ │ ├─ Props:
│ │ │ ├─ items + extent (required) — item rows + world bounds (canvas scales to fit)
│ │ │ ├─ item (optional) — row mapper to { key, x, y, label, sublabel?, icon?, status?, meter?{value,max}, metric?, width?, footprint?, tone?, color?, bg?, fillOpacity?, weight?, excluded?, layer? } (omit when already Schematic.Types.Item)
│ │ │ ├─ zones + zone (optional) — zone rows + mapper to { key, label, x, y, width, height, pattern?, geometry?, tone?, color?, bg?, fillOpacity?, weight?, layer? }
│ │ │ ├─ links + link (optional) — link rows + mapper to { key, from, to, label?, metric?, style?, route?, via?, layer? }
│ │ │ ├─ nets + net (optional) — manifold/bus rows (ONE row = many sources → many destinations) + mapper to { key, sources, destinations, label?, metric?, style?, route?, via?, layer? }; drawn P&ID-style — a header BAR spans each multi-endpoint side with a stub per endpoint, the trunk runs bar → via… → bar (junction dots ONLY where a tap 3-way joins a bar)
│ │ │ ├─ selectionMode (optional) — "single" (default) | "multiple" | "range"; multiple ⇒ marquee tool (drag-box multi-select w/ live preview + count; plain box/tap replaces, Shift extends)
│ │ │ ├─ onSelect / onSelectionChange (optional) — item click (key) / full selection-set events ({key?, selected, selectedKeys, additive, region?}); works in every tool (grab/zoom/marquee)
│ │ │ ├─ onSelectZone / onZoneSelectionChange (optional) — zone click-select (items win hit-test; innermost zone wins; Shift extends) reporting the zones AND their childItemKeys
│ │ │ ├─ selectZoomFocus (optional) — a canvas selection also moves the camera (tap flies, marquee fits)
│ │ │ ├─ onItemOpen (optional) — double-click drill-in (background double-click keeps Fit/reset)
│ │ │ ├─ onViewportChange (optional) — debounced viewport-settled reporting ({zoom, minX, minY, maxX, maxY}) for sync / lazy-load / persist
│ │ │ ├─ linkMode (optional) — connect-gesture mode: "draw" (adds locally, form-input style) | "connect" (event-only, repeatable: plan operations); Shift+drag ADDS to the session
│ │ │ ├─ onCreateLink / onSelectLink / onEditLink / onDeleteLink (optional) — link lifecycle ({link, links, additive, existing} on create; click-select key; endpoint re-target; Del delete)
│ │ │ ├─ canConnect (optional) — fn(from, to) => Bool vetoes pairs BEFORE they resolve (the draft never snaps; one rule covers links, Shift-session/net extensions, and re-targets; a throwing validator fails OPEN)
│ │ │ ├─ onEditNet (optional) — net membership edits: trunk/bar click selects the WHOLE net, a STUB click selects ONE leg (Del removes just that endpoint; onEditNet reports membership AFTER; a side emptying deletes the whole net via onDeleteLink). Shift-session gestures keep ONE bus (a member never flips sides); with a LINK selected, Shift-drag from its endpoint SEEDS the session with that link (onCreateLink.absorbed lists absorbed keys — delete those rows when upserting); with a NET selected, Shift-drag out of a member adds the target as a leg; a Shift connect-session commits as a net (onCreateLink.net = {key, sources, destinations}, stable session key — upsert by net.key)
│ │ │ ├─ readOnly / readOnlyLinks / readOnlyItems (optional) — flattened per-domain edit gates
│ │ │ ├─ onMoveItem (optional) — move tool (readOnlyItems off): drags items to new positions; a selected item moves the WHOLE selection rigidly, local-first; fires once per gesture ({key, x, y, keys, dx, dy})
│ │ │ ├─ itemHover / zoneHover / linkHover (optional) — East fn key => UIComponent lazy hover cards (charts in a card over a tank / pipe); any camera or edit gesture closes them; hover ignores readOnly + locked layers
│ │ │ ├─ slice + affordances (optional) — bound slice chrome (default ["search"]); flat effect props sliceHidden / sliceOpacity / sliceDesaturate / sliceDot / sliceEmphasis:"halo"|"pulse" / sliceFrame / sliceFrameFit keep filtered-OUT items as ghost/desaturate/dot context + emphasise the remainder instead of hiding — feed the FULL set (Slice.partition) and mark item.excluded (e.g. t.matched.not())
│ │ │ ├─ sliceSelectField (optional) — bound-slice fieldId a marquee/tap selection writes an `in` filter of selected item keys into (one-directional selection→slice) — pair with the ghost effect (not a Slice.rows feed on the same slice)
│ │ │ ├─ layers (optional) — [{ key, label, tone?, visible?, locked?, opacity? }] + tag items/zones/links with layer:"key"; a canvas layer button opens a show/hide/solo/lock panel (persists per panel); lock ⇒ non-selectable (click-through), opacity dims
│ │ │ ├─ scaleUnit / grid / navigator / minimap (optional) — scale bar unit, metric grid (default on), zones→items TOC (default when zones exist), minimap (default 25+ items)
│ │ │ └─ height / maxHeight (optional) — uniform sizing (#320); default aspect-driven, capped 75vh
│ │ └─ Factories:
│ │ ├─ Schematic.circle(r) / .polyline(verts, {width}) / .polygon(verts) / .rect() — item footprints + zone geometry
│ │ ├─ Schematic.outline() / .hatch() — zone boundary patterns
│ │ └─ Schematic.solid() / .dashed() — link / net stroke styles
│ ├─ <Flowchart states={…} links={…} lanes={…} /> — state-transition flowchart: states as nodes in ORDERED phase lanes (layout derived — no coordinates), H/V-routed transition arrows, optional per-link decision triggers (lettered diamonds); dim-ladder highlight built in; hover content is DEV-DEFINED (the Schematic contract); view lenses are saved slice cohorts
│ │ ├─ Props:
│ │ │ ├─ states + links + lanes (required) — the three tables (lanes accept a literal [{ key, label? }] array; array order = band order)
│ │ │ ├─ state / link / lane / trigger (optional) — row mappers to { key, label?, lane, members?, notes? } / { key?, from, to, kind?, trigger?, evidence? } / { key, label? } / { key, label, letter?, owner?, queue?, outcomes? }; omit when rows are already Flowchart.Types.*
│ │ │ ├─ triggers (optional) — decision registry; a link's `trigger` names one (0..1 per link ⇒ the lettered diamond at the longest-run midpoint; clicking it highlights governed links)
│ │ │ ├─ link `kind` "planned" (solid, default) | "observed" (dashed 5/4); DERIVED marks: a from/to ref with no state row ⇒ the neg-dashed ghost "No state row" node (unresolved, counted in the footer); from == to folds to the `↻ n` badge (never routed); `members` ⇒ the ×N state-class badge
│ │ │ ├─ link `evidence` { volume?, count?, measuredAt?, unit? } — stroke weight (log 1.6 / 2 / 2.5 px, floor 1.4) + paper-filled run badges whose chrome inherits the link class (imported, never hand-authored)
│ │ │ ├─ stateHover / linkHover / triggerHover (optional) — hover-card content builders (East fn key => UIComponent, evaluated lazily on hover in the standard 400ms shell); absent ⇒ no hover card; detail drills through the click callbacks (open a <Drawer> in the handler)
│ │ │ ├─ orientation (optional) — "LR" (default) | "TD" initial; the eyebrow segment toggles it (view state, never a filter chip; TD swaps the handle axes); freshness (optional) — eyebrow chip { label, date? }
│ │ │ ├─ legend / minimap (optional) — legend default true (reserves canvas space); minimap auto at ≥ 25 states
│ │ │ ├─ slice + affordances (optional) — bound slice chrome at compact density (default ["filter","search"]; search = "⌕ find state"; "brush" is a build-time error — no continuous 1D axis); the footer derives `N links · narrowed from M · −%` + the planned/observed split
│ │ │ ├─ onSelectState / onSelectLink / onSelectTrigger / onTracePath (optional) — click / ⌥-click callbacks (entity keys); Esc restores everything instantly
│ │ │ ├─ linkMode + onCreateLink + onDeleteLink + canConnect (optional) — drag-to-connect authoring from ANY handle ("draw" | "connect"; links join at the closest FACING handle pair; canConnect(from, to) vetoes BEFORE the draft snaps and fails OPEN; dropping on the SOURCE node commits an ↻ in-place transition; the drag previews the spec-compliant H/V route; Del deletes the selected link)
│ │ │ ├─ onAddLane (optional) — its presence renders the dashed full-height "+ LANE" tail affordance (click fires it); absent ⇒ no affordance
│ │ │ ├─ onRenameLane + onDeleteLane (optional) — lane editing: headers become click-to-edit (Enter/blur commits → { key, label }); × beside each header deletes (lane key). The HOST owns the cascade — the canvas stays safe either way: states referencing a missing lane fall into the LAST lane, dangling links render as neg-dashed ghosts (orphans stay visible)
│ │ │ ├─ onAddState + onEditState + onMoveState (optional) — state editing: hovering a lane band reveals the dashed node-footprint "+ STATE" ghost parked one row below its last node (click → inline editor, code auto-focused + label; ⏎ commits { lane, key, label }, esc/blur-empty dismisses; the committed state starts unconnected); double-click a node opens the same editor ({ key, code, label } — rekeying links is the host's call); dragging a node across lanes highlights candidate bands and drops fire { key, lane }
│ │ │ ├─ readOnly (optional) — runtime edit gate: true suppresses every authoring affordance (connect gesture, Del, + LANE) WITHOUT unwiring callbacks (feed a permission / published-mode flag); read-only is otherwise the DEFAULT — each edit channel exists only when its callback / mode is provided; selection + hover always stay (inspecting isn't editing)
│ │ │ └─ density / height / maxHeight (optional) — rhythm + uniform sizing (#320); default content-sized
│ │ └─ Factories: (tables are plain rows + mappers; closed-set fields are typed values via Flowchart.Types.* — State, Link, Lane, Trigger, Evidence, Kind, Orientation, LinkMode, LinkCreateEvent)
│ ├─ <Map markers={…} center={Map.at(lat,lng)} zoom={n} /> — interactive geographic basemap; read-only / selection-only
│ │ ├─ Props:
│ │ │ ├─ markers + center + zoom (required) — marker rows + initial camera
│ │ │ ├─ marker / area / line / label (optional) — row mappers for each table (omit when rows are already Map.Types.* values)
│ │ │ ├─ areas / lines / labels (optional) — the additional overlay tables
│ │ │ ├─ hexes (optional) — H3 lattice + per-cell detail (Map.hex(…))
│ │ │ ├─ overlays (optional) — positioned East children (Map.overlay(child, { align }))
│ │ │ ├─ tiles (optional) — basemap (default Map.carto("positron"))
│ │ │ ├─ minZoom / maxZoom / lodZoom / fitBounds (optional) — zoom clamps, detail-LOD threshold, camera framing
│ │ │ ├─ onAreaClick / onMarkerClick / onZoom / onSelect (optional) — interaction callbacks
│ │ │ └─ scrollWheelZoom / attributionPrefix / height (optional) — chrome
│ │ └─ Factories:
│ │ ├─ Map.at(lat, lng) — a coordinate; Map.point(…) / Map.bounds(…) — flyTo targets
│ │ ├─ Map.carto(style) / Map.osm() / Map.tile(url, …) — basemaps
│ │ ├─ Map.hexDisk(…) / Map.cells(…) / Map.polygon(…) — H3 shapes; Map.hex(…) — the hex layer
│ │ ├─ Map.marker(…) / Map.area(…) / Map.line(…) / Map.label(…) — overlay values
│ │ ├─ Map.solid() / Map.dashed() — line styles
│ │ └─ Map.overlay(child, { align }) — a positioned East child
│ ├─ <Library id="people" data={rows} item={r => ({…})} /> — draggable palette (DnD source; targets list its id in their `sources`)
│ │ ├─ Props:
│ │ │ ├─ id (required) — DnD source identity
│ │ │ ├─ data (required) — item rows
│ │ │ ├─ item (required) — row mapper to { key, label, sublabel?, icon?, status?, draggable?, filtered? }
│ │ │ ├─ hint (optional) — header-right caption (absent ⇒ no header band)
│ │ │ ├─ dimensions + defaultDimensions (optional) — toolbar-toggleable card facts ({ kind: "meter" | "chips" | "text", … }); initially-visible keys (default first two)
│ │ │ ├─ groupBy (optional) — [{ key, label, value, summary? }] GROUP BY options (omit for flat)
│ │ │ ├─ search (optional) — filter-text accessor; unmatched cards hide (footer shows hidden count + Show all)
│ │ │ ├─ addLabel + onAdd (optional) — footer action
│ │ │ ├─ slice + affordances (optional) — bound slice chrome (default ["filter","search"])
│ │ │ └─ style (optional) — { height, maxHeight, virtualization }
│ │ └─ Factories:
│ │ └─ Library.status(label, tone) — a card status chip (tone = a status token; see the Statuses branch)
│ ├─ <Deck data={rows} statuses={Deck.statuses({…})} card={r => ({ key, title, status, metrics, fill })} /> — grouped mini-card board (display, NOT a drag source — that's Library); every card carries an EXPLICIT status colour from the deck's STATUS REGISTRY (solid tag + dot, faint face wash, fill-bar colour); two card states: the LIST face + a VIEW state in an anchored POPOVER CARD whose head is inherited from the face
│ │ ├─ Props:
│ │ │ ├─ data (required) — array of rows to project into cards
│ │ │ ├─ card (required) — accessor r => face fields: key (required; identity reported by onCardClick/onOpen) · title (required; identity line) · sublabel (muted mono-uppercase second line) · icon (FA solid name) · status (a KEY into `statuses` — paints the tag/wash/fill) · metrics ([Deck.metric(…)] raw-value strip) · fill ({ value, max, format? } status-coloured bar; format = shared spec over value OR (value, max) => String accessor; omitted → percentage) · facts ([Deck.meter/chips/text(…)]) · filtered (render dimmed — the Slice.partition "keep the excluded" feed)
│ │ │ ├─ statuses (optional) — the status registry, Deck.statuses({…}); card `status` fields reference entries by key
│ │ │ ├─ groupBy (optional) — [{ key, label, value, summary? }] named GROUP BY toolbar + None; grouping by the status accessor decorates group heads with the registry swatch + hint
│ │ │ ├─ layout (optional) — "grid" (default; wrap auto-fill minmax(minCardWidth,1fr) → one phone column) | "list" (full-width rows)
│ │ │ ├─ onClick (optional) — r => <…/> the STICKY popover's BODY (tap opens; Esc / outside / × close); the head — title, sublabel, icon, status tag + wash — is INHERITED from the card face
│ │ │ ├─ onHover (optional) — r => <…/> the transient hover peek's body (hover-capable pointers only, intent-delayed)
│ │ │ ├─ onOpen (optional) — fn(key) => Null; fires when a card's popover opens
│ │ │ ├─ onClose (optional) — fn() => Null; fires once per popover close (Esc / outside / ×)
│ │ │ ├─ onCardClick (optional) — fn(key) => Null; cards are tap targets even without popover content
│ │ │ ├─ render (optional) — r => <…/> fully custom card body beneath the structured face
│ │ │ ├─ footer (optional) — [{ label, value }] board-foot key/value stats
│ │ │ ├─ legend (optional) — boolean; renders the registry legend (swatch + label + hint)
│ │ │ ├─ slice + affordances (optional) — slice={handle} + affordances (default ["filter","search"] — brush/legend/breakdown rejected) mounts the rail + count footer; feed data via Slice.rows (remove) or Slice.partition → filtered (dim); filtering/search flow through the slice interface like Table — no bespoke search
│ │ │ └─ style (optional) — { height, maxHeight, minCardWidth, virtualization }; height/maxHeight make the board its own (virtualized) scroll region, flat AND grouped
│ │ └─ Factories:
│ │ ├─ Deck.statuses({ key: { label, color, pulse?, hint? } }) — build the status registry; color is a standard status token "success"|"warning"|"danger"|"info"|"neutral" OR any custom CSS colour (the faint face tint is derived); pulse animates the tag dot (active states); hint feeds the legend + group heads
│ │ ├─ Deck.metric(label, value, { format?, warn? }) — one metric cell: the RAW Float (or Option Float) value plus a shared format — a Chart.format.* spec or a v => String accessor (same vocabulary as chart axes); a none value renders "—"; warn paints the value in the danger tone
│ │ ├─ Deck.Readout([{ label, value, format?, unit?, warn? }]) — popover readout rail: a bordered grid of big mono values with unit suffixes (raw values + shared format, like Deck.metric)
│ │ ├─ Deck.Rows([{ label, value }]) — popover key/value detail rows (mono-uppercase keys, body-voice values)
│ │ ├─ Deck.Note(text) — popover dashed-top mono footnote
│ │ ├─ Deck.meter(label, value, max, text) — card-face meter fact (utilisation bar + right-aligned reading)
│ │ ├─ Deck.chips(label, values) — card-face chip-set fact
│ │ └─ Deck.text(label, text) — card-face dim text fact
│ ├─ <ValueTree value={anyEastValue} /> — editable tree of ANY East value, materialized from its STATIC type at authoring time (structs/arrays/dicts/options/variants → branches; primitives → typed editable leaves; sets/blobs/vectors/matrices/refs/fns → read-only summaries; non-string-keyed dicts browsable read-only)
│ │ ├─ Props:
│ │ │ ├─ value (required) — the East value to render; omit every callback for a read-only inspector
│ │ │ ├─ onUpdate (optional) — fn([T], Null) whole-value handler: receives the WHOLE value with the edit applied (the factory rebuilds it for you); sync or async
│ │ │ ├─ at (optional) — [ValueTree.at(T, probe, fn)] scoped subtree handlers; deepest matching scope wins, unmatched edits bubble to onUpdate
│ │ │ ├─ onEdit (optional) — RAW path callback (escape hatch; overrides per event): fn(path, leaf) => Null leaf edit
│ │ │ ├─ onInsert (optional) — RAW append/insert: array append paths end with an `append` step, dict adds carry the new `key`
│ │ │ ├─ onRemove (optional) — RAW remove with the element/entry path
│ │ │ ├─ onTag (optional) — RAW variant switch + option set/clear ("some"/"none")
│ │ │ └─ style (optional) — { height, maxHeight }; bounded trees virtualize rows
│ │ └─ Factories:
│ │ ├─ ValueTree.at(T, p => p.machines.entry("m1"), fn([SubT], Null)) — a typed scope: struct fields as properties, .item(i), .entry(k), .some()
│ │ ├─ ValueTree.zero(T) — the default element for inserts (delegates to East defaultValue)
│ │ └─ ValueTree.Types.{Root, Node, Path, Step, Leaf, Style} — the East types for RAW callbacks
│ ├─ <Roster people={…} shifts={…} id person={…} shift={…} /> — people × days-of-week shift grid; joins the two flat tables by person key
│ │ └─ Props:
│ │ ├─ people + shifts + id (required) — the two tables + DnD target identity
│ │ ├─ person (optional) — row mapper to { key, label, sublabel? } (omit when already Roster.Types.Person)
│ │ ├─ shift (optional) — row mapper to { key, person, day, hours|label, state } (state is a PlannerStateType — Roster.Types.State)
│ │ ├─ mode (optional) — published (default) | edit
│ │ ├─ days (optional) — day columns in order (default Mon–Sun)
│ │ ├─ personHeader / personWidth (optional) — frozen column header (default "Operator") + CSS width (default 150px)
│ │ ├─ sources + onDrag + canDrop (optional) — DnD target (add/move/remove funnel); canDrop = fn(DragEvent) => Bool IR veto (⊘ over vetoed cells); a remove-capable drag raises the shared trash sink (drop = remove/trash)
│ │ ├─ onSelect / onAccept / onAddAt (optional) — cell click / ghost-shift accept / empty-cell add (CellRef payloads); granularity contract: onAccept(CellRef) resolves ONE ghost, review.onApprove({rowIndex}) signs off the LINE (interplay host-owned)
│ │ ├─ review (optional) — row-level Decision column + foot (+ person status/approval fields)
│ │ ├─ summary (optional) — status-strip text (dirty / ghost counts)
│ │ └─ density / height / maxHeight (optional) — rhythm + uniform sizing (#320)
│ ├─ <Board areas={…} shifts={…} people={…} assignments={…} id … /> — single-day areas × shifts assignment grid; cells stack MULTIPLE person chips, faces joined to people by person key
│ │ └─ Props:
│ │ ├─ areas + shifts + people + assignments + id (required) — the four tables + DnD target identity
│ │ ├─ area / shift / person (optional) — entity row mappers to { key, label, sublabel? }
│ │ ├─ assignment (optional) — row mapper to { key, person, area, shift, state }
│ │ ├─ requirements + requirement (optional) — coverage rows + mapper to { area, shift, required } (n/required numerals + ⊕ open-slot placeholders, under/over tones)
│ │ ├─ mode (optional) — published (default) | edit
│ │ ├─ areaHeader / areaWidth (optional) — frozen column header (omit = blank; zero baked copy) + CSS width (default 150px)
│ │ ├─ maxVisible (optional) — per-cell chip cap before the +N overflow popover
│ │ ├─ sources + onDrag + canDrop (optional) — DnD target (add/move/remove); canDrop = fn(DragEvent) => Bool veto (⊘ while dragging; duplicate-person guard stays built in)
│ │ ├─ canAssign (optional) — DEPRECATED sugar fn(person, area, shift) => Bool the factory compiles into canDrop
│ │ ├─ onSelect / onAccept / onAddAt (optional) — cell click / ghost accept / open-slot click (CellRef payloads)
│ │ ├─ review (optional) — { summary?, onApproveAll, onRejectAll, onRerun? } batch commitBar foot only (per-row fields unused in v1, the factory warns); ghost onAccept unchanged
│ │ ├─ summary (optional) — status-strip text (open / proposed counts); toolbar chrome is page composition
│ │ └─ density / height / maxHeight (optional) — rhythm + uniform sizing (#320)
│ ├─ <Blend targets={…} config={{…}} /> — blend / batch assembly surface; pairs with a Library; target count picks the mode: 1 single | 2 compare (derived diff / Δ table) | 3+ portfolio
│ │ ├─ Props:
│ │ │ ├─ targets + id (required) — target rows + DnD target identity
│ │ │ ├─ target (optional) — row mapper to Blend target fields (omit when already Blend.Types.Target)
│ │ │ ├─ sources (optional) — Library ids accepted for add-drops
│ │ │ ├─ diff (optional) — compare mode: metric keys for the foot table (default all); verdict (optional) — compare verdict line
│ │ │ ├─ onDrag + canDrop (optional) — add/remove drag funnel + IR drop veto (⊘)
│ │ │ ├─ onAmountChange (optional) — allocation amount edits
│ │ │ └─ onAction (optional) — panel actions; the action foot rides the shared commitBar (Apply = approve-primary, Discard = reject-danger, Reset plain — apply/discard are the panel-scope review verbs)
│ │ └─ Factories:
│ │ ├─ Blend.allocation({ source, amount, pinned?, state? }) — one allocation line
│ │ └─ Blend.metric({ key, label, value, numeric?, model?, band? }) — one target metric
│ ├─ <Slice.Rail slice={slice} affordances={[…]} /> — shared narrowing chrome over one bound dataset; feed consumers via Slice.rows
│ │ ├─ Props:
│ │ │ ├─ slice (required) — the bound handle from Slice.bind
│ │ │ ├─ affordances (optional) — ["filter","search","range","breakdown","cohort","presets","brush","legend"]; legends are explicit-only (list "legend" or compose <Slice.Legend>)
│ │ │ ├─ persist (optional) — "local" | "session" | "url" opts the state into reload-surviving / shareable-link storage
│ │ │ └─ brush (optional) — the brush strip is rich by default (the range field's format drives the axis labels; a self-excluding count histogram shows the row distribution); brush={{ axis?, count?, buckets? }} opts down to the bare track. The applied window is a full brush selection: drag its body to slide (width preserved), an edge to resize, empty track to draw (also on the Gantt timeline header)
│ │ ├─ Nested tags: <Slice.Filter/Search/Range/Breakdown/Legend/Cohort/Presets/Summary slice={slice} /> — per-affordance chrome; <Slice.Cohort mode="toggle"|"manage" allowCreate?> (cohorts toggle on chip click; <Slice.Presets> = toggle-only preset bar); <Slice.Legend> = facet bar (click = in-set multi-select over self-excluding slice.facetGroups(); mode="visibility" = eye rail); Summary/Filter footers read "N of M"
│ │ └─ Factories:
│ │ ├─ Slice.bind([Row], key, config, initialState, data, searchMatcher?) — bind a dataset to a slice key (searchMatcher = optional Option of a per-row match fn; pass `none` for the config-driven default)
│ │ ├─ Slice.config(Row, { fields, rangeFieldId?, searchFieldIds?, breakdownFieldIds? }) — fields: { id: { label, hints?, format? } }; format reuses the shared Chart.format vocabulary (see the Formats branch)
│ │ ├─ Slice.state({…}) — the initial slice state
│ │ ├─ Slice.rows([Row], slice) — the narrowed feed (excluded rows gone)
│ │ ├─ Slice.partition([Row], slice) — the FULL set tagged [{value, matched}] (the "keep the excluded" feed — drive a de-emphasis effect from `matched`)
│ │ ├─ Slice.apply.where / .matches / .breakdown — the pure filter engine (string ops eq/neq/in/notIn/contains/matches/startsWith/endsWith/isEmpty/isNotEmpty; integer in; datetime between)
│ │ └─ slice.toggleFilter(pred) — idempotent single-predicate toggle for custom wiring; Range picker presets anchor to the DATA's date extent (clamped to now for live data) and pin concrete windows; an All chip clears the range; programmatic datetimePreset seeds stay rolling/wall-clock
│ └─ <Pagination> — page-number control
│ └─ Props:
│ ├─ page / pageSize / count (required) — current page, rows per page, total rows
│ ├─ onPageChange (required) — fn(newPage) => Null
│ ├─ siblings / boundaries (optional) — ellipsis control
│ └─ size / variant / activeBackground / activeColor + COLOR overrides (optional)
│
├─ Charts (visualize data) — layers are a config array of factory values, never child tags
│ ├─ <Chart layers={…} /> — assemble mark + annotation layers; x-scale inferred from the x accessor type (String → band, number → linear, DateTime → time)
│ │ ├─ Props:
│ │ │ ├─ layers (required) — array of Chart.Line/Column/Bar/Area/Scatter/Band/refLine/refBand/refDot/Series values
│ │ │ ├─ x / y / y2 (optional) — axis options: { label?, format? (shared spec — see the Formats branch), domain?, scale?, numTicks?, tickValues?, hideTicks?, hideLine?, tickStyle?, titleStyle?, titleGap? }
│ │ │ │ tickValues (#318): floats on a linear axis ([0,1,2,…] to line up with a Planner) or DateTime[] on a time axis (pin ticks to exact instants, rendered through the date format); Date ticks on y/y2 are a build-time error
│ │ │ │ tickStyle/titleStyle (#315): { fontSize?, fontFamily?: "sans"|"serif"|"mono", fontWeight?, color?, letterSpacing? } — restyle ticks/captions over the spec chrome
│ │ │ │ titleGap (#327): px between ticks and caption — widens that axis's OWN margin band, never the shared AlignedStack gutter, so nudging a title can't shift a stacked plot lane
│ │ │ ├─ height (optional) — px or "fill"; width (optional) — px (omit for responsive)
│ │ │ ├─ grid / legend / tooltip (optional) — background gridlines (default on) / colour-matched legend / hover tooltip
│ │ │ ├─ stackOffset (optional) — "none" | "expand" (percent stacking)
│ │ │ └─ slice + affordances (optional) — bound slice chrome (default ["breakdown","range"]; the brush sets the slice's range)
│ │ └─ Factories:
│ │ ├─ Chart.Line / Chart.Column / Chart.Area / Chart.Scatter(rows, encoding, style?) — marks; encoding: { x, y } · { x, y, by } (split) · { x, columns: { Name: r => r.field } } (wide)
│ │ ├─ Chart.Bar(rows, { x: numeric, y: category }, style?) — HORIZONTAL bars (band y-axis, linear x; flips the whole frame; same { x, y, by } / { y, columns } splits; can't mix with vertical marks)
│ │ ├─ Chart.Band(rows, { x, low, high }, style?) — filled range (e.g. confidence band)
│ │ ├─ mark style (all optional) — { key (legend label), color, curve, width, dash, dots, fillOpacity, opacity, legend, tooltip, stack (group id — layers sharing one stack accumulate), axis: "left"|"right", order (draw order) }; Scatter adds size (uniform px radius; a per-point size accessor overrides)
│ │ ├─ Chart.refLine({ y }|{ x }, label?, dash?) — reference line
│ │ ├─ Chart.refBand({ y: [lo, hi] }|{ x: [lo, hi] }, label?) — reference band
│ │ ├─ Chart.refDot({ x, y, label? }) — reference marker
│ │ ├─ Chart.format.{ number, currency, percent, compact, date, time, datetime } — the SHARED format specs (see the Formats branch)
│ │ └─ Chart.Series(slice, { x, value, mark? }) — a slice-bound layer (x/value are slice field ids; mark: "line"|"bar"|"area"|"scatter", default line)
│ └─ <Sparkline> — inline trend beside a <Stat>
│ └─ Props:
│ ├─ data (required) — the values
│ ├─ type (optional) — line | area
│ └─ color / height / width (optional)
│
├─ Display (show information)
│ ├─ <Badge> — mono uppercase micro-label (status / taxonomy); stays a tier smaller than tags
│ │ └─ Props: variant (solid|subtle|outline) / colorPalette / size / density / borderRadius / borderWidth / borderStyle / justifyContent / alignItems + BOX bag + COLOR overrides (all optional; children required)
│ ├─ <Tag> — operator-set keyword / filter pill (body font)
│ │ └─ Props:
│ │ ├─ children (required) — the tag text
│ │ ├─ closable + onClose (optional) — × affordance + callback
│ │ └─ variant / colorPalette / size / density / borderRadius / borderWidth / borderStyle + BOX bag + COLOR overrides (optional)
│ ├─ <Avatar> / <AvatarGroup> — user avatar / overlapping cluster with "+N more"
│ │ └─ Props:
│ │ ├─ Avatar: src (image URL) / name (initials fallback) / variant / colorPalette / size / density / borderRadius + BOX bag + COLOR overrides (all optional)
│ │ └─ AvatarGroup: children (required) — the avatars; max (optional) — overflow threshold (+N after); size / density / borderColor (optional)
│ ├─ <Image> — raster/vector image or logo
│ │ ├─ Props:
│ │ │ ├─ source (required) — Image.url(u) | Image.dataUri(s) | Image.blob(bytes, "png"|"svg"|…)
│ │ │ ├─ fit (optional) — object-fit: contain | cover | fill | none | scaleDown
│ │ │ ├─ aspectRatio / alt / borderRadius / background (optional) — framing + accessibility
│ │ │ └─ …plus BOX bag
│ │ └─ Factories:
│ │ ├─ Image.url(u) — hosted image
│ │ ├─ Image.dataUri(s) — self-contained base64
│ │ └─ Image.blob(bytes, format) — raw BlobType bytes → revocable object URL
│ ├─ <Icon> — FontAwesome icon
│ │ └─ Props:
│ │ ├─ prefix / name (required) — FA icon identity
│ │ ├─ variant (optional) — solid | regular | light | thin | brands
│ │ ├─ size (optional) — xs…2xl; label (optional) — accessible name
│ │ └─ colorPalette / borderRadius + BOX bag + COLOR overrides (optional)
│ ├─ <Kbd> — keyboard-shortcut chip (⌘ K)
│ │ └─ Props: children (required); variant / size / density / colorPalette / shadowColor + COLOR overrides (optional)
│ ├─ <Stat> — metric tile with label / value / change indicator
│ │ └─ Props:
│ │ ├─ label (required) — metric caption; value (required) — the raw value (Float / Integer / String)
│ │ ├─ format (optional) — a shared Chart.format.* spec over a numeric value (see the Formats branch)
│ │ ├─ helpText (optional) — caption beneath the value
│ │ ├─ baseline / delta / info (optional) — secondary line / change pill / ⓘ ToggleTip trigger (UIComponents)
│ │ ├─ indicator (optional) — "up"|"down"|"flat" or { direction, sentiment?: positive|negative|neutral, icon? }
│ │ ├─ density / size (optional) — rhythm + size preset
│ │ └─ valueColor / labelColor / helpTextColor / indicatorColor (optional)
│ ├─ <MetricChip> — compact mono delta chip
│ │ └─ Props:
│ │ ├─ children (required) — the value text; tone (required) — positive | negative | neutral | info (drives the palette)
│ │ ├─ unit (optional) — suffix ("%", "ms"); icon (optional) — leading icon
│ │ ├─ emphasis (optional) — subtle | solid | outline
│ │ └─ density / size / borderRadius / iconColor + COLOR overrides (optional)
│ ├─ <Meter> — horizontal capacity bar with sentiment colour (never a bare bar — value text on by default)
│ │ └─ Props:
│ │ ├─ value (required) — current value; max (optional) — full-bar value (default 100)
│ │ ├─ tone (optional) — a status token driving the fill (see the Statuses branch)
│ │ ├─ label (optional) — UIComponent beside the bar; showValue (optional) — trailing mono percent (default true)
│ │ └─ density / thickness / borderRadius / fillColor / trackColor / labelColor (optional)
│ ├─ <SegmentedMeter> — multi-segment meter (e.g. decomposed confidence)
│ │ └─ Props:
│ │ ├─ segments (required) — [{ value, tone?, color?, label? }]
│ │ ├─ caption (optional) — UIComponent beside the bar; max (optional) — total reference (default sum)
│ │ ├─ labels (optional) — inside | outside | none
│ │ └─ density / thickness / borderRadius / trackColor / captionColor / labelColor (optional)
│ ├─ <BarStrip> — ranked horizontal-bar list (axis-free; fits inside a <Stat>)
│ │ └─ Props:
│ │ ├─ items (required) — [{ label (UIComponent), value, tone?, color?, trailing? }]
│ │ ├─ showValues (optional) — trailing value text (default true)
│ │ ├─ sort / maxItems (optional) — factory-time sort + row cap
│ │ └─ density / orientation / thickness / borderRadius / trackColor / labelColor / valueColor (optional)
│ ├─ <EditableChip> — chip whose text becomes inline input on click
│ │ └─ Props:
│ │ ├─ children (required) — chip text
│ │ ├─ onClick (optional) — activation callback; trigger (optional) — trailing icon (default chevron)
│ │ └─ disabled / density / size / borderRadius / triggerIconColor + COLOR overrides (optional)
│ ├─ <ChipRail> — horizontal rail of mixed chip-shaped children (<Tag>/<Badge>/<MetricChip>/<Avatar>/…); provides its density to the children
│ │ └─ Props:
│ │ ├─ children (required) — the chips
│ │ ├─ separator (optional) — dot | line; labels (optional) — per-chip labels
│ │ ├─ overflow (optional) — behaviour when the rail can't fit every chip (⋯ menu)
│ │ └─ density / separatorColor / overflowTriggerColor / background (optional)
│ └─ <Trace> — read-only inline heatmap (tracks × steps) with a now-line; sits flush beside a <ChipRail> at the same density in table cells
│ └─ Props:
│ ├─ tracks (required) — [{ name, values }] (heat normalises per track's own min/max)
│ ├─ now (optional) — step index of the now-line; [0, now) measured, [now, end) predicted; omit for measured-only
│ ├─ scale (optional) — heat colour encoding; future (optional) — how predicted steps are distinguished
│ ├─ axis (optional) — per-step labels (ruler at comfortable density, tooltips always)
│ ├─ density / brandColor / nowLineColor / labelWidth (optional) — rhythm + colour + name-gutter width
│ └─ plotGutter (optional) — shared gutter (#147): pins the step lane so a Trace stacked under a Chart lines up (left supersedes labelWidth; usually supplied by <AlignedStack>)
│
├─ Feedback (status & async signals)
│ ├─ <Banner> — page-spanning notice
│ │ └─ Props:
│ │ ├─ status (required) — info | warning | success | error | neutral | change | guard | stale
│ │ ├─ title (required) — string or UIComponent
│ │ ├─ description / actions (optional) — body + trailing actions
│ │ ├─ icon / showIcon (optional) — explicit icon override / hide the paired icon
│ │ ├─ dismissible + onDismiss (optional) — close affordance
│ │ └─ variant / size / iconColor / accentColor + COLOR overrides (optional)
│ ├─ <Status> — dot + uppercase label (no fill)
│ │ └─ Props:
│ │ ├─ label (required) — string or UIComponent
│ │ ├─ value (optional) — success | warning | danger | info | neutral (default neutral; see the Statuses branch)
│ │ ├─ pulsing (optional) — animate the dot
│ │ ├─ icon / showIcon (optional) — icon override / hide
│ │ └─ size / dotColor + COLOR overrides (optional)
│ ├─ <Progress> — linear progress bar
│ │ └─ Props:
│ │ ├─ value (required) — current value
│ │ ├─ min / max (optional) — range (defaults 0–100)
│ │ ├─ indeterminate (optional) — unknown-% mode
│ │ ├─ label / valueText / showValue (optional) — captions
│ │ ├─ estimatedDuration / startedAt (optional) — drive an ETA display
│ │ ├─ tone (optional) — brand | pos | neg (bsys-restricted fill tone)
│ │ └─ variant / size / striped / animated / trackColor / fillColor / labelColor (optional)
│ ├─ <Skeleton> — shimmer placeholder
│ │ └─ Props:
│ │ ├─ shape (required) — text | rect | circle
│ │ ├─ lines (optional) — line count (text shape); count (optional) — repeat in a VStack
│ │ └─ width / height / fontSize / background / shimmerColor (optional)
│ └─ <EmptyState> — zero-data state
│ └─ Props:
│ ├─ title (required) — string or UIComponent
│ ├─ glyph (optional) — spec-preferred mono glyph ("· · ·"; takes precedence over icon)
│ ├─ icon (optional) — FA icon escape hatch
│ ├─ description / actions (optional) — body + action row
│ └─ size / iconColor + COLOR overrides (optional)
│
├─ Disclosure (reveal / switch content) — item metadata is config, not child tags
│ ├─ <Tabs items={…}> — content-panel switcher
│ │ ├─ Props:
│ │ │ ├─ items (required) — array of Tabs.Item(…)
│ │ │ ├─ value / defaultValue (optional) — controlled / initial selected tab
│ │ │ ├─ onValueChange (optional) — fn(String) => Null
│ │ │ ├─ variant (optional) — line | plain; size — sm | md | lg
│ │ │ ├─ orientation / activationMode / fitted / justify (optional) — layout + keyboard behaviour
│ │ │ ├─ lazyMount / unmountOnExit (optional) — panel mount policy
│ │ │ └─ colorPalette / listBackground / indicatorColor / activeTriggerColor / inactiveTriggerColor / contentBackground (optional)
│ │ └─ Factories:
│ │ └─ Tabs.Item(value, title, body, { disabled? }) — one tab
│ ├─ <Accordion items={…}> — single/multi-open sections
│ │ ├─ Props:
│ │ │ ├─ items (required) — array of Accordion.Item(…)
│ │ │ ├─ multiple / collapsible (optional) — allow multiple open / allow all closed
│ │ │ ├─ value / defaultValue / onValueChange (optional) — controlled expansion
│ │ │ └─ variant / size / triggerBackground / triggerHoverBackground / contentBackground + COLOR overrides (optional)
│ │ └─ Factories:
│ │ └─ Accordion.Item(value, trigger, children, { meta?, disabled? }) — one section (meta = right-aligned header caption)
│ ├─ <Carousel> — paginated slide carousel
│ │ └─ Props:
│ │ ├─ children (required) — the slides
│ │ ├─ index / defaultIndex / onIndexChange (optional) — controlled slide position
│ │ ├─ slidesPerView / slidesPerMove / spacing (optional) — layout
│ │ ├─ loop / autoplay / allowMouseDrag (optional) — behaviour
│ │ ├─ showIndicators / showControls (optional) — dots + prev/next chrome
│ │ └─ orientation / padding / indicatorColor / activeIndicatorColor / controlColor / controlBackground (optional)
│ ├─ <Collapsible> — single show/hide section
│ │ └─ Props:
│ │ ├─ trigger (required) — always-visible header (string or UIComponent); children (required) — the body
│ │ ├─ defaultOpen / onOpenChange (optional) — initial state + toggle callback
│ │ └─ triggerColor / contentColor + COLOR overrides (optional)
│ ├─ <SegmentGroup items={…}> — compact single-select mode switcher (no panels)
│ │ ├─ Props:
│ │ │ ├─ value (required) — selected segment; items (required) — array of SegmentGroup.Item(…)
│ │ │ ├─ onChange (optional) — fn(String) => Null
│ │ │ └─ size / colorPalette / orientation / activeBackground / activeColor / inactiveColor + COLOR overrides (optional)
│ │ └─ Factories:
│ │ └─ SegmentGroup.Item(value, label, { disabled? }) — one segment
│ ├─ <OptionList> — keyboard-navigable single-select list (rows + description)