diff --git a/.planning/phases/21-chat-foundation/21-UI-SPEC.md b/.planning/phases/21-chat-foundation/21-UI-SPEC.md new file mode 100644 index 00000000..b5e95472 --- /dev/null +++ b/.planning/phases/21-chat-foundation/21-UI-SPEC.md @@ -0,0 +1,273 @@ +--- +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: `