--- phase: 21 slug: chat-foundation status: draft shadcn_initialized: true preset: new-york / neutral / cssVariables created: 2026-04-01 --- # Phase 21 — UI Design Contract > Visual and interaction contract for frontend phases. Generated by gsd-ui-researcher, verified by gsd-ui-checker. --- ## Design System | Property | Value | |----------|-------| | Tool | shadcn (new-york style) | | Preset | new-york, baseColor: neutral, cssVariables: true | | Component library | Radix UI (via shadcn) | | Icon library | lucide-react (^0.574.0) | | Font | System UI stack (inherited from existing CSS — no custom font declared) | Source: `ui/components.json`, `ui/src/index.css` **shadcn components already installed (usable without install):** `Avatar`, `Badge`, `Button`, `Card`, `Checkbox`, `Dialog`, `DropdownMenu`, `Input`, `ScrollArea`, `Separator`, `Sheet`, `Skeleton`, `Tabs`, `Textarea`, `Tooltip` **New components for this phase (install before use):** None required — all necessary primitives are available. `Textarea` covers `ChatInput`, `ScrollArea` covers the message list and conversation list. --- ## Spacing Scale Declared values (multiples of 4 only). Source: 8-point scale, confirmed via existing `p-4 md:p-6` usage in `Layout.tsx`. | Token | Value | Usage | |-------|-------|-------| | xs | 4px | Icon gaps (`gap-1`), inline icon + label spacing | | sm | 8px | Compact padding inside list items (`px-2 py-1`), badge padding | | md | 16px | Default element padding (`p-4`), conversation list item padding | | lg | 24px | Section padding on desktop (`p-6`), chat panel header padding | | xl | 32px | Gap between major UI zones | | 2xl | 48px | Empty-state vertical padding (`py-12`) | | 3xl | 64px | Page-level section breaks (not applicable in panel context) | Exceptions: - Touch targets on coarse-pointer devices: `min-height: 44px` (already enforced globally in `index.css` `@media (pointer: coarse)`) - Sidebar conversation list item: 12px vertical padding (`py-3`) — between sm and md — acceptable as it follows existing `EntityRow` pattern - Chat input bottom padding: `pb-[calc(env(safe-area-inset-bottom)+16px)]` on mobile to clear the home indicator --- ## Typography All sizes use Tailwind utility classes mapped to the project's system-UI font stack. Source: observed usage in `Layout.tsx`, `EmptyState.tsx`, `MarkdownBody.tsx`. | Role | Size | Weight | Line Height | Tailwind Class | |------|------|--------|-------------|----------------| | Body | 14px | 400 (regular) | 1.5 | `text-sm` | | Label | 13px | 500 (medium) | 1.4 | `text-[13px] font-medium` | | Heading | 16px | 600 (semibold) | 1.25 | `text-base font-semibold` | | Meta / Timestamp | 12px | 400 (regular) | 1.4 | `text-xs text-muted-foreground` | Rules: - Conversation titles in the sidebar use Label (13px / medium). - Agent message content renders inside `MarkdownBody` which applies `prose prose-sm` — do not override prose typography for markdown content. - The chat input `Textarea` uses body (14px / regular). - Section headers inside the chat panel (e.g. "Conversations") use Heading (16px / semibold). - Timestamps and message count badges use Meta (12px / regular / muted-foreground). --- ## Color All colors reference CSS custom properties already declared for all three themes (Catppuccin Mocha, Tokyo Night, Catppuccin Latte) in `ui/src/index.css`. Never hard-code hex values. Source: `ui/src/index.css`. | Role | CSS Variable | 60/30/10 | Usage | |------|-------------|----------|-------| | Dominant surface | `--background` | 60% | Chat panel background, message list background, main layout background | | Secondary surface | `--card` / `--sidebar` | 30% | Conversation list sidebar background (`--sidebar`), individual conversation list item hover state, code block container | | Accent | `--primary` | 10% | Send button, active conversation highlight border-left indicator, conversation pin icon (filled) | | Muted | `--muted` | — | Input background, empty state icon container | | Muted foreground | `--muted-foreground` | — | Placeholder text in chat input, timestamps, secondary labels | | Destructive | `--destructive` | — | Delete conversation action only | | Border | `--border` | — | Dividers between message groups, chat panel border, input border | Accent (`--primary`) is reserved for exactly these elements: 1. The "Send" / "New conversation" primary action button background 2. The active conversation in the sidebar (left-border indicator: `border-l-2 border-primary`) 3. Filled pin icon on pinned conversations The accent must NOT be applied to: conversation list hover states, timestamps, agent message bubbles, or any decorative elements. Theme-specific code highlighting: - `catppuccin-mocha` (dark): use highlight.js theme `base16/catppuccin` (dark variant via `.dark` class) - `tokyo-night` (dark): use highlight.js theme `tokyo-night-dark` (via `.theme-tokyo-night.dark` class) - `catppuccin-latte` (light): use highlight.js theme `base16/catppuccin` (light variant via `:root` / light mode class) Implementation: Load one CSS file with per-theme class overrides for `.hljs` variables rather than three separate `` imports. See RESEARCH.md Pitfall 1. --- ## Component Inventory Components to build for this phase. Each references the design token contract above. ### ChatPanel (right-side drawer) - Width: 380px open, 0px closed - Transition: `transition-[width] duration-100 ease-out` (same as sidebar in `Layout.tsx`) - Background: `bg-background` (dominant surface) - Position: sibling of `` in the Layout flex row; opening ChatPanel closes PropertiesPanel (see RESEARCH.md Open Question 1 resolution) - Header: 48px tall, `border-b border-border`, contains "Chat" title (Heading) + "New conversation" icon button (Plus from lucide) + close icon button - localStorage key: `nexus:chat-panel-open` (use `nexus:` prefix, not `paperclip:`) ### ChatConversationList (inside ChatPanel left column, width: 240px) - Background: `bg-sidebar` (secondary surface) - Item height: 48px (`py-3 px-3`) - Active item: `border-l-2 border-primary bg-sidebar-accent` - Hover item: `bg-sidebar-accent/50` - Title: Label (13px / medium), truncated with `truncate` - Timestamp: Meta (12px / muted-foreground), right-aligned - Pin icon: 14px, `text-primary` when active - Archive icon: 14px, `text-muted-foreground` - Skeleton loader: 3 skeleton items (``) while `isLoading` - Infinite scroll sentinel: `
` at bottom of list ### ChatMessageList (main chat area) - Background: `bg-background` - Padding: `p-4` with `gap-4` between messages - User message: right-aligned, `bg-secondary text-secondary-foreground`, `rounded-none` (matches `--radius: 0` global setting), `max-w-[75%]`, padding `px-4 py-2` - Assistant message: left-aligned, no background (transparent), `max-w-[85%]` - Message timestamp: Meta, visible on hover only (`opacity-0 group-hover:opacity-100 transition-opacity`) - Agent label on assistant messages: not in Phase 21 (agent identity lands in Phase 22) — omit avatar/name row entirely ### ChatMarkdownMessage (assistant message renderer) - Extends `MarkdownBody` with `rehype-highlight` added to `rehypePlugins` - Code block container: `bg-card border border-border`, `relative` positioning for copy button - Code block language label: Meta (12px), `text-muted-foreground`, top-left inside block (`absolute top-2 left-3`) - Copy button: icon-only (Copy from lucide, 14px), `absolute top-1.5 right-1.5`, `variant="ghost" size="icon-sm"`, transitions to Check icon for 2 seconds on success - Copy button accessible label: `aria-label="Copy code"` ### ChatInput (bottom of ChatPanel) - Component: `