@vinyasa/ai-native
v2.0.3
Published
24 UI patterns for building AI products: chat threads, streaming responses, tool calls, agent timelines, diff review, workflow graphs, and the surrounding chrome (composer, citations, model picker, token usage, conversation list).
Downloads
552
Readme
@vinyasa/ai-native
24 UI patterns for building AI products: chat threads, streaming responses, tool calls, agent timelines, diff review, workflow graphs, and the surrounding chrome (composer, citations, model picker, token usage, conversation list).
Installation
pnpm add @vinyasa/ai-native @vinyasa/button @vinyasa/feedback @vinyasa/icons @vinyasa/layout @vinyasa/overlay @vinyasa/tokens @vinyasa/typography @tiptap/core @tiptap/extension-document @tiptap/extension-hard-break @tiptap/extension-paragraph @tiptap/extension-text @tiptap/extensions @tiptap/pm @tiptap/react @tiptap/suggestion @xyflow/react cmdk react react-domThis package composes existing @vinyasa/* primitives rather than reimplementing them — Button for action slots, Dialog/Popover/DropdownMenu from @vinyasa/overlay, ProgressBar from @vinyasa/feedback, MarkdownRenderer/Text/Code/Link from @vinyasa/typography, Box/Flex from @vinyasa/layout, and icons from @vinyasa/icons — all seven are peer dependencies, and rendering requires a VinyasaProvider (from @vinyasa/tokens) above them in the tree, same as everywhere else.
The remaining peers exist for one component each:
@tiptap/*(core, extension-document/-hard-break/-paragraph/-text, extensions, pm, react, suggestion) —PromptEditorbuilds a deliberately minimal schema (text and hard breaks only, no headings/marks/lists) rather than depending on@vinyasa/form's full-StarterKitRichTextEditor, which doesn't fit a single-line chat composer.@tiptap/suggestionpowers its "/" slash-command popup.@xyflow/react—WorkflowBuilderwraps itsReactFlowdirectly, with custom themed node/edge components.cmdk— backs the slash-command popup insidePromptEditor, the same list primitive@vinyasa/form's Combobox and@vinyasa/overlay's CommandPalette already use.
No separate stylesheet import is needed — each component's CSS is bundled into its own JS entry.
This package has no root export — every component is subpath-only (import { ChatThread } from '@vinyasa/ai-native/chat-thread', never from '@vinyasa/ai-native'). A root barrel re-exporting all 24 components would let a bundler tree-shake the unused JS down to just the ones you import, but the CSS side-effect imports the others carry are not eligible for the same tree-shaking (confirmed empirically with both esbuild and Rollup on this monorepo's other packages). Removing the root entry entirely makes that the only possible outcome, not something that depends on your bundler being clever enough to shake it out.
Composition
Unlike @vinyasa/overlay/@vinyasa/form, none of these 24 components expose a compound .Root/.Trigger/... namespace — each is a single flat-prop component. Several are meant to be composed into another component's slot rather than used standalone: ThinkingIndicator, StreamingResponse, and ToolCallCard typically fill a ChatThread message's content; MessageActions and MessageVersionPager typically fill its actions. AssistantPanel is the batteries-included composite that bundles ChatThread + PromptEditor (+ StreamingResponse) into one ready-to-drop-in widget — reach for ChatThread/PromptEditor directly only if you need a layout AssistantPanel doesn't offer.
Usage
ChatThread
The core message-list layout for a chat UI — user/assistant bubbles, with assistant string content rendered through @vinyasa/typography's MarkdownRenderer.
import { ChatThread } from '@vinyasa/ai-native/chat-thread';
<ChatThread
messages={[
{ id: '1', role: 'user', content: 'Summarize this PR.' },
{
id: '2',
role: 'assistant',
content: '**Summary:** adds retry logic.',
actions: <MessageActions copyText="..." />,
},
]}
/>;| Prop | Type | Default | Description |
| ---------------- | --------------------- | ------------- | ----------------------------------------------------------------------------------------- |
| messages | ChatThreadMessage[] | — | Required. { id; role: 'user' \| 'assistant'; content: ReactNode; actions?: ReactNode }. |
| userLabel | string | 'You' | |
| assistantLabel | string | 'Assistant' | |
content accepts any ReactNode — pass a markdown string, or compose in StreamingResponse/ThinkingIndicator/ToolCallCard for an in-progress or tool-augmented message.
PromptEditor
The chat composer — a Tiptap-backed field with Enter-to-submit, Shift+Enter for a newline, {{variable}} highlighting, a "/" slash-command menu, attachments, and an auto inline-vs-stacked layout.
import { PromptEditor } from '@vinyasa/ai-native/prompt-editor';
import Button from '@vinyasa/button/button';
<PromptEditor
value={draft}
onValueChange={setDraft}
onSubmit={handleSend}
placeholder="Ask anything..."
attachments={attachments}
onRemoveAttachment={removeAttachment}
commands={[{ id: 'explain', label: '/explain', description: 'Explain the selected code' }]}
trailing={
<Button size="sm" onClick={() => handleSend(draft)}>
Send
</Button>
}
/>;| Prop | Type | Default | Description |
| ------------------------- | --------------------------------- | -------- | ---------------------------------------------------------------------------------------------- |
| value | string | — | Required. |
| onValueChange | (value: string) => void | — | Required. |
| onSubmit | (value: string) => void | — | Fires on Enter without Shift, when non-blank. |
| disabled/isSubmitting | boolean | false | |
| leading/trailing | ReactNode | — | Docked bottom-left/right once the layout is stacked. |
| layout | 'auto' \| 'inline' \| 'stacked' | 'auto' | |
| attachments | PromptEditorAttachment[] | — | { id; name; size?; previewUrl? }. Pair with onRemoveAttachment. |
| radius | 'sm' \| 'md' \| 'full' | 'sm' | |
| highlightVariables | boolean | true | Pair with variablePattern (default /\{\{[^{}]+\}\}/g). |
| commands | PromptEditorCommand[] | — | Enables the "/" menu. { id; label; description?; icon?; keywords?; insertText?; onSelect? }. |
| maxLength | number | — | Enables a live character counter. |
StreamingResponse
Renders an assistant message's text with a gradual reveal animation as new chunks of text arrive — a token-by-token feel, backed by markdown rendering.
import { StreamingResponse } from '@vinyasa/ai-native/streaming-response';
<StreamingResponse text={partialAnswer} isStreaming={!isDone} />;| Prop | Type | Default | Description |
| --------------------- | --------- | ------- | -------------------------------------------------------- |
| text | string | — | Required. Full text so far — update it as chunks arrive. |
| isStreaming | boolean | true | |
| charactersPerSecond | number | 40 | Reveal speed. |
ThinkingIndicator
A pre-stream "assistant is composing" affordance (label + animated dots) — compose it as a ChatThread message's content before real tokens arrive, then swap it for StreamingResponse.
import { ChatThread } from '@vinyasa/ai-native/chat-thread';
import { ThinkingIndicator } from '@vinyasa/ai-native/thinking-indicator';
<ChatThread messages={[{ id: 't', role: 'assistant', content: <ThinkingIndicator /> }]} />;| Prop | Type | Default | Description |
| ------- | ----------- | ------------ | -------------------------------------------------------------------- |
| label | ReactNode | 'Thinking' | Also the role="status" accessible name. Pass null for dots only. |
ReasoningInspector
A collapsed-by-default disclosure for an assistant's chain-of-thought, matching the "thinking block" UX pattern.
import { ReasoningInspector } from '@vinyasa/ai-native/reasoning-inspector';
<ReasoningInspector defaultOpen={false}>
{'The user wants X, so I should first check Y...'}
</ReasoningInspector>;| Prop | Type | Default | Description |
| ------------- | ----------- | ------------- | ---------------------------------------------------------------------------- |
| children | ReactNode | — | Required. Markdown string, or StreamingResponse for in-progress reasoning. |
| open | boolean | uncontrolled | |
| defaultOpen | boolean | false | |
| label | string | 'Reasoning' | |
ToolCallCard
An inline, collapsible card for a single tool invocation shown inside a chat message — distinct from AgentTimeline's standalone multi-event log and ExecutionConsole's standalone terminal.
import { ToolCallCard } from '@vinyasa/ai-native/tool-call-card';
<ToolCallCard
toolName="search_docs"
status="success"
args={'{"query":"useEffect"}'}
result={'3 results found'}
/>;| Prop | Type | Default | Description |
| ---------- | ------------------------------------------------ | ------- | ------------------------------------------------------------------ |
| toolName | string | — | Required. |
| status | 'pending' \| 'running' \| 'success' \| 'error' | — | Required. |
| args | ReactNode | — | String renders as a code block via @vinyasa/typography's Code. |
| result | ReactNode | — | Same rendering as args. |
The trigger is disabled (non-expandable) when neither args nor result is given.
MessageActions
A row of small per-message actions — copy, thumbs up/down, regenerate, edit — that only renders the buttons whose handler or data is actually supplied.
import { MessageActions } from '@vinyasa/ai-native/message-actions';
<MessageActions
copyText={message.content}
feedback={feedback}
onFeedback={setFeedback}
onRegenerate={regenerate}
/>;| Prop | Type | Default | Description |
| -------------- | ------------------------ | ------- | ------------------------------------------------------------------------------------- |
| copyText | string | — | Renders the copy button; self-manages clipboard write + a 1.5s "Copied" confirmation. |
| feedback | 'up' \| 'down' \| null | — | Pair with onFeedback — both are required to show the thumbs buttons. |
| onRegenerate | () => void | — | |
| onEdit | () => void | — | |
MessageVersionPager
A "◀ 2/3 ▶" pager for stepping through an edited or regenerated message's versions — the caller owns the version array and which one is current.
import { MessageVersionPager } from '@vinyasa/ai-native/message-version-pager';
<MessageVersionPager
index={version}
count={versions.length}
onPrevious={() => setVersion((v) => v - 1)}
onNext={() => setVersion((v) => v + 1)}
/>;| Prop | Type | Default | Description |
| --------------------- | ------------ | ------- | ------------------ |
| index | number | — | Required. 0-based. |
| count | number | — | Required. |
| onPrevious/onNext | () => void | — | Required. |
AssistantEmptyState
A "How can I help?" empty state for the assistant panel, with optional example prompts — pass it as <AssistantPanel emptyMessage={<AssistantEmptyState ... />}>.
import { AssistantEmptyState } from '@vinyasa/ai-native/assistant-empty-state';
<AssistantEmptyState
description="Ask about your codebase, or try one of these:"
examples={[{ key: 'explain', label: 'Explain this file', onClick: explainFile }]}
/>;| Prop | Type | Default | Description |
| ------------- | -------------------- | ------------------- | --------------------------------------------------------------- |
| title | ReactNode | 'How can I help?' | |
| description | ReactNode | — | |
| examples | ActionSuggestion[] | — | Same shape as ActionSuggestions, rendered via that component. |
ActionSuggestions
A wrapping row of AI-suggested quick-follow-up actions ("Run tests", "View diff") shown after an assistant response, built on @vinyasa/button's outline variant.
import { ActionSuggestions } from '@vinyasa/ai-native/action-suggestions';
<ActionSuggestions
actions={[
{ key: 'run-tests', label: 'Run tests', onClick: runTests },
{ key: 'view-diff', label: 'View diff', onClick: viewDiff },
]}
/>;| Prop | Type | Default | Description |
| --------- | -------------------- | ------- | ------------------------------------------------------- |
| actions | ActionSuggestion[] | — | Required. { key; label; icon?; disabled?; onClick? }. |
| size | ButtonSize | 'sm' | |
PromptHistory
A simple click-to-reuse list of past prompts with relative timestamps ("2 hours ago").
import { PromptHistory } from '@vinyasa/ai-native/prompt-history';
<PromptHistory entries={history} onSelect={(entry) => setDraft(entry.text)} />;| Prop | Type | Default | Description |
| -------------- | ---------------------- | ------------------- | ------------------------------------ |
| entries | PromptHistoryEntry[] | — | Required. { id; text; timestamp }. |
| onSelect | (entry) => void | — | Required. |
| emptyMessage | ReactNode | 'No prompts yet.' | |
ConversationList
A sidebar list of past conversations (not messages), grouped into day buckets (Pinned, Today, Yesterday, Previous 7 Days, Older), with a per-row rename/delete/pin menu built on @vinyasa/overlay's DropdownMenu.
import { ConversationList } from '@vinyasa/ai-native/conversation-list';
<ConversationList
conversations={conversations}
activeId={activeId}
onSelect={(c) => setActiveId(c.id)}
onRename={renameConversation}
onDelete={deleteConversation}
onTogglePin={togglePin}
/>;| Prop | Type | Default | Description |
| ----------------------------------- | ------------------------- | ------------------------- | ---------------------------------------------- |
| conversations | ConversationListEntry[] | — | Required. { id; title; timestamp; pinned? }. |
| onSelect | (entry) => void | — | Required. |
| activeId | string | — | |
| onRename/onDelete/onTogglePin | (id: string) => void | — | Each adds its menu action only when given. |
| emptyMessage | ReactNode | 'No conversations yet.' | |
ModelPicker
A pill trigger + dropdown radio list for picking exactly one model, built on @vinyasa/overlay's DropdownMenu.RadioGroup/.RadioItem.
import { ModelPicker } from '@vinyasa/ai-native/model-picker';
<ModelPicker
options={[
{ id: 'sonnet', label: 'Claude Sonnet', description: 'Balanced' },
{ id: 'opus', label: 'Claude Opus', description: 'Most capable' },
]}
value={model}
onValueChange={setModel}
/>;| Prop | Type | Default | Description |
| --------------- | ---------------------- | ------------------ | ---------------------------------------- |
| options | ModelPickerOption[] | — | Required. { id; label; description? }. |
| value | string | — | Required. |
| onValueChange | (id: string) => void | — | Required. |
| placeholder | string | 'Select a model' | Shown when value matches nothing. |
TokenUsageMeter
A context-window usage bar, built on @vinyasa/feedback's ProgressBar, escalating color as usage approaches or exceeds the max (warning at ≥90%, error at ≥100%).
import { TokenUsageMeter } from '@vinyasa/ai-native/token-usage-meter';
<TokenUsageMeter used={24500} max={32000} />;| Prop | Type | Default | Description |
| ------------- | --------------------------------------- | ------------------------- | ----------- |
| used/max | number | — | Required. |
| formatLabel | (used: number, max: number) => string | e.g. "12.4k/32k tokens" | |
AttachmentPreview
A full-size look at one attachment in a Dialog (from @vinyasa/overlay) — an image preview when previewUrl is given, otherwise a generic file-icon fallback. Standalone/caller-triggered, with no trigger prop — same as Dialog itself.
import { AttachmentPreview } from '@vinyasa/ai-native/attachment-preview';
<AttachmentPreview
attachment={selectedAttachment}
open={previewOpen}
onOpenChange={setPreviewOpen}
/>;| Prop | Type | Default | Description |
| ------------ | ------------------------ | ------------ | ------------------------------------------------------- |
| attachment | PromptEditorAttachment | — | Required. Reuses PromptEditor's own attachment shape. |
| open | boolean | uncontrolled | |
CitationPopover
A numbered (or custom) citation marker that opens a Popover (from @vinyasa/overlay) showing the cited source's title, URL, and snippet.
import { CitationPopover } from '@vinyasa/ai-native/citation-popover';
<CitationPopover
index={1}
title="React docs — useEffect"
url="https://react.dev/reference/react/useEffect"
snippet="..."
/>;| Prop | Type | Default | Description |
| --------------- | -------------- | ------- | --------------------------------------------------- |
| index | number | — | Renders the default [n] marker. |
| trigger | ReactElement | — | Custom marker; wins over index if both are given. |
| title | ReactNode | — | Required. |
| url/snippet | string | — | |
CitationsPanel
The "show every source at once" counterpart to CitationPopover's one-at-a-time marker — a plain content list with no panel chrome of its own, meant to compose into ContextDrawer (see below).
import { CitationsPanel } from '@vinyasa/ai-native/citations-panel';
<CitationsPanel sources={sources} />;| Prop | Type | Default | Description |
| -------------- | ------------------------ | --------------------------------- | -------------------------------------------------- |
| sources | CitationsPanelSource[] | — | Required. { id; index?; title; url?; snippet? }. |
| emptyMessage | ReactNode | 'No sources for this response.' | |
ContextDrawer
A fixed-width side panel for showing retrieved context or documents fed to the model. Renders nothing when closed.
import { CitationsPanel } from '@vinyasa/ai-native/citations-panel';
import { ContextDrawer } from '@vinyasa/ai-native/context-drawer';
<ContextDrawer title="Sources" open={open} onOpenChange={setOpen}>
<CitationsPanel sources={sources} />
</ContextDrawer>;| Prop | Type | Default | Description |
| ------------------ | ----------- | ----------------- | ----------- |
| children | ReactNode | — | Required. |
| title | ReactNode | — | |
| open | boolean | uncontrolled | |
| width | string | '20rem' | |
| closeButtonLabel | string | 'Close context' | |
SemanticHighlight
Inline highlighting of AI-annotated spans (entities, citations, edits) within plain text, each span carrying its own semantic category — distinct from @vinyasa/typography's plain Highlight/Mark.
import { SemanticHighlight } from '@vinyasa/ai-native/semantic-highlight';
<SemanticHighlight
text="Contact Jamie Reyes about the Q3 report."
ranges={[
{ start: 8, end: 19, category: 'info', label: 'Entity: person' },
{ start: 27, end: 36, category: 'success', label: '92% confidence' },
]}
/>;| Prop | Type | Default | Description |
| -------- | -------------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| text | string | — | Required. |
| ranges | SemanticHighlightRange[] | — | Required. { start; end; category?: 'neutral' \| 'info' \| 'success' \| 'warning' \| 'error'; label? }. Offsets into text, not substring search. |
buildSemanticHighlightSegments is also exported standalone as a pure helper for computing the rendered segments yourself.
AgentTimeline
A vertical, append-only log (role="log") of heterogeneous agent events — tool calls, file edits, thoughts — each with a status marker.
import { AgentTimeline } from '@vinyasa/ai-native/agent-timeline';
<AgentTimeline
events={[
{
id: '1',
type: 'tool_call',
label: 'Reading src/index.ts',
timestamp: new Date(),
status: 'success',
},
{
id: '2',
type: 'file_edit',
label: 'Editing README.md',
timestamp: new Date(),
status: 'running',
},
]}
/>;| Prop | Type | Default | Description |
| -------------- | ---------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| events | AgentTimelineEvent[] | — | Required. { id; type: string; label: ReactNode; timestamp: Date; status: 'pending' \| 'running' \| 'success' \| 'error' }. |
| emptyMessage | ReactNode | 'No activity yet.' | |
ExecutionConsole
A monospace, terminal-style output panel that auto-scrolls to the newest line — for showing command or tool output.
import { ExecutionConsole } from '@vinyasa/ai-native/execution-console';
<ExecutionConsole
lines={[
{ id: '1', text: '$ pnpm test', tone: 'muted' },
{ id: '2', text: '✓ 42 passed', tone: 'success' },
]}
/>;| Prop | Type | Default | Description |
| -------------- | ------------------------ | ------------------ | ------------------------------------------------------------------------------------------- |
| lines | ExecutionConsoleLine[] | — | Required. { id; text; tone?: 'default' \| 'muted' \| 'success' \| 'warning' \| 'error' }. |
| emptyMessage | ReactNode | 'No output yet.' | |
| height | string | '16rem' | Max height before scroll. |
CommandRunner
Composes ExecutionConsole with a run/stop/retry control row and a status dot, for a single runnable command's output. Owns no execution logic itself — the caller drives status and lines.
import { CommandRunner } from '@vinyasa/ai-native/command-runner';
<CommandRunner title="pnpm test" status={status} lines={lines} onRun={runTests} onStop={cancel} />;| Prop | Type | Default | Description |
| ----------------------------------- | --------------------------------------------- | -------------------------- | ---------------------------------------- |
| lines | ExecutionConsoleLine[] | — | Required. |
| status | 'idle' \| 'running' \| 'success' \| 'error' | — | Required. |
| onRun | () => void | — | Required. |
| onStop | () => void | — | Only used while status is 'running'. |
| title | ReactNode | — | |
| runLabel/retryLabel/stopLabel | string | 'Run'/'Retry'/'Stop' | |
AIDiffViewer
Renders a caller-supplied, pre-computed diff (no diffing engine, no syntax highlighting) with per-hunk accept/reject controls.
import { AIDiffViewer } from '@vinyasa/ai-native/ai-diff-viewer';
<AIDiffViewer
hunks={[
{
id: 'h1',
header: 'src/app.ts',
lines: [
{ type: 'remove', text: 'old()' },
{ type: 'add', text: 'new()' },
],
},
]}
onAcceptHunk={(h) => accept(h.id)}
onRejectHunk={(h) => reject(h.id)}
/>;| Prop | Type | Default | Description |
| --------------- | --------------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------- |
| hunks | DiffHunk[] | — | Required. { id; header?: ReactNode; lines: DiffLine[] }, DiffLine is { type: 'add' \| 'remove' \| 'context'; text }. |
| onAcceptHunk | (hunk: DiffHunk) => void | — | Omitting either this or onRejectHunk hides that control. |
| onRejectHunk | (hunk: DiffHunk) => void | — | |
| hunkDecisions | Partial<Record<string, 'accepted' \| 'rejected'>> | — | Caller-tracked decision state — a decided hunk shows its settled state instead of controls. |
WorkflowBuilder
A themed wrapper around @xyflow/react's ReactFlow for visualizing or editing an agent's workflow graph, with custom node/edge components and a built-in status dot per node. ReactFlowProvider and its base stylesheet are wrapped/imported internally — no extra setup needed.
import { WorkflowBuilder } from '@vinyasa/ai-native/workflow-builder';
<WorkflowBuilder
nodes={[
{ id: '1', position: { x: 0, y: 0 }, data: { label: 'Fetch data', status: 'success' } },
{ id: '2', position: { x: 200, y: 0 }, data: { label: 'Summarize', status: 'running' } },
]}
edges={[{ id: 'e1-2', source: '1', target: '2', data: { active: true } }]}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
/>;| Prop | Type | Default | Description |
| ------------------------------- | ------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| nodes | WorkflowBuilderNode[] | — | Required. An @xyflow/react Node<WorkflowNodeData>; data is { label: ReactNode; status?: 'idle' \| 'running' \| 'success' \| 'error' }. |
| edges | WorkflowBuilderEdge[] | — | Required. Edge<WorkflowEdgeData>; data is { active?: boolean } to highlight the current path. |
| onNodesChange/onEdgesChange | ReactFlow's own change handlers | — | Mirrors ReactFlow's controlled API. |
| onConnect | ReactFlow's own connect handler | — | Omit to disable interactive connection-dragging. |
| fitView | boolean | true | |
| height | string | '24rem' | |
This component owns no graph logic itself — it's a pure controlled wrapper; your app supplies nodes/edges and reacts to the change handlers.
AssistantPanel
The full composite "drop-in assistant widget" — bundles ChatThread and PromptEditor (plus StreamingResponse for an in-flight message) into one panel with a header, scroll management, and older-message pagination. Auto-scrolls to the bottom on a new message only if the user was already near the bottom, and preserves scroll position when older messages are prepended.
import { AssistantPanel } from '@vinyasa/ai-native/assistant-panel';
<AssistantPanel
title="Assistant"
messages={messages}
onSendMessage={sendMessage}
onClose={() => setOpen(false)}
isSubmitting={isWaiting}
hasMoreOlderMessages={hasMore}
onLoadOlderMessages={loadMore}
/>;| Prop | Type | Default | Description |
| --------------------------------------------------------------------- | ------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| messages | AssistantPanelMessage[] | — | Required. { id; role: 'user' \| 'assistant'; content: string; streaming?: boolean } — streaming renders via StreamingResponse. |
| onSendMessage | (text: string) => void | — | Required. |
| title | ReactNode | — | |
| onClose | () => void | — | |
| isSubmitting | boolean | false | |
| emptyMessage | ReactNode | 'Ask a question to get started.' | Pass an <AssistantEmptyState> for the full empty-state pattern. |
| hasMoreOlderMessages/isLoadingOlderMessages/onLoadOlderMessages | mixed | — | Infinite-scroll-up pagination. |
| attachments/onRemoveAttachment, leading/trailing | mixed | — | Passed straight through to the internal PromptEditor. |
Subpath imports
Every component is imported by its own subpath, named after its directory: action-suggestions, agent-timeline, ai-diff-viewer, assistant-empty-state, assistant-panel, attachment-preview, chat-thread, citation-popover, citations-panel, command-runner, context-drawer, conversation-list, execution-console, message-actions, message-version-pager, model-picker, prompt-editor, prompt-history, reasoning-inspector, semantic-highlight, streaming-response, thinking-indicator, token-usage-meter, tool-call-card, workflow-builder. There is no root @vinyasa/ai-native entry, so import { X } from '@vinyasa/ai-native' fails to resolve. See "This package has no root export" above for why.
Development
From the repository root:
pnpm --filter @vinyasa/ai-native build
pnpm --filter @vinyasa/ai-native test
pnpm --filter @vinyasa/ai-native lint
pnpm storybook # AI Native/<ComponentName>