[nexus] docs(21): fix UI-SPEC checker issues — copywriting, typography, spacing

- Replace "Cancel" with "Keep conversation" in delete confirmation (Dimension 1)
- Drop font-weight 500 (medium); consolidate to 400 + 600 only (Dimension 4)
- Fix ChatInput padding from py-2.5 (10px) to py-2 (8px) (Dimension 5)
- Name 12px as sm+ token with justification for conversation list item padding (Dimension 5)
- Add noun suffix to all dropdown action labels ("Archive conversation", etc.)
- Add focal point statement to contract

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Mikkel Georgsen 2026-04-01 12:40:17 +02:00
parent 181af7b00d
commit 08d01f89c9

View file

@ -5,6 +5,7 @@ status: draft
shadcn_initialized: true
preset: new-york / neutral / cssVariables
created: 2026-04-01
revised: 2026-04-01
---
# Phase 21 — UI Design Contract
@ -33,6 +34,12 @@ None required — all necessary primitives are available. `Textarea` covers `Cha
---
## Focal Point
The primary focal point of this phase is the **chat input** at the bottom of the ChatPanel. When the panel opens, focus moves immediately to the `ChatInput` textarea. All surrounding elements (conversation list, message thread, header) are secondary supporting surfaces.
---
## Spacing Scale
Declared values (multiples of 4 only). Source: 8-point scale, confirmed via existing `p-4 md:p-6` usage in `Layout.tsx`.
@ -41,15 +48,16 @@ Declared values (multiples of 4 only). Source: 8-point scale, confirmed via exis
|-------|-------|-------|
| 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 |
| sm+ | 12px | Conversation list item vertical padding (`py-3`) — named exception; follows existing `EntityRow` pattern; sits between sm and md |
| md | 16px | Default element padding (`p-4`), chat panel header padding |
| lg | 24px | Section padding on desktop (`p-6`) |
| 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:
- `sm+` (12px) is a named token for conversation list item vertical padding. Justification: follows the existing `EntityRow` pattern throughout the codebase. It is not a one-off magic number — it is a deliberate in-between value that keeps list items readable without wasting vertical space. Only use `sm+` for list item vertical padding.
- 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
---
@ -58,19 +66,22 @@ Exceptions:
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`.
Two weights only: regular (400) and semibold (600). Medium (500) is not used in this phase.
| 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` |
| Label | 13px | 400 (regular) | 1.4 | `text-[13px]` |
| 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).
- Conversation titles in the sidebar use Label (13px / regular). Do not apply `font-medium` or `font-semibold` to conversation titles — regular weight at 13px provides sufficient legibility without visual noise.
- 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).
- 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).
- Active conversation title in the sidebar: do NOT use `font-semibold` to indicate active state. Use the left-border accent indicator instead (see Color contract).
---
@ -89,7 +100,7 @@ All colors reference CSS custom properties already declared for all three themes
| 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
1. The "Send message" / "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
@ -118,10 +129,10 @@ Components to build for this phase. Each references the design token contract ab
### ChatConversationList (inside ChatPanel left column, width: 240px)
- Background: `bg-sidebar` (secondary surface)
- Item height: 48px (`py-3 px-3`)
- Item height: 48px (`py-3 px-3`) — uses `sm+` (12px) vertical padding token
- Active item: `border-l-2 border-primary bg-sidebar-accent`
- Hover item: `bg-sidebar-accent/50`
- Title: Label (13px / medium), truncated with `truncate`
- Title: Label (13px / regular), truncated with `truncate`
- Timestamp: Meta (12px / muted-foreground), right-aligned
- Pin icon: 14px, `text-primary` when active
- Archive icon: 14px, `text-muted-foreground`
@ -147,7 +158,7 @@ Components to build for this phase. Each references the design token contract ab
- Component: `<Textarea>` (shadcn) with `field-sizing: content` CSS + scroll-height fallback
- Min height: 40px (one line)
- Max height: 160px (approx 6 lines), then scrolls internally
- Padding: `px-3 py-2.5`
- Padding: `px-3 py-2` (8px vertical — `sm` token)
- Background: `bg-muted` (matches `--muted` for input feel)
- Border: `border border-border focus:ring-1 focus:ring-ring`
- Placeholder: "Send a message..." — `text-muted-foreground`
@ -166,14 +177,15 @@ Actions are available via a `<DropdownMenu>` (lucide `MoreHorizontal` icon) that
| Action | Icon | Color | Confirmation |
|--------|------|-------|-------------|
| Pin / Unpin | Pin / PinOff | default foreground | None — immediate |
| Archive | Archive | default foreground | None — immediate |
| Delete | Trash2 | `text-destructive` | Inline confirm: replace DropdownMenu trigger with "Delete?" + "Yes" / "No" buttons inside the same popover |
| Pin conversation / Unpin conversation | Pin / PinOff | default foreground | None — immediate |
| Archive conversation | Archive | default foreground | None — immediate |
| Unarchive conversation | ArchiveRestore | default foreground | None — immediate |
| Delete conversation | Trash2 | `text-destructive` | Inline confirm: replace DropdownMenu trigger with popover containing "Delete conversation?" + "Delete conversation" (destructive) / "Keep conversation" (ghost) buttons |
No separate Dialog for delete — use inline confirmation popover to keep the interaction contained in the sidebar.
### Conversation title editing
- Trigger: double-click on the conversation title in the list, OR via a "Rename" item in the DropdownMenu
- Trigger: double-click on the conversation title in the list, OR via a "Rename conversation" item in the DropdownMenu
- Inline input replaces the title text (`<input>` at same 13px font size)
- Confirm: Enter or blur
- Cancel: Escape restores previous title
@ -208,16 +220,18 @@ Source: Claude's discretion (discuss phase skipped). All copy follows Nexus tone
| Error: failed to load conversations | "Couldn't load conversations. Check your connection and try again." |
| Error: failed to send message | "Message not sent. Try again." |
| Error: failed to create conversation | "Couldn't create conversation. Try again." |
| Delete confirmation (inline) | "Delete this conversation?" with "Delete" (destructive) and "Cancel" buttons |
| Delete confirmation question (inline) | "Delete this conversation?" |
| Delete confirmation — destructive button | "Delete conversation" |
| Delete confirmation — dismiss button | "Keep conversation" |
| Conversation auto-title prefix | First 60 characters of the user's first message, no prefix label |
| Archive action label | "Archive" |
| Unarchive action label | "Unarchive" |
| Pin action label | "Pin" |
| Unpin action label | "Unpin" |
| Rename action label | "Rename" |
| Archive action label | "Archive conversation" |
| Unarchive action label | "Unarchive conversation" |
| Pin action label | "Pin conversation" |
| Unpin action label | "Unpin conversation" |
| Rename action label | "Rename conversation" |
Destructive action in this phase:
- **Delete conversation**: triggered from DropdownMenu, confirmed inline (no Dialog). Confirmation text: "Delete this conversation?" Button labels: "Delete" (destructive variant) and "Cancel" (ghost variant).
- **Delete conversation**: triggered from DropdownMenu, confirmed inline (no Dialog). Confirmation text: "Delete this conversation?" Button labels: "Delete conversation" (destructive variant) and "Keep conversation" (ghost variant). "Keep conversation" is the explicit dismissal — it communicates what is preserved, not just that the action was cancelled.
---