docs(21): UI design contract for chat-foundation phase
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
8489057f05
commit
babff844e5
1 changed files with 342 additions and 0 deletions
342
.planning/phases/21-chat-foundation/21-UI-SPEC.md
Normal file
342
.planning/phases/21-chat-foundation/21-UI-SPEC.md
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
---
|
||||
phase: 21
|
||||
slug: chat-foundation
|
||||
status: draft
|
||||
shadcn_initialized: true
|
||||
preset: new-york / neutral / css-variables
|
||||
created: 2026-04-01
|
||||
---
|
||||
|
||||
# Phase 21 — UI Design Contract
|
||||
|
||||
> Visual and interaction contract for Phase 21: Chat Foundation.
|
||||
> Generated by gsd-ui-researcher. Verified by gsd-ui-checker.
|
||||
|
||||
---
|
||||
|
||||
## Design System
|
||||
|
||||
| Property | Value | Source |
|
||||
|----------|-------|--------|
|
||||
| Tool | shadcn/ui | `ui/components.json` |
|
||||
| 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` + RESEARCH.md |
|
||||
| Font | System UI (`font-sans` from Tailwind default, 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:**
|
||||
- `MarkdownBody` — extend with `rehype-highlight` for syntax highlighting (CHAT-02/03)
|
||||
- `PanelContext` pattern — mirror for `ChatPanelContext` with `nexus:chat-panel-open` key
|
||||
- `PropertiesPanel` — reference layout; chat drawer must be a sibling, not a replacement
|
||||
- `ScrollArea` — use for conversation list and message thread scroll regions
|
||||
|
||||
---
|
||||
|
||||
## Layout Contract
|
||||
|
||||
### Chat Drawer Position
|
||||
|
||||
The `ChatPanel` is a fixed-width right-side drawer, positioned as a sibling of `<PropertiesPanel>` within the existing `flex` row in `Layout.tsx`.
|
||||
|
||||
```
|
||||
[ CompanyRail ] [ Sidebar ] [ <main> ] [ ChatPanel ] [ PropertiesPanel ]
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Width when open: 380px (matching `RESEARCH.md` recommendation; same order-of-magnitude as `PropertiesPanel` at 320px)
|
||||
- Width when closed: 0px (`overflow-hidden`)
|
||||
- Transition: `transition-[width] duration-100 ease-out` — matches sidebar and PropertiesPanel animations exactly
|
||||
- When `ChatPanel` opens, `PropertiesPanel` closes (set `panelVisible: false` via `setPanelVisible`). They do not coexist — the combined 700px would crowd a default 1280px viewport.
|
||||
- Desktop only initially: `hidden md:flex` — same guard as `PropertiesPanel`
|
||||
- `localStorage` key for open state: `nexus:chat-panel-open`
|
||||
|
||||
### Chat Drawer Internal Layout
|
||||
|
||||
```
|
||||
[ Header: title + close button ] — border-b border-border, px-4 py-2
|
||||
[ ChatConversationList ] — fixed width left column, 240px, border-r
|
||||
[ ChatMessageList ] — flex-1, overflow-auto via ScrollArea
|
||||
[ ChatInput ] — sticky bottom, border-t border-border
|
||||
```
|
||||
|
||||
Alternatively, the drawer can be a single-column panel toggling between conversation list view and message thread view (simpler for Phase 21; agent streaming in Phase 22 makes the two-column layout more valuable). **Use two-column layout within the 380px drawer**: left column 160px (conversation list), right column flex-1 (message thread + input).
|
||||
|
||||
### Chat Panel Trigger
|
||||
|
||||
Add a `MessageSquare` icon button to the top-right control area of `Layout.tsx` (or to `BreadcrumbBar`), using the same `Button variant="ghost" size="icon-sm"` pattern as the existing theme toggle and settings buttons.
|
||||
|
||||
---
|
||||
|
||||
## Spacing Scale
|
||||
|
||||
Declared values (all multiples of 4):
|
||||
|
||||
| Token | Value | Usage |
|
||||
|-------|-------|-------|
|
||||
| xs | 4px | Icon gaps (`gap-1`), inline chip padding |
|
||||
| sm | 8px | Compact element spacing, badge padding (`px-2 py-1`) |
|
||||
| md | 16px | Default element spacing, panel padding (`p-4`) |
|
||||
| lg | 24px | Section padding (`px-6`), message bubble vertical rhythm |
|
||||
| xl | 32px | Layout gaps between major zones |
|
||||
| 2xl | 48px | Not used in Phase 21 (no full-page sections) |
|
||||
| 3xl | 64px | Not used in Phase 21 |
|
||||
|
||||
**Exceptions:**
|
||||
- Chat input area: `px-3 py-2` (12px/8px) — matches existing BreadcrumbBar footer pattern
|
||||
- Message bubble padding: `px-3 py-2` for compact density
|
||||
- Code block padding: `padding: 0.5rem 0.65rem` — matches existing `.paperclip-markdown pre` rule in `index.css`
|
||||
- Touch targets: minimum 44px height on `@media (pointer: coarse)` — already enforced globally in `index.css`
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
All sizes and weights are drawn from the existing `.paperclip-markdown` and `index.css` typographic system. No new type tokens are introduced.
|
||||
|
||||
| Role | Size | Weight | Line Height | Usage |
|
||||
|------|------|--------|-------------|-------|
|
||||
| Body / message text | 15px (0.9375rem) | 400 | 1.6 | Chat message prose (`paperclip-markdown` font-size: 0.9375rem; line-height: 1.6) |
|
||||
| Label / UI chrome | 13px (0.8125rem) | 400 | 1.4 | Conversation list titles, timestamps, sidebar nav (`text-[13px]` already used in Layout) |
|
||||
| Subheading | 14px (0.875rem) | 500 | 1.4 | Code block language labels, drawer section headers |
|
||||
| Heading (in markdown) | 20px (1.25rem) | 600 | 1.3 | `## ` headings inside agent responses (`.paperclip-markdown h2`) |
|
||||
|
||||
**Weights used:** 400 (regular) and 600 (semibold). No additional weights.
|
||||
|
||||
**Monospace font (code blocks):** `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace` — matches existing `.paperclip-markdown code` declaration.
|
||||
|
||||
---
|
||||
|
||||
## Color
|
||||
|
||||
The design uses the existing Nexus CSS variable system across all three themes. No new color values are introduced.
|
||||
|
||||
| Role | Catppuccin Mocha (.dark) | Tokyo Night (.theme-tokyo-night.dark) | Catppuccin Latte (:root) | Usage |
|
||||
|------|--------------------------|---------------------------------------|--------------------------|-------|
|
||||
| Dominant (60%) | `--background` #1e1e2e | `--background` #1a1b26 | `--background` #eff1f5 | Chat panel background, message list |
|
||||
| Secondary (30%) | `--card` #181825 / `--sidebar` #181825 | `--card` #16161e / `--sidebar` #16161e | `--card` #e6e9ef | Conversation list pane, code block background, input area |
|
||||
| Accent (10%) | `--accent` #45475a | `--accent` #3b4261 | `--accent` #bcc0cc | Hovered conversation row, active conversation highlight |
|
||||
| Primary | `--primary` #89b4fa | `--primary` #7aa2f7 | `--primary` #1e66f5 | Send button, focus rings, active conversation indicator |
|
||||
| Destructive | `--destructive` #f38ba8 | `--destructive` #f7768e | `--destructive` #d20f39 | Delete conversation confirmation only |
|
||||
| Muted text | `--muted-foreground` #6c7086 | `--muted-foreground` #565f89 | `--muted-foreground` #9ca0b0 | Timestamps, conversation preview text, empty state body |
|
||||
|
||||
**Accent reserved for:**
|
||||
1. Hovered conversation list row (`hover:bg-accent`)
|
||||
2. Currently active/selected conversation row (`bg-accent/60`)
|
||||
3. Code block toolbar background on hover (existing `index.css` pattern)
|
||||
4. Input area focus-within ring (`focus-within:ring-1 ring-ring`)
|
||||
|
||||
**Accent is NOT used for:** send button, message bubbles, badges, or any primary action affordance.
|
||||
|
||||
### Code Block Syntax Highlighting — Theme Mapping
|
||||
|
||||
| Nexus Theme | highlight.js CSS Theme | `THEME_META[theme].dark` |
|
||||
|-------------|----------------------|--------------------------|
|
||||
| catppuccin-mocha | `highlight.js/styles/base16/catppuccin.css` (dark variant) | true |
|
||||
| tokyo-night | `highlight.js/styles/tokyo-night-dark.css` | true |
|
||||
| catppuccin-latte | `highlight.js/styles/base16/catppuccin.css` (light variant) | false |
|
||||
|
||||
Theme CSS must be loaded via a single CSS file that gates `.hljs` color overrides on `.dark` and `.theme-tokyo-night` — never import all three theme CSS files simultaneously. Override approach: use CSS custom properties per theme class, matching the existing pattern in `index.css`.
|
||||
|
||||
**Existing code block colors (hardcoded in index.css, keep consistent):**
|
||||
- Dark code block background: `#1e1e2e` (Catppuccin Mocha base)
|
||||
- Dark code block text: `#cdd6f4`
|
||||
- These hardcoded values apply for Mocha; hljs theme overrides layer on top for Tokyo Night
|
||||
|
||||
---
|
||||
|
||||
## Component Inventory
|
||||
|
||||
Components to build in Phase 21:
|
||||
|
||||
| Component | shadcn base | Notes |
|
||||
|-----------|-------------|-------|
|
||||
| `ChatPanelContext.tsx` | none | localStorage persistence, mirrors `PanelContext` pattern |
|
||||
| `ChatPanel.tsx` | `ScrollArea`, `Button` | Right drawer shell; two-column (list + thread) |
|
||||
| `ChatConversationList.tsx` | `ScrollArea`, `skeleton`, `button` | Infinite scroll via `useInfiniteQuery`; skeleton on load |
|
||||
| `ChatConversationItem.tsx` | `dropdown-menu`, `button` | Row with title, preview, timestamp; hover reveals action menu |
|
||||
| `ChatMessageList.tsx` | `ScrollArea` | Message thread; virtualization deferred to Phase 22 |
|
||||
| `ChatMessage.tsx` | none | Wrapper for user vs assistant messages; role-based alignment |
|
||||
| `ChatMarkdownMessage.tsx` | none | Extends `MarkdownBody` with `rehype-highlight`; adds copy button on `pre` |
|
||||
| `ChatInput.tsx` | `textarea`, `button` | Auto-resize textarea; keyboard shortcuts |
|
||||
| `ChatCodeBlock.tsx` | `button`, `tooltip` | Wraps `pre`; adds language label + copy button |
|
||||
|
||||
**Icons (lucide-react):**
|
||||
- `MessageSquare` — chat panel trigger button in Layout
|
||||
- `Plus` — new conversation button
|
||||
- `Pin` / `PinOff` — pin/unpin conversation (dropdown action)
|
||||
- `Archive` — archive conversation (dropdown action)
|
||||
- `Trash2` — delete conversation (dropdown action, triggers confirmation)
|
||||
- `Copy` — copy code block content
|
||||
- `Check` — copy success state (shown 1500ms, then reverts to Copy)
|
||||
- `Send` — send message button
|
||||
- `X` — close chat panel, dismiss input
|
||||
|
||||
---
|
||||
|
||||
## Interaction Contract
|
||||
|
||||
### Conversation List
|
||||
|
||||
| Interaction | Behavior |
|
||||
|-------------|---------|
|
||||
| Click conversation row | Load that conversation's messages into the thread pane |
|
||||
| Hover conversation row | Reveal `...` (MoreHorizontal) icon button at row end |
|
||||
| Click `...` | Open `dropdown-menu` with: Rename, Pin/Unpin, Archive, Delete |
|
||||
| Click `+` New conversation | Create new conversation via POST, optimistic insert at top of list |
|
||||
| Scroll to bottom of list | Trigger `fetchNextPage` for infinite scroll (intersection observer on last item) |
|
||||
| Long-press (mobile) | Same as hover — reveal action menu |
|
||||
|
||||
### Conversation CRUD States
|
||||
|
||||
| Action | UI Response |
|
||||
|--------|------------|
|
||||
| Create conversation | Optimistic insert at top of list; title shows "New Conversation" placeholder until first message auto-sets it |
|
||||
| Rename (title edit) | Inline contenteditable or input field replacing the title text; blur or Enter confirms; Escape cancels |
|
||||
| Pin conversation | Row moves to top of list within a "Pinned" group (visually separated); pin icon visible on row |
|
||||
| Archive conversation | Row removed from default list; no undo in Phase 21 |
|
||||
| Delete conversation | Confirmation dialog (shadcn `dialog`): "Delete conversation? This cannot be undone." with "Delete" (destructive) + "Cancel" buttons |
|
||||
|
||||
### Message Thread
|
||||
|
||||
| Interaction | Behavior |
|
||||
|-------------|---------|
|
||||
| User message | Right-aligned bubble, `bg-secondary` background, `text-secondary-foreground` |
|
||||
| Assistant message | Left-aligned, no bubble background, full width, `ChatMarkdownMessage` renders content |
|
||||
| New message sent | Textarea clears; optimistic message appends at bottom; thread scrolls to bottom |
|
||||
| Thread empty state | Centered message: "No messages yet" + instruction text (see Copywriting) |
|
||||
|
||||
### Chat Input
|
||||
|
||||
| Interaction | Behavior |
|
||||
|-------------|---------|
|
||||
| Type in textarea | Auto-resizes: `field-sizing: content` (fallback: scrollHeight calculation); max-height 160px before scrolling |
|
||||
| Enter (no modifier) | Submit message via `chatApi.postMessage`; clears textarea |
|
||||
| Shift+Enter | Insert newline; do not submit |
|
||||
| Escape | Clear textarea content if non-empty; if already empty, do nothing |
|
||||
| Send button click | Same as Enter; button is disabled when textarea is empty |
|
||||
|
||||
### Code Block Copy Button
|
||||
|
||||
| Interaction | Behavior |
|
||||
|-------------|---------|
|
||||
| Hover code block | Copy button (`Copy` icon) appears in top-right corner of `pre` block |
|
||||
| Click copy | `navigator.clipboard.writeText(code)` executes; icon changes to `Check` for 1500ms; then reverts to `Copy` |
|
||||
| Copy failure | Log to console; no user-visible error (local trusted mode) |
|
||||
|
||||
### Keyboard Shortcuts (INPUT-07)
|
||||
|
||||
| Shortcut | Scope | Action |
|
||||
|----------|-------|--------|
|
||||
| Enter | Chat input focused | Send message |
|
||||
| Shift+Enter | Chat input focused | Insert newline |
|
||||
| Escape | Chat input focused | Clear input |
|
||||
| Cmd+K | Global | Opens search (Phase 24 — wire shortcut now, handler is no-op in Phase 21) |
|
||||
|
||||
---
|
||||
|
||||
## Copywriting Contract
|
||||
|
||||
| Element | Copy | Notes |
|
||||
|---------|------|-------|
|
||||
| Chat panel toggle button aria-label | "Open chat" / "Close chat" | Toggle based on `chatOpen` state |
|
||||
| New conversation button | "New conversation" | Tooltip + aria-label |
|
||||
| Conversation list empty state heading | "No conversations yet" | Shown when list is empty |
|
||||
| Conversation list empty state body | "Start a conversation to get help from your agents." | Directs user toward action |
|
||||
| Message thread empty state | "Send a message to start this conversation." | Shown when conversation exists but has no messages |
|
||||
| Delete conversation dialog title | "Delete conversation?" | shadcn Dialog title |
|
||||
| Delete conversation dialog body | "This conversation and all its messages will be permanently deleted." | Confirms scope |
|
||||
| Delete confirmation button | "Delete" | `variant="destructive"` |
|
||||
| Delete cancel button | "Cancel" | `variant="outline"` |
|
||||
| Archive action label | "Archive" | Dropdown menu item |
|
||||
| Pin action label | "Pin" / "Unpin" | Toggle based on pinned state |
|
||||
| Rename action label | "Rename" | Dropdown menu item |
|
||||
| Input placeholder | "Message your agent..." | Textarea placeholder |
|
||||
| Send button aria-label | "Send message" | Icon-only button needs label |
|
||||
| Copy code button aria-label | "Copy code" | Before copy; reverts after success |
|
||||
| Copy code success aria-label | "Copied!" | 1500ms, then reverts |
|
||||
| Title auto-generated pattern | First 60 chars of first message, truncated with ellipsis | Server-side; shown in list immediately |
|
||||
|
||||
**Tone:** Direct, functional, no corporate language. No exclamation marks except "Copied!" (which is a status, not marketing).
|
||||
|
||||
---
|
||||
|
||||
## States and Loading
|
||||
|
||||
| Component | Loading state | Empty state | Error state |
|
||||
|-----------|--------------|-------------|-------------|
|
||||
| `ChatConversationList` | 5x `Skeleton` rows (h-10, w-full, rounded) | "No conversations yet" with CTA | "Could not load conversations. Refresh to try again." |
|
||||
| `ChatMessageList` | No skeleton — load is fast (persisted local server) | "Send a message to start this conversation." | "Could not load messages. Refresh to try again." |
|
||||
| `ChatInput` send | Send button shows `Loader2` spinning icon while POST is in flight; disabled state | n/a | Toast: "Message failed to send. Try again." |
|
||||
|
||||
**Optimistic updates:** New conversations and new messages insert immediately into the UI before server confirmation. On failure, remove the optimistic item and show the error toast.
|
||||
|
||||
---
|
||||
|
||||
## Theme Integration Contract
|
||||
|
||||
Requirements THEME-01 and THEME-02 require zero new plumbing — `useTheme()` + `THEME_META` + existing CSS variables handle everything.
|
||||
|
||||
**Checklist:**
|
||||
- All `ChatPanel` backgrounds use `var(--background)` and `var(--card)` — never hardcoded hex
|
||||
- Conversation list hover uses `hover:bg-accent` — resolves correctly in all three themes
|
||||
- Border colors use `border-border` — resolves correctly in all three themes
|
||||
- `ChatMarkdownMessage` passes `THEME_META[theme].dark` to `rehype-highlight` theme selection
|
||||
- Code block syntax highlight CSS: load via scoped CSS selector approach (`.dark .hljs` / `.theme-tokyo-night .hljs`), NOT via multiple `<link>` imports
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
| Concern | Requirement |
|
||||
|---------|-------------|
|
||||
| Chat panel | `<aside>` with `aria-label="Chat"` |
|
||||
| Conversation list | `<ul>` with `role="listbox"` or `<nav>`; active item has `aria-current="true"` |
|
||||
| Message thread | `<ol>` with messages as `<li>`; `aria-live="polite"` on the list for new message announcements |
|
||||
| Input submit | `<form>` wrapper with `onSubmit`; Enter key handled via form submission |
|
||||
| Icon-only buttons | All have `aria-label` — see Copywriting Contract |
|
||||
| Focus management | When chat panel opens, focus moves to the input textarea |
|
||||
| Keyboard trap | Chat panel is NOT a modal — no focus trap; users navigate freely |
|
||||
| Color contrast | All text uses existing CSS variables which are WCAG-AA compliant for their respective themes |
|
||||
|
||||
---
|
||||
|
||||
## Registry Safety
|
||||
|
||||
| Registry | Blocks Used | Safety Gate |
|
||||
|----------|-------------|-------------|
|
||||
| shadcn official | `scroll-area`, `skeleton`, `button`, `textarea`, `dialog`, `dropdown-menu`, `tooltip` (all already installed) | not required |
|
||||
| Third-party | none | not applicable |
|
||||
|
||||
**No third-party registries used in Phase 21.** All shadcn components are already installed in `ui/src/components/ui/`. The one new package (`rehype-highlight`) is an npm package installed via `pnpm`, not a shadcn registry block.
|
||||
|
||||
---
|
||||
|
||||
## Animation and Motion
|
||||
|
||||
| Element | Animation | Duration | Easing |
|
||||
|---------|-----------|----------|--------|
|
||||
| Chat panel open/close | `transition-[width]` | 100ms | `ease-out` — matches sidebar |
|
||||
| New message insert | No animation (Phase 21; streaming animations in Phase 22) | — | — |
|
||||
| Copy button icon swap | CSS transition `opacity` | 150ms | `ease` |
|
||||
| Conversation row highlight (new) | `activity-row-enter` keyframe (reuse existing) | 520ms | `cubic-bezier(0.16, 1, 0.3, 1)` |
|
||||
| Skeleton loading | Tailwind `animate-pulse` (shadcn default) | continuous | — |
|
||||
|
||||
**Reduced motion:** Wrap `conversation-row-enter` in `@media (prefers-reduced-motion: reduce)` — matching the existing `activity-row-enter` guard in `index.css`.
|
||||
|
||||
---
|
||||
|
||||
## 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