WallyAssistant
The closed Wally panel: pass a ChatSnapshot (messages, status, suggestions, permissions, context) and callbacks, and it renders the whole Figma chat — header, stick-to-bottom log, greeting, chain of thought, docked approval and input. It holds no agent state: the app folds its stream into a new snapshot on every delta. Send is blocked while the last message has a pending approval (typing still works), and a resolved card stays docked until the next message. Every rule lives in exported pieces — ChatViewProvider, deriveChatState, ChatMessageView, ChatPartView — so a custom layout reuses them (Custom Layout). Seams like renderPart, renderStepContent and renderMessageActions hand back the default rendering. Stories are driven by useMockWally() from WallyAssistant/mocks and keep feedback and permissions in app state. Messages render with the default AssistantMessageMarkdown, and fenced code highlights with the lazy Prism adapter WallyAssistant mounts unless the app provides a CodeSnippetAdapterProvider.
Usage
Examples
Conversation
Two finished turns: folded chain of thought, answers with Copy, thumbs and timestamps, follow-up pills and the permissions button.
Streaming simulation
The whole Wally script from useMockWally(): send a message or click a pill to watch the loader,
a live reasoning step, three tool steps with the Working row between them, the streamed answer,
then the fold under "Worked for 12s". Stop cancels the run. speed scales every delay (0 = instant).
Approval
A pending approval docked above the input: Send is blocked (typing still works). Allow or Reject goes through onApprovalRespond, and the next snapshot resolves the card in place.
Error state
A run that failed before any output: AssistantMessageError with Retry, wired to onRetry.
Overrides
Every seam at once: renderPart draws a custom attack summary, renderStepContent puts a table
under search_attacks, renderMessageActions adds a tagged Copy and an Ellipsis menu, a thread
Select in headerTitle, an attach button in inputTools, and German labels.
Custom layout
A full-page chat with a centered 768px column, built from ChatViewProvider, deriveChatState, Chat, Conversation*, ChatMessageView, docked ChatPartView and its own ChatInput — proof the open parts reuse every rule.
In drawer
Hosted in a 480px Drawer opened by ChatTrigger. Esc in the textarea while a reply streams stops it without closing the drawer — ChatInput handles that itself, no host recipe needed.
Analytics
Analytics attributes land on real nodes: the root <section>, header-action Buttons and a tagged Copy from renderMessageActions (like, dislike and timestamp stay default). Closed targets (pills, Send, approval options) report through their callbacks — see ANALYTICS_GAPS.md.