docs(22): UI design contract for agent-streaming phase
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2c89093107
commit
f7e4ed9d7b
1 changed files with 507 additions and 0 deletions
507
.planning/phases/22-agent-streaming/22-UI-SPEC.md
Normal file
507
.planning/phases/22-agent-streaming/22-UI-SPEC.md
Normal file
|
|
@ -0,0 +1,507 @@
|
|||
---
|
||||
phase: 22
|
||||
slug: agent-streaming
|
||||
status: draft
|
||||
shadcn_initialized: true
|
||||
preset: new-york / neutral / css-variables
|
||||
created: 2026-04-01
|
||||
---
|
||||
|
||||
# Phase 22 — UI Design Contract
|
||||
|
||||
> Visual and interaction contract for Phase 22: Agent Streaming.
|
||||
> Generated by gsd-ui-researcher. Verified by gsd-ui-checker.
|
||||
|
||||
---
|
||||
|
||||
## Design System
|
||||
|
||||
| Property | Value | Source |
|
||||
|----------|-------|--------|
|
||||
| Tool | shadcn/ui | `ui/components.json` — unchanged from Phase 21 |
|
||||
| Style | new-york | `ui/components.json` |
|
||||
| Base color | neutral | `ui/components.json` |
|
||||
| CSS variables | true | `ui/components.json` |
|
||||
| Component library | Radix UI (via shadcn new-york) | `ui/components.json` |
|
||||
| Icon library | lucide-react ^0.574.0 | `ui/components.json` |
|
||||
| Font | System UI (`font-sans`, inherited) | `ui/src/index.css` |
|
||||
|
||||
**Existing shadcn components available (no install needed):**
|
||||
`avatar`, `badge`, `button`, `card`, `checkbox`, `collapsible`, `command`, `dialog`, `dropdown-menu`, `input`, `label`, `popover`, `scroll-area`, `select`, `separator`, `sheet`, `skeleton`, `tabs`, `textarea`, `tooltip`
|
||||
|
||||
**Existing custom components to reuse/extend:**
|
||||
- `ChatPanel.tsx` — extend with agent selector header area and streaming state; do not restructure the two-column layout
|
||||
- `ChatMessage.tsx` — extend props to accept `agentId`, `agentName`, `agentIcon` for identity rendering
|
||||
- `ChatMessageList.tsx` — replace with virtualized list (`@tanstack/react-virtual`) for PERF-03
|
||||
- `ChatInput.tsx` — extend with slash command popover and @mention popover (reuse `MentionOption` pattern from `MarkdownEditor.tsx`)
|
||||
- `AgentIcon` (from `AgentIconPicker.tsx`) — reuse directly for agent avatar rendering in messages
|
||||
- `agentStatusDot` (from `status-colors.ts`) — reuse `running: "bg-cyan-400 animate-pulse"` for streaming indicator
|
||||
|
||||
**New package required:**
|
||||
- `@tanstack/react-virtual` — not currently installed; add to `ui/package.json` for PERF-03 (1,000+ message virtualization)
|
||||
|
||||
---
|
||||
|
||||
## Layout Contract
|
||||
|
||||
### Layout Unchanged
|
||||
|
||||
The overall layout established in Phase 21 is unchanged:
|
||||
|
||||
```
|
||||
[ CompanyRail ] [ Sidebar ] [ <main> ] [ ChatPanel ] [ PropertiesPanel ]
|
||||
```
|
||||
|
||||
Phase 22 adds new UI surfaces **inside** `ChatPanel` only. No layout-level changes.
|
||||
|
||||
### ChatPanel Header — Agent Selector
|
||||
|
||||
The `ChatPanel` header row gains an agent selector to the left of the close button:
|
||||
|
||||
```
|
||||
[ "Chat" label ] [ AgentSelector dropdown ] [ ─── spacer ─── ] [ X close ]
|
||||
```
|
||||
|
||||
- The `AgentSelector` is a `<Select>` or `<Popover>` trigger showing the active agent's icon + name
|
||||
- Width: fit-content, max 120px, truncated with ellipsis
|
||||
- Placement: `flex items-center gap-2` row, same `px-4 py-2` padding as Phase 21 header
|
||||
- When no agent is selected, label reads "Select agent" in `text-muted-foreground`
|
||||
|
||||
### Message Bubble — Agent Identity Bar
|
||||
|
||||
Every assistant message gains an identity bar above the message content:
|
||||
|
||||
```
|
||||
[ AgentIcon 16px ] [ agentName 13px semibold ] [ timestamp 11px muted ]
|
||||
```
|
||||
|
||||
- Identity bar: `flex items-center gap-1.5 mb-1`
|
||||
- Icon: 16×16px, rendered via `<AgentIcon icon={agentIcon} className="h-4 w-4" />`
|
||||
- Name: `text-[13px] font-semibold text-foreground`
|
||||
- Timestamp: `text-[11px] text-muted-foreground`
|
||||
- No background, no border — identity floats above message content
|
||||
|
||||
### Streaming Cursor
|
||||
|
||||
A blinking block cursor appended to the last streamed token while generation is active:
|
||||
|
||||
- Element: `<span className="inline-block w-2 h-[1em] bg-foreground/70 animate-cursor-blink ml-0.5 align-text-bottom" />`
|
||||
- Animation: `animate-cursor-blink` — declare in `index.css` (see Animation section)
|
||||
- Rendered only when `isStreaming: true` on the message; removed when stream ends
|
||||
|
||||
### Stop Button
|
||||
|
||||
While a response is streaming, a Stop button appears below the message thread, centered above `ChatInput`:
|
||||
|
||||
```
|
||||
[ (─────────────) ] ← centered row
|
||||
[ ■ Stop generating ] ← Button variant="outline" size="sm"
|
||||
[ (─────────────) ]
|
||||
[ ChatInput ]
|
||||
```
|
||||
|
||||
- Container: `flex justify-center py-2 border-t border-border`
|
||||
- Button: `variant="outline" size="sm"`, icon `Square` (filled stop), label "Stop generating"
|
||||
- Disappears immediately on click (optimistic) before server confirms cancellation
|
||||
|
||||
### Edit / Retry Controls
|
||||
|
||||
User messages gain an edit pencil on hover. Assistant messages gain a retry button on hover.
|
||||
|
||||
**User message edit:**
|
||||
- `Pencil` icon button, 14×14px, `variant="ghost" size="icon"`, positioned at top-right of message bubble
|
||||
- Visible only on `group-hover`; always visible on touch devices
|
||||
- Click → switches message bubble to inline edit mode (see Interaction Contract)
|
||||
|
||||
**Assistant message retry:**
|
||||
- `RefreshCw` icon button, 14×14px, `variant="ghost" size="icon"`, positioned below message content, right-aligned
|
||||
- Visible only on `group-hover`; always visible on touch devices
|
||||
- Only present on messages where `isStreaming: false` and `role === "assistant"`
|
||||
|
||||
### Slash Command Popover
|
||||
|
||||
When the user types `/` as the first character in `ChatInput`:
|
||||
|
||||
```
|
||||
[ /brainstorm — Route to Brainstormer ] ← highlighted item
|
||||
[ /ask-pm — Route to PM ]
|
||||
[ /ask-engineer — Route to Engineer ]
|
||||
[ /task — Create a task ]
|
||||
[ /search — Search conversations ]
|
||||
```
|
||||
|
||||
- Container: shadcn `<Popover>` anchored to the top-left of the textarea, opens upward
|
||||
- List: `<Command>` component inside popover (reuses existing `cmdk` install)
|
||||
- Width: 260px, max 5 items visible before scroll
|
||||
- Dismiss: Escape, clicking outside, or completing the command
|
||||
|
||||
### @Mention Popover
|
||||
|
||||
When the user types `@` in `ChatInput`, an agent autocomplete popover appears. Reuses the `MentionOption` pattern from `MarkdownEditor.tsx`:
|
||||
|
||||
- Container: `<Popover>` anchored to the `@` character position, opens upward
|
||||
- List: agent names filtered by query string after `@`
|
||||
- Each row: `<AgentIcon>` 14×14px + `agentName` text + `role` label in muted text
|
||||
- Width: 200px, max 5 agents before scroll
|
||||
- Selection: click or Enter inserts `@agentName` token into textarea and routes message to that agent
|
||||
|
||||
---
|
||||
|
||||
## Spacing Scale
|
||||
|
||||
Inherited from Phase 21. No new tokens for Phase 22.
|
||||
|
||||
| Token | Value | Phase 22 Usage |
|
||||
|-------|-------|----------------|
|
||||
| xs | 4px | Icon gaps in identity bar (`gap-1`) |
|
||||
| sm | 8px | Stop button padding, retry button margin |
|
||||
| md | 16px | Panel padding (unchanged) |
|
||||
| lg | 24px | (no new usage) |
|
||||
| xl | 32px | (no new usage) |
|
||||
|
||||
**New spacing values (Phase 22 only):**
|
||||
- Identity bar gap: `gap-1.5` (6px) — between icon and agent name
|
||||
- Edit/retry icon button size: 14×14px (`h-3.5 w-3.5`) — matches `MoreHorizontal` in `ChatConversationItem`
|
||||
- Streaming cursor width: `w-2` (8px), height: `h-[1em]`
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
All inherited from Phase 21. One addition for agent identity:
|
||||
|
||||
| Role | Size | Weight | Line Height | Usage |
|
||||
|------|------|--------|-------------|-------|
|
||||
| Body / message text | 15px (0.9375rem) | 400 | 1.6 | Chat message prose (unchanged) |
|
||||
| Label / UI chrome | 13px (0.8125rem) | 400 | 1.4 | Conversation list, timestamps (unchanged) |
|
||||
| Agent name (identity bar) | 13px (0.8125rem) | 600 | 1.4 | Agent name above assistant messages |
|
||||
| Message timestamp | 11px (0.6875rem) | 400 | 1.4 | Timestamp in identity bar |
|
||||
| Slash command label | 13px (0.8125rem) | 400 | 1.4 | Command name in slash popover |
|
||||
| Slash command description | 12px (0.75rem) | 400 | 1.4 | Description text in slash popover, `text-muted-foreground` |
|
||||
|
||||
**Weights used:** 400 (regular) and 600 (semibold). No additional weights. Same constraint as Phase 21.
|
||||
|
||||
**11px timestamp note:** This is a 2px reduction below the Phase 21 minimum of 13px, used only within the compact identity bar where spatial context is sufficient. It is never used for interactive or error text.
|
||||
|
||||
---
|
||||
|
||||
## Color
|
||||
|
||||
All values inherited from Phase 21 CSS variable system. No new color variables introduced.
|
||||
|
||||
| Role | Catppuccin Mocha | Tokyo Night | Catppuccin Latte | Phase 22 Usage |
|
||||
|------|-----------------|-------------|-----------------|----------------|
|
||||
| Dominant (60%) | `--background` #1e1e2e | `--background` #1a1b26 | `--background` #eff1f5 | Unchanged |
|
||||
| Secondary (30%) | `--card` #181825 | `--card` #16161e | `--card` #e6e9ef | Unchanged |
|
||||
| Accent (10%) | `--accent` #45475a | `--accent` #3b4261 | `--accent` #bcc0cc | Hovered rows (unchanged) |
|
||||
| Primary | `--primary` | `--primary` | `--primary` | Agent selector focus ring, send button |
|
||||
| Destructive | `--destructive` | `--destructive` | `--destructive` | Not used in Phase 22 |
|
||||
| Muted text | `--muted-foreground` | `--muted-foreground` | `--muted-foreground` | Timestamps, role labels, empty states |
|
||||
|
||||
**Accent reserved for (Phase 22 additions):**
|
||||
- Same as Phase 21 (conversation rows, code block toolbar)
|
||||
- Slash command popover highlighted item: `bg-accent` (via `<Command>` item selection)
|
||||
|
||||
**Streaming indicator color:** `bg-cyan-400 animate-pulse` — reused directly from `agentStatusDot.running` in `status-colors.ts`. Applied as a 6×6px dot beside the agent name in the identity bar while streaming.
|
||||
|
||||
### Agent Role Colors (THEME-03)
|
||||
|
||||
Agent avatars are distinguishable across all three themes using semantic role color tokens. These use Tailwind semantic colors with `dark:` variants — no new CSS variables needed.
|
||||
|
||||
| Role | Icon color (light theme) | Icon color (dark theme) | Rationale |
|
||||
|------|--------------------------|-------------------------|-----------|
|
||||
| `pm` | `text-blue-600` | `text-blue-400` | Matches `todo` status color (existing pattern) |
|
||||
| `engineer` | `text-violet-600` | `text-violet-400` | Matches `in_review` status color (existing pattern) |
|
||||
| `ceo` / `general` | `text-yellow-600` | `text-yellow-400` | Matches `idle` agent status (existing pattern) |
|
||||
| `designer` | `text-pink-600` | `text-pink-400` | New — no conflict with existing semantic |
|
||||
| `qa` | `text-orange-600` | `text-orange-400` | Matches `paused` agent status (existing pattern) |
|
||||
| `researcher` | `text-teal-600` | `text-teal-400` | New — no conflict with existing semantic |
|
||||
| `devops` / `cto` | `text-green-600` | `text-green-400` | Matches `done` / `active` status (existing pattern) |
|
||||
| `cmo` / `cfo` | `text-neutral-600` | `text-neutral-400` | Secondary/finance roles — muted |
|
||||
| fallback | `text-muted-foreground` | `text-muted-foreground` | Unknown or null role |
|
||||
|
||||
These colors apply to the `<AgentIcon>` in the message identity bar only. The existing `status-colors.ts` patterns are NOT modified — Phase 22 adds a new `agent-role-colors.ts` utility.
|
||||
|
||||
---
|
||||
|
||||
## Component Inventory
|
||||
|
||||
New components to build in Phase 22:
|
||||
|
||||
| Component | shadcn base | Notes |
|
||||
|-----------|-------------|-------|
|
||||
| `ChatAgentSelector.tsx` | `select` or `popover` + `command` | Dropdown in ChatPanel header; shows active agent icon + name; lists all workspace agents |
|
||||
| `ChatMessageIdentityBar.tsx` | none | Icon + name + timestamp row above assistant messages; uses `AgentIcon` |
|
||||
| `ChatStreamingCursor.tsx` | none | Inline blinking cursor element; rendered conditionally during streaming |
|
||||
| `ChatStopButton.tsx` | `button` | "Stop generating" button shown above input during active stream |
|
||||
| `ChatMessageActions.tsx` | `button`, `tooltip` | Edit (on user messages) and Retry (on assistant messages) hover actions |
|
||||
| `ChatSlashCommandPopover.tsx` | `popover`, `command` | Slash command menu anchored to textarea; 5 commands |
|
||||
| `ChatMentionPopover.tsx` | `popover` | Agent @mention autocomplete anchored to @ position |
|
||||
| `useStreamingChat.ts` | none | Hook managing SSE connection, token accumulation, streaming state |
|
||||
| `agent-role-colors.ts` | none | Map of `AgentRole → Tailwind class string` for icon coloring |
|
||||
|
||||
**Existing components to modify:**
|
||||
|
||||
| Component | Change |
|
||||
|-----------|--------|
|
||||
| `ChatPanel.tsx` | Add `ChatAgentSelector` to header; add `ChatStopButton` above input when streaming |
|
||||
| `ChatMessage.tsx` | Accept `agentId`, `agentName`, `agentIcon`, `agentRole`, `isStreaming` props; render `ChatMessageIdentityBar` and `ChatStreamingCursor`; wrap in `group` for hover actions |
|
||||
| `ChatMessageList.tsx` | Replace div iteration with `@tanstack/react-virtual` virtualizer; pass agent identity props down to `ChatMessage` |
|
||||
| `ChatInput.tsx` | Wire `ChatSlashCommandPopover` (on `/` keystroke) and `ChatMentionPopover` (on `@` keystroke); accept `disabled` prop during streaming |
|
||||
|
||||
**Icons (lucide-react) — Phase 22 additions:**
|
||||
- `Square` — Stop generating button icon (filled stop)
|
||||
- `Pencil` — Edit user message
|
||||
- `RefreshCw` — Retry assistant message
|
||||
- `ChevronDown` — Agent selector dropdown indicator
|
||||
- `Bot` — Default agent icon fallback (already in `AGENT_ICON_NAMES`)
|
||||
|
||||
**All icons from Phase 21 remain unchanged.**
|
||||
|
||||
---
|
||||
|
||||
## Interaction Contract
|
||||
|
||||
### Streaming Response
|
||||
|
||||
| Interaction | Behavior |
|
||||
|-------------|---------|
|
||||
| User sends message | `ChatInput` disabled; `isSubmitting` spinner on send button; SSE connection opens |
|
||||
| First token arrives | New assistant message appended to list; identity bar renders; `ChatStreamingCursor` shown; `ChatStopButton` appears above input |
|
||||
| Tokens continue | Text accumulates character-by-character in the message; cursor stays at end; list auto-scrolls to bottom if user has not scrolled up |
|
||||
| User scrolls up during stream | Auto-scroll pauses; "Jump to bottom" button appears (↓ icon, `variant="outline" size="icon-sm"`, fixed at bottom-right of message list) |
|
||||
| Stream ends | `ChatStreamingCursor` removed; `ChatStopButton` removed; `ChatInput` re-enabled; message gets final `updatedAt` timestamp |
|
||||
| Stream error | Streaming cursor replaced with error inline text: "Response interrupted. [Retry ↺]" — `text-destructive` with inline Retry link |
|
||||
|
||||
### Stop Generation
|
||||
|
||||
| Interaction | Behavior |
|
||||
|-------------|---------|
|
||||
| Click "Stop generating" | Button removed immediately (optimistic); SSE connection closed client-side; partial message preserved as-is with a `[stopped]` suffix in muted text |
|
||||
| Server confirms stop | No additional UI change needed — state already settled on client |
|
||||
|
||||
### Message Edit (User Messages)
|
||||
|
||||
| Interaction | Behavior |
|
||||
|-------------|---------|
|
||||
| Hover user message | `Pencil` icon button appears at top-right of bubble |
|
||||
| Click `Pencil` | Message bubble becomes an inline textarea pre-filled with existing content; "Save" and "Cancel" buttons appear below; send button hidden |
|
||||
| Edit textarea + click "Save" | POST updated message to server; assistant messages after this message are removed from thread (regeneration); new streaming response begins |
|
||||
| Click "Cancel" | Textarea reverts to read-only bubble; no changes |
|
||||
| Save with empty content | "Save" button disabled when textarea is empty |
|
||||
|
||||
### Retry (Assistant Messages)
|
||||
|
||||
| Interaction | Behavior |
|
||||
|-------------|---------|
|
||||
| Hover assistant message | `RefreshCw` icon button appears below message, right-aligned |
|
||||
| Click `RefreshCw` | Current assistant message replaced with a streaming placeholder; SSE connection opens; regenerated response streams in |
|
||||
| Retry during active stream | Not available — `RefreshCw` button hidden while any stream is active |
|
||||
|
||||
### Agent Selector
|
||||
|
||||
| Interaction | Behavior |
|
||||
|-------------|---------|
|
||||
| Click agent selector | Popover/dropdown opens listing all workspace agents (icon + name + role label) |
|
||||
| Select agent | Active agent updated for current conversation; `PATCH /conversations/:id` with `agentId`; selector shows new agent immediately (optimistic) |
|
||||
| No agents available | Selector shows "No agents" in muted text; disabled state |
|
||||
| Active agent deleted externally | Selector shows "Unknown agent" in muted text; conversation continues with null `agentId` until user selects another |
|
||||
|
||||
### Slash Commands (INPUT-05)
|
||||
|
||||
| Interaction | Behavior |
|
||||
|-------------|---------|
|
||||
| Type `/` as first char | `ChatSlashCommandPopover` opens above input listing all 5 commands |
|
||||
| Type `/bra` (partial) | Popover filters to matching commands |
|
||||
| Arrow keys / Tab | Navigate popover items |
|
||||
| Enter or click item | Command token inserted into textarea (e.g. `/brainstorm `); popover closes; routing applied on send |
|
||||
| Escape | Popover closes; typed text remains |
|
||||
| Send with `/brainstorm` prefix | Routes message to Brainstormer agent regardless of active agent selector |
|
||||
|
||||
**Slash command routing table:**
|
||||
|
||||
| Command | Routes to |
|
||||
|---------|-----------|
|
||||
| `/brainstorm` | Brainstormer (general role with brainstormer prompt) |
|
||||
| `/ask-pm` | PM role agent |
|
||||
| `/ask-engineer` | Engineer role agent |
|
||||
| `/task` | PM role agent (task creation intent) |
|
||||
| `/search` | No-op in Phase 22 (stubbed; Phase 24 implements) |
|
||||
|
||||
### @Mention Routing (INPUT-06)
|
||||
|
||||
| Interaction | Behavior |
|
||||
|-------------|---------|
|
||||
| Type `@` | `ChatMentionPopover` opens listing workspace agents (icon + name + role) |
|
||||
| Type `@eng` (partial) | List filters to agents whose name contains "eng" (case-insensitive) |
|
||||
| Select agent | `@AgentName` token inserted; popover closes |
|
||||
| Send message with `@AgentName` | Message routed to named agent for this turn only (overrides active agent selector) |
|
||||
| Unknown mention | Sent as plain text; no routing override |
|
||||
|
||||
### Virtualized Message List (PERF-03)
|
||||
|
||||
- Uses `@tanstack/react-virtual` with `useVirtualizer`
|
||||
- `overscan: 5` — renders 5 items above/below visible window
|
||||
- Item height: estimated 80px default; dynamic measurement via `measureElement` for variable-height markdown messages
|
||||
- `scrollMarginBottom: 120px` to keep new messages visible above the input area
|
||||
- "Jump to bottom" appears when `scrollOffset > 200px` from bottom; clicking calls `scrollToIndex(messages.length - 1, { align: 'end' })`
|
||||
|
||||
---
|
||||
|
||||
## Copywriting Contract
|
||||
|
||||
All Phase 21 copy is preserved unchanged. Phase 22 additions:
|
||||
|
||||
| Element | Copy | Notes |
|
||||
|---------|------|-------|
|
||||
| Agent selector placeholder | "Select agent" | `text-muted-foreground`; shown when no agent assigned |
|
||||
| Agent selector no-options | "No agents configured" | Disabled state |
|
||||
| Stop button label | "Stop generating" | `variant="outline" size="sm"`; icon `Square` precedes label |
|
||||
| Edit message button aria-label | "Edit message" | Icon-only; `Pencil` icon |
|
||||
| Retry message button aria-label | "Retry response" | Icon-only; `RefreshCw` icon |
|
||||
| Inline edit save button | "Save" | `variant="default" size="sm"` |
|
||||
| Inline edit cancel button | "Cancel" | `variant="ghost" size="sm"` |
|
||||
| Streaming interrupted error | "Response interrupted." | Inline `text-destructive`; followed by Retry link |
|
||||
| Retry inline link | "Retry" | `text-primary underline cursor-pointer` — not a `<Button>` |
|
||||
| Stopped message suffix | "[stopped]" | `text-muted-foreground text-[11px] ml-1`; appended to partial message |
|
||||
| Slash command: `/brainstorm` description | "Route to Brainstormer" | Second line in slash popover item |
|
||||
| Slash command: `/ask-pm` description | "Route to PM" | Second line |
|
||||
| Slash command: `/ask-engineer` description | "Route to Engineer" | Second line |
|
||||
| Slash command: `/task` description | "Create a task" | Second line |
|
||||
| Slash command: `/search` description | "Search conversations" | Second line; greyed out — "Coming soon" suffix in muted text |
|
||||
| Jump to bottom button aria-label | "Scroll to latest message" | Icon-only; `ArrowDown` icon |
|
||||
| Input placeholder (streaming) | "Waiting for response..." | Replaces "Message your agent..." while `isStreaming: true`; textarea is disabled |
|
||||
|
||||
**Tone:** Direct, functional, no corporate language. Consistent with Phase 21.
|
||||
|
||||
---
|
||||
|
||||
## States and Loading
|
||||
|
||||
| Component | Loading state | Empty state | Error state | Streaming state |
|
||||
|-----------|--------------|-------------|-------------|-----------------|
|
||||
| `ChatAgentSelector` | Skeleton pill 80px wide | "No agents configured" | "Could not load agents." — popover body | n/a |
|
||||
| `ChatMessage` (assistant) | n/a | n/a | Inline "Response interrupted. Retry" | Cursor blink + streaming text accumulation |
|
||||
| `ChatMessageList` | 3x Skeleton rows (h-16 variable) | Inherited from Phase 21 | Inherited from Phase 21 | Auto-scroll; Stop button visible |
|
||||
| `ChatInput` | n/a | n/a | Inherited from Phase 21 | `disabled`; placeholder "Waiting for response..." |
|
||||
| `ChatSlashCommandPopover` | n/a | n/a | n/a | Hidden while streaming |
|
||||
| `ChatMentionPopover` | Skeleton 3 rows | "No agents found" | n/a | Hidden while streaming |
|
||||
|
||||
**Optimistic updates:**
|
||||
- Agent selector: updates conversation's `agentId` optimistically; reverts on API error with toast "Could not update agent. Try again."
|
||||
- Stop: button removed immediately; error shown inline if SSE close fails silently
|
||||
- Edit/Retry: new streaming message renders immediately; old messages removed optimistically
|
||||
|
||||
---
|
||||
|
||||
## Theme Integration Contract
|
||||
|
||||
Phase 22 extends Phase 21's zero-new-plumbing approach:
|
||||
|
||||
- Agent role colors use `text-{color}-600 dark:text-{color}-400` Tailwind classes — resolve correctly in all three themes via the existing `.dark` class on `<html>`
|
||||
- Tokyo Night: `.dark` class is present (as established in Phase 21 ThemeContext) — dark variants apply correctly
|
||||
- Streaming cursor: `bg-foreground/70` uses CSS variable — resolves to near-white in dark themes, near-black in Catppuccin Latte
|
||||
- Slash command popover and mention popover: use shadcn `<Popover>` + `<Command>` which inherit `--popover`, `--popover-foreground` CSS variables — no manual theme wiring needed
|
||||
|
||||
**THEME-03 checklist:**
|
||||
- Agent icon colors declared per-role with `dark:` variants — all three themes tested
|
||||
- Streaming indicator uses `bg-cyan-400` (same across themes — sufficient contrast on all three backgrounds)
|
||||
- No hardcoded hex colors introduced in Phase 22
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
Inherits all Phase 21 accessibility contracts. Phase 22 additions:
|
||||
|
||||
| Concern | Requirement |
|
||||
|---------|-------------|
|
||||
| Agent selector | `aria-label="Active agent"` on trigger; dropdown items have `role="option"` |
|
||||
| Streaming message | `aria-live="polite"` on `ChatMessageList` (established in Phase 21) handles streamed token announcements without modification |
|
||||
| Stop button | `aria-label="Stop generating response"` |
|
||||
| Streaming cursor | `aria-hidden="true"` — decorative only |
|
||||
| Edit textarea | `aria-label="Edit your message"` on inline textarea |
|
||||
| Retry button | `aria-label="Retry response"` |
|
||||
| Slash popover | `role="listbox"` on items; announce open state with `aria-expanded` |
|
||||
| Mention popover | `role="listbox"` on items; announce open state with `aria-expanded` |
|
||||
| Jump to bottom | `aria-label="Scroll to latest message"` |
|
||||
| Edit save/cancel | Standard button labels (no icon-only ambiguity) |
|
||||
| Focus after stop | Focus returns to `ChatInput` textarea after stop action |
|
||||
| Focus after retry | No focus change — user may be reading the regenerated message |
|
||||
|
||||
---
|
||||
|
||||
## Animation and Motion
|
||||
|
||||
Inherits Phase 21 animation contract. Phase 22 additions:
|
||||
|
||||
| Element | Animation | Duration | Easing | CSS |
|
||||
|---------|-----------|----------|--------|-----|
|
||||
| Streaming cursor | `animate-cursor-blink` keyframe | 800ms | step-start | See below |
|
||||
| Stop button appear | `animate-in fade-in slide-in-from-bottom-1` | 150ms | `ease-out` — shadcn default |
|
||||
| Stop button disappear | `animate-out fade-out slide-out-to-bottom-1` | 100ms | `ease-in` |
|
||||
| Slash popover open | `animate-in fade-in zoom-in-95` | 100ms | `ease-out` — shadcn Popover default |
|
||||
| Mention popover open | Same as slash popover | 100ms | `ease-out` |
|
||||
| Jump to bottom button | `animate-in fade-in slide-in-from-bottom-2` | 150ms | `ease-out` |
|
||||
| Streaming indicator dot | `animate-pulse` (Tailwind) | continuous | — |
|
||||
| Edit mode transition | No animation — immediate swap; avoids layout shift | — | — |
|
||||
|
||||
**`animate-cursor-blink` keyframe (add to `index.css`):**
|
||||
```css
|
||||
@keyframes cursor-blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
.animate-cursor-blink {
|
||||
animation: cursor-blink 800ms step-start infinite;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.animate-cursor-blink {
|
||||
animation: none;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Reduced motion:** All Phase 22 entrance/exit animations must respect `prefers-reduced-motion`. Use `motion-safe:` Tailwind prefix or `@media` guards matching the Phase 21 pattern in `index.css`.
|
||||
|
||||
---
|
||||
|
||||
## Performance Contract
|
||||
|
||||
| Requirement | Target | Implementation |
|
||||
|-------------|--------|----------------|
|
||||
| PERF-02 | First token ≤ 100ms server-to-UI | SSE connection opened before server begins generation; client-side: no React re-render batching delay — use `startTransition` for text accumulation to keep input responsive |
|
||||
| PERF-03 | 1,000+ messages no jank | `@tanstack/react-virtual` with dynamic measurement; `overscan: 5`; avoid re-rendering all messages on token append — only the active streaming message re-renders |
|
||||
|
||||
**Token accumulation pattern:**
|
||||
- Maintain a `streamingContent` string in `useStreamingChat` local state
|
||||
- Append tokens via `setState(prev => prev + token)` — single string concat, not array push
|
||||
- When stream ends, commit full message to React Query cache via `queryClient.setQueryData`; clear local `streamingContent`
|
||||
|
||||
---
|
||||
|
||||
## Registry Safety
|
||||
|
||||
| Registry | Blocks Used | Safety Gate |
|
||||
|----------|-------------|-------------|
|
||||
| shadcn official | All existing Phase 21 components (already installed) | not required |
|
||||
| npm (non-registry) | `@tanstack/react-virtual` — standard npm package install via `pnpm add` | not applicable (not a shadcn registry block) |
|
||||
| Third-party | none | not applicable |
|
||||
|
||||
**No third-party shadcn registries used in Phase 22.**
|
||||
|
||||
---
|
||||
|
||||
## Checker Sign-Off
|
||||
|
||||
- [ ] Dimension 1 Copywriting: PASS
|
||||
- [ ] Dimension 2 Visuals: PASS
|
||||
- [ ] Dimension 3 Color: PASS
|
||||
- [ ] Dimension 4 Typography: PASS
|
||||
- [ ] Dimension 5 Spacing: PASS
|
||||
- [ ] Dimension 6 Registry Safety: PASS
|
||||
|
||||
**Approval:** pending
|
||||
Loading…
Add table
Reference in a new issue