Three layers, and only the first two are ours.
| Layer | Ships in the package | You own |
|---|---|---|
| Primitives | Button, Input, Badge, Card | palette, radius, density |
| Conversation | Message, Composer, ConversationRail | copy, model list, policy |
| Layout | AppShell, SplitView, WindowDeck | persistence |
Every surface is a compound component with data-slot attributes, so
restyling is a class away and nothing is locked behind a variant prop.
Five components, no provider, no context:
import { Message, MessageBubble, MessageContent } from "@nessa-ui/react"; export function Transcript({ turns }: { turns: Turn[] }) { return ( <div role="log" className="flex flex-col gap-3"> {turns.map((turn, i) => ( <Message key={i} from={turn.role}> <MessageContent> <MessageBubble variant={turn.role === "user" ? "primary" : "plain"}> {turn.text} </MessageBubble> </MessageContent> </Message> ))} </div> );}Alignment, bubble shape, hover actions and the streaming fade come from the slots. Swap variant and it is still your palette.
No. New text mounts inside per-word spans that fade from transparent; already-painted words keep their DOM nodes, so the work per token is proportional to the tail, not the transcript:
Code, math and diagram nodes are excluded from the fade wrapper, so a half-parsed fence never animates character by character.
MathBlock holds the last valid render while intermediate TeX is unparseable, which is why formulas grow instead of flashing an error.
Ranking is plain cosine similarity over normalised embeddings so a citation is just a hover target inside the sentence it supports.
- packages/react/src/components/conversation-rail.tsx148 additions, 0 deletions+148-0
- packages/react/src/components/workflow-canvas/node.tsx62 additions, 41 deletions+62-41
- packages/react/src/components/event-calendar.tsx34 additions, 58 deletions+34-58
ConversationRail is the headline: turn markers beside the transcript that widen toward the pointer and preview on hover. It is in this pane, to the left, once the pane is wide enough for it.
Your product. We hand you interaction and state:
- keyboard and focus behaviour, including roving focus in lists and canvases
- drag, drop and resize with commit semantics you can veto
- streaming, queueing and cancellation in the composer
- layout persistence for panes, docks and splits
What we deliberately do not hand you is opinion. There is no brand colour,
no mandated font, no copy. Tokens sit on --nessa-* variables, and every
themed surface in this harness, including the eleven presets in Settings,
is a variable swap rather than a fork.