Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
c079855
RFC: veryfront/chat + attachments — proposed API shape (docs only)
mattboon Jul 18, 2026
6a0d83d
docs(rfc): deepen all component pages — per-part props, default conte…
mattboon Jul 18, 2026
19d03b6
docs(rfc): part status badges + parts indexes; audit-settled decision…
mattboon Jul 20, 2026
9478ff0
docs(rfc): apply all 73 verification-audit findings
mattboon Jul 20, 2026
ebf051c
Resolve chat RFC review blockers
kojiwakayama Jul 24, 2026
c3ce006
Merge main into chat RFC branch
kojiwakayama Jul 24, 2026
47a9e6e
Keep the chat RFC reviewable on the active framework line
kojiwakayama Jul 24, 2026
9b3b88e
Keep middleware guidance inside the public-doc contract
kojiwakayama Jul 24, 2026
8bbd6fb
Make the proposed chat contracts internally complete
kojiwakayama Jul 24, 2026
3b9b5e7
Keep the chat RFC mergeable on the current release line
kojiwakayama Jul 24, 2026
3aad629
docs(rfc): collapse veryfront/ui hook stubs into one generic reference
mattboon Jul 24, 2026
bb24727
docs(rfc): make useStepIndicator hybrid (explicit arg or context)
mattboon Jul 24, 2026
90bd47d
docs(rfc): correct consumed-from-ui — chat DOES re-export AppShell; f…
mattboon Jul 24, 2026
54617a1
docs(rfc): fix doc contradictions surfaced by full-surface review
mattboon Jul 24, 2026
95be5a5
docs(rfc): record generic-core/veryfront-adapter split + flag app-cou…
mattboon Jul 24, 2026
cabba4b
Keep the chat RFC aligned with the active framework line
kojiwakayama Jul 25, 2026
0804feb
Keep the chat shell contract decided before implementation
kojiwakayama Jul 25, 2026
f16059d
Keep vulnerable transitive npm packages out of releases
kojiwakayama Jul 24, 2026
f79fefa
Keep the security lock graph native to the chat RFC branch
kojiwakayama Jul 25, 2026
f361399
Keep the chat RFC mergeable after the middleware release
kojiwakayama Jul 25, 2026
524c709
docs(rfc): record proposed v1 scope cuts (earns-its-place review)
mattboon Jul 25, 2026
fc70ef0
docs(rfc): reframe reusability around AG-UI protocol; correct AgentCa…
mattboon Jul 25, 2026
ac997b1
Keep the chat RFC docs-only at the branch boundary
kojiwakayama Jul 25, 2026
05b74fb
Keep the chat RFC current with main after cleanup
kojiwakayama Jul 25, 2026
01c84cf
Keep the chat RFC inside Deno markdown formatting
kojiwakayama Jul 25, 2026
fb2f28e
Keep the chat RFC current with main
kojiwakayama Jul 29, 2026
5b3356f
Align the chat RFC with public copy punctuation
kojiwakayama Jul 29, 2026
5a975ad
Preserve chat RFC navigation after punctuation cleanup
kojiwakayama Jul 29, 2026
0717a62
Resolve chat RFC internal contradictions
kojiwakayama Jul 30, 2026
7d238d8
Settle popper anchoring: fix in ui, no wrapper exception
kojiwakayama Jul 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ boundaries, or docs structure.
page is `api-reference/index.md`; do not use `README.md` for public pages.
- `architecture/`: Private Veryfront Code architecture notes. These docs are
not part of the public docs sync.
- `rfcs/`: Proposed target-state designs and migration records. These docs are
not current-state architecture notes and must not claim merge readiness while
unresolved contract questions remain.

Shared plans and unresolved work belong in the GitHub issue tracker. Local
planning notes may use `docs/plans/`; that directory is Git-ignored and excluded
Expand Down
1,124 changes: 1,124 additions & 0 deletions docs/rfcs/29-chat-api-shape.md

Large diffs are not rendered by default.

124 changes: 124 additions & 0 deletions docs/rfcs/29-chat-api-shape/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# `veryfront/chat` - proposed API shape

Reference pages for the proposed `veryfront/chat` surface, accompanying the RFC one level up: [`29-chat-api-shape.md`](../29-chat-api-shape.md). Everything here documents the _proposed_ shape - none of it is implemented yet. Each page carries the same status banner; the RFC holds the full rationale, cross-cutting contracts, and resolved decisions.

## The three layers

```
L1 Preset (black box) <Chat agentId api />
L2 Components (ui-style) <ChatInput><ChatInput.Field/><ChatInput.Submit/></ChatInput>
L3 Headless hooks const c = useChatInput(); <textarea {...c.getFieldProps()} />
```

One graduation path, not three products: every L1 default is public L2; every L2 component is a thin shell over a public L3 hook. The library owns behaviour and state; the consumer owns markup.

## Components

### Session & shell

- [Chat](./components/chat.md) - the L1 preset
- [ChatRoot](./components/chat-root.md) - scoped session provider
- [ChatMessageList](./components/chat-message-list.md) - the transcript scroll container
- [ChatThemeScope](./components/chat-theme-scope.md) - token scope
- [ChatErrorBoundary](./components/chat-error-boundary.md) - error boundary
- [AppShell](./components/app-shell.md) - app layout (from `veryfront/ui`; reference)

### Composer

- [ChatInput](./components/chat-input.md) - the composer (`Field`, `Attach`, `Model`, `Voice`, `Submit`, …)
- [AttachmentPill](./components/attachment-pill.md) - pending upload chip

### Messages

- [Message](./components/message.md) - one message row and its parts
- [ToolCall](./components/tool-call.md) - tool lifecycle incl. approval
- [Reasoning](./components/reasoning.md) - reasoning disclosure
- [StepIndicator](./components/step-indicator.md) - step lifecycle
- [Sources](./components/sources.md) - citation list
- [InlineCitation](./components/inline-citation.md) - inline footnote markers
- [MessageActionBar](./components/message-action-bar.md) - re-export of the `Message.Actions` family
- [BranchPicker](./components/branch-picker.md) - message branch navigation
- [Markdown](./components/markdown.md) - streamed markdown + `RichCodeBlock` (the sanctioned multi-node exception)

### Conversations & files

- [ChatSidebar](./components/chat-sidebar.md) - conversation list
- [AttachmentsPanel](./components/attachments-panel.md) - durable files

### Agents & models

- [AgentPicker](./components/agent-picker.md) - agent selection compound
- [ModelSelector](./components/model-selector.md) - model selection compound
- [AgentCard](./components/agent-card.md) - agent detail preset
- [ChatAgentPicker](./components/chat-agent-picker.md) - preset over `AgentPicker`

### Chrome

- [ChatEmptyState](./components/chat-empty-state.md) - empty transcript + suggestions
- [ChatActions](./components/chat-actions.md) - thread-level actions menu

## Hooks

### Session & thread

- [useChat](./hooks/use-chat.md) - base session
- [useConversationChat](./hooks/use-conversation-chat.md) - session bound to the active thread
- [useCompletion](./hooks/use-completion.md) - one-shot text
- [useStreaming](./hooks/use-streaming.md) - low-level stream state
- [useChatContext](./hooks/use-chat-context.md) - read `ChatRoot` context
- [useChatErrorHandler](./hooks/use-chat-error-handler.md) - error boundary state
- [useChatScroll](./hooks/use-chat-scroll.md) - the scroll contract
- [useChatActions](./hooks/use-chat-actions.md) - `ChatActions` context reader

### Composer

- [useChatInput](./hooks/use-chat-input.md) - sole owner of input state
- [useChatInputContext](./hooks/use-chat-input-context.md) - read `ChatInput` context
- [useVoiceInput](./hooks/use-voice-input.md) - dictation
- [useUpload](./hooks/use-upload.md) - pending uploads + dropzone
- [useAttachmentPill](./hooks/use-attachment-pill.md) - per-pill context reader

### Messages

- [useMessageContext](./hooks/use-message-context.md) - read `Message` context
- [useMessageParts](./hooks/use-message-parts.md) - typed part groups
- [useClipboard](./hooks/use-clipboard.md) - copy with `copied` feedback
- [useToolCall](./hooks/use-tool-call.md) - tool part state
- [useReasoning](./hooks/use-reasoning.md) - reasoning disclosure state
- [useStepIndicator](./hooks/use-step-indicator.md) - step state
- [useSources](./hooks/use-sources.md) - citation list
- [useMessageBranches](./hooks/use-message-branches.md) - branch index/count/navigation

### Files

- [useAttachments](./hooks/use-attachments.md) - durable files
- [useAttachmentsPanel](./hooks/use-attachments-panel.md) - panel context reader

### Conversations

- [useConversations](./hooks/use-conversations.md) - list, active thread, CRUD, `selectAgent`
- [useConversation](./hooks/use-conversation.md) - one conversation
- [useConversationsContext](./hooks/use-conversations-context.md) - read `ConversationsProvider`
- [useChatSidebarItem](./hooks/use-chat-sidebar-item.md) - per-row `ChatSidebar.Item` context reader

### Agents & models

- [useAgents](./hooks/use-agents.md) - agents list
- [useAgentMetadata](./hooks/use-agent-metadata.md) - one agent's metadata
- [useAgent](./hooks/use-agent.md) - agent session callbacks
- [useAgentCard](./hooks/use-agent-card.md) - `AgentCard` context reader
- [useAgentPicker](./hooks/use-agent-picker.md) - picker state
- [useModelSelector](./hooks/use-model-selector.md) - selector state

### Shell

- [Consumed from `veryfront/ui`](./hooks/consumed-from-ui.md) - `useAppShell`, `useColorMode` (reference only; owned by `veryfront/ui`, not chat)

## Everything else

- [Helpers](./helpers.md) - pure functions, no DOM (`getTextContent`, `groupPartsInOrder`, `mergeProps`, …)
- [Providers](./providers.md) - the zero-node provider contract and precedence rules

## Cut from v1

**`MessageFeedback` / `useFeedback`** are cut from v1 - there is no backend endpoint behind them. They return additively when the endpoint exists ("nothing ships ahead of its backend").
216 changes: 216 additions & 0 deletions docs/rfcs/29-chat-api-shape/components/agent-card.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# AgentCard

A status card for one running agent: identity header, live status, reasoning, tool calls, and streamed output - render it whole, or compose the parts.

> **Status: proposed (RFC).** This page documents the _proposed_ API shape - not yet implemented. Full rationale: [`29-chat-api-shape.md`](../../29-chat-api-shape.md).

> **⚠ Reusability flag** (see [generic core vs veryfront adapter](../../29-chat-api-shape.md)): the status _values_ (`idle` / `thinking` / `tool_execution` / `streaming` / `completed` / `error`) are **AG-UI-protocol states - generic, not coupling**. The real coupling is that this component imports veryfront agent-SDK `AgentMessage`/`ToolCall` **types** and duplicates `Message`/`ToolCall`/`Reasoning`/`Markdown` for a runtime view. Type it against the generic AG-UI shape, or move the SDK-typed card to the veryfront adapter.

## Import

```tsx
import { AgentCard } from "veryfront/chat";

// every sub-part is also a flat named export (same function), with its props type
import { AgentCard, AgentCardHeader, type AgentCardHeaderProps } from "veryfront/chat";
```

## Parts index

- [`.Root`](#agentcardroot---changed) - `changed`: status surfaces as `data-agent-status`
- [`.Header`](#agentcardheader---kept) - `kept`
- [`.Reasoning`](#agentcardreasoning---kept) - `kept`
- [`.Tools`](#agentcardtools---kept) - `kept`
- [`.Body`](#agentcardbody---kept) - `kept`

## Anatomy

```tsx
<AgentCard.Root status="thinking" name="Support Agent">
<AgentCard.Header /> {/* Avatar · name · status dot */}
<AgentCard.Reasoning /> {/* <Reasoning> block, only while thinking text exists */}
<AgentCard.Tools /> {/* one <ToolCall> card per tool call */}
<AgentCard.Body /> {/* each message rendered as <Markdown> */}
</AgentCard.Root>;
```

`<AgentCard.Root>` with **no children renders exactly this default anatomy** (render-or-compose, like `ToolCall`). Pass children to recompose, reorder, or omit parts.

## Default DOM (childless render)

Everything is an in-flow flex child - a vertical stack of rows. Nothing is
floated or absolutely positioned:

```html
<div class="flex flex-col gap-3 …card outline surface, md padding" data-agent-status="thinking">
<!-- .Root - vertical stack -->
<div class="flex items-center gap-2">
<!-- .Header - one horizontal row -->
<div class="size-8 …">…</div>
<!-- avatar: fixed square, first in row -->
<span class="min-w-0 truncate font-medium">…</span><!-- name: truncates when narrow -->
<div class="ml-auto …">● Thinking</div>
<!-- status: ml-auto pushes it to the row end -->
</div>
<div>…</div>
<!-- .Reasoning - only when `thinking` present -->
<div class="flex flex-col gap-2">
<!-- .Tools - vertical list, only when toolCalls.length > 0 -->
<div>…ToolCall card…</div>
<!-- one per tool call, full row width -->
</div>
<div class="flex flex-col gap-2">
<!-- .Body - vertical list, only when messages exist -->
<div class="text-[15px] leading-7">…markdown…</div>
<!-- one Markdown block per message -->
</div>
</div>
```

**Layout model:** `.Root` = column with `gap-3`; `.Header` = the only horizontal
row (avatar → name → status, status right-aligned via `ml-auto`, not absolute);
`.Reasoning`/`.Tools`/`.Body` stack full-width beneath it and disappear entirely
(render `null`) when their data is absent.

## Parts

### `AgentCard.Root` - `changed`

**Changed:** agent status - today presented only visually (dot color + pulse) - surfaces as `data-agent-status` for styling.

The card container (one `<div>`, `ui` Card surface) + the compound's scoped context. All agent data enters here; sub-parts read it from context.

| Prop | Type | Default | Description |
| --------------------- | ----------------------------------------------------------------------------------- | --------- | ---------------------------------------------------------- |
| `status` _(required)_ | `'idle' \| 'thinking' \| 'tool_execution' \| 'streaming' \| 'completed' \| 'error'` | - | Drives the header status dot/label and `data-agent-status` |
| `name` | `string` | `"Agent"` | Display name shown by `.Header` |
| `avatarUrl` | `string` | - | Avatar image; falls back to the name's initial |
| `messages` | `AgentMessage[]` | - | Streamed output; `.Body` renders each as Markdown |
| `toolCalls` | `AgentToolCall[]` | `[]` | `.Tools` renders each through the `ToolCall` card |
| `thinking` | `string` | - | Reasoning text; `.Reasoning` renders only when present |
| `asChild` | `boolean` | `false` | Merge onto your own element |
| + native | `React.HTMLAttributes<HTMLDivElement>` · `ref` | - | Spread onto the single node; `className` merges |

**State attributes (proposed):** `data-agent-status="idle|thinking|tool_execution|streaming|completed|error"` - today status is presented only visually (dot color + pulse); the RFC surfaces it as `data-*` so you can style any part off `[data-agent-status="error"]`.

### `AgentCard.Header` - `kept`

One `<div>` row. Default content: `Avatar` (image or initial) → agent name (`truncate`) → `Status` dot + label pushed right (`Thinking`/`Running tools`/`Responding`/`Completed`/`Error`/`Idle`, pulsing while active). Mirrors `Message.Header`.

| Prop | Type | Description |
| ------------------------------------------------------------ | ---- | --------------------------------------------------------------------- |
| `asChild` + native (`HTMLAttributes<HTMLDivElement>`, `ref`) | | Own the node; children replace the default Avatar/name/Status content |

### `AgentCard.Reasoning` - `kept`

Renders the shared [`Reasoning`](./reasoning.md) block with the card's `thinking` text. **Renders `null` when `thinking` is empty** - safe to include unconditionally.

| Prop | Type | Description |
| -------------------------- | ---- | ---------------------------------------------------------------------------------------------------------- |
| `asChild` + native + `ref` | | Applied to the `Reasoning` root; all `Reasoning` behavior (auto-open while streaming, `data-open`) applies |

### `AgentCard.Tools` - `kept`

One `<div>` list. Default content: one [`ToolCall`](./tool-call.md) card per entry in `toolCalls` (agent tool statuses map onto the standard tool lifecycle: `pending → input-available`, `executing → input-streaming`, `completed → output-available`, `error → output-error`). **Renders `null` when there are no tool calls.**

| Prop | Type | Description |
| -------------------------- | ---- | --------------------------------------------------------------------------------------------------- |
| `asChild` + native + `ref` | | Own the list node; children replace the default `ToolCall` mapping (use `useAgentCard().toolCalls`) |

### `AgentCard.Body` - `kept`

One `<div>` column. Default content: each message's text rendered as [`Markdown`](./markdown.md) (mirroring `Message.Content`). **Renders `null` when there are no messages.**

| Prop | Type | Description |
| -------------------------- | ---- | ------------------------------------------------------------------------------------------- |
| `asChild` + native + `ref` | | Own the node; children replace the default Markdown mapping (use `useAgentCard().messages`) |

## Context (what the parts read)

`useAgentCard()` - throws outside `AgentCard.Root`:

```ts
{
name: string
avatarUrl?: string
status: AgentStatus
thinking?: string
messages?: AgentMessage[]
toolCalls: AgentToolCall[]
presentation: { color: StatusColor; label: string; pulse: boolean }
}
```

The card's data types:

```ts
type AgentMessage = {
id: string;
parts: Array<{ type: "text"; text: string } | { type: string }>; // `.Body` renders the text parts
};

// renamed from the agent-side `ToolCall` type to avoid colliding with the `ToolCall` component
type AgentToolCall = {
id: string;
name: string;
status: "pending" | "executing" | "completed" | "error";
args: Record<string, unknown>;
result?: unknown;
error?: string;
};

type StatusColor = "gray" | "blue" | "green" | "red" | "yellow"; // the `ui` Status color union
```

## Examples

### Default

```tsx
<AgentCard
status={agent.status}
name={agent.name}
avatarUrl={agent.avatarUrl}
messages={agent.messages}
toolCalls={agent.toolCalls}
thinking={agent.thinking}
/>;
```

### Composed - reorder, restyle, keep behavior

```tsx
<AgentCard.Root status={agent.status} name={agent.name} toolCalls={agent.toolCalls}>
<AgentCard.Header className="border-b pb-2" />
<div className="grid grid-cols-2 gap-3">
{/* YOUR div */}
<AgentCard.Tools />
<AgentCard.Body />
</div>
{/* Reasoning deliberately omitted */}
</AgentCard.Root>;
```

### Headless - your card, library data

```tsx
function MyAgentRow() {
const { name, status, toolCalls, presentation } = useAgentCard();
return (
<li data-agent-status={status} className="anything">
{name} - {presentation.label} ({toolCalls.length} tools)
</li>
);
}
```

## Customization (eject path)

1. **L1** - `<AgentCard {...data} />`, default anatomy.
2. **L2** - pass children: the printed default anatomy above _is_ the starting point; every part is one node (`asChild`, your classes), every layout div between them is yours.
3. **L3** - [`useAgentCard()`](../hooks/use-agent-card.md) inside the Root, or skip the compound entirely and build from [`useAgentMetadata`](../hooks/use-agent-metadata.md) / [`useAgents`](../hooks/use-agents.md).

## Related

- [`useAgentCard`](../hooks/use-agent-card.md) · [`useAgentMetadata`](../hooks/use-agent-metadata.md) · [`useAgents`](../hooks/use-agents.md)
- [`ToolCall`](./tool-call.md) · [`Reasoning`](./reasoning.md) · [`Markdown`](./markdown.md) · [`AgentPicker`](./agent-picker.md)
Loading