[nexus] 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
87a140a409
commit
8e1ce40a4a
1 changed files with 376 additions and 0 deletions
376
.planning/phases/22-agent-streaming/22-UI-SPEC.md
Normal file
376
.planning/phases/22-agent-streaming/22-UI-SPEC.md
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
---
|
||||
phase: 22
|
||||
slug: agent-streaming
|
||||
status: draft
|
||||
shadcn_initialized: true
|
||||
preset: new-york / neutral / cssVariables
|
||||
created: 2026-04-01
|
||||
revised: 2026-04-01
|
||||
---
|
||||
|
||||
# Phase 22 — 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) — carried forward from Phase 21 |
|
||||
| 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` (unchanged from Phase 21)
|
||||
|
||||
**shadcn components already installed (usable without install):**
|
||||
`Avatar`, `Badge`, `Button`, `Card`, `Checkbox`, `Command`, `Dialog`, `DropdownMenu`, `Input`, `Popover`, `ScrollArea`, `Select`, `Separator`, `Sheet`, `Skeleton`, `Tabs`, `Textarea`, `Tooltip`
|
||||
|
||||
**New shadcn components for this phase (install before use):**
|
||||
None required — `Select` and `DropdownMenu` cover the AgentSelector. `Avatar` covers the agent badge avatar slot.
|
||||
|
||||
**New npm packages for this phase (not shadcn registry):**
|
||||
- `virtua` ^0.49.0 — virtualized list for ChatMessageList (PERF-03). Install: `pnpm --filter @paperclipai/ui add virtua`
|
||||
- Server: `openai` or `ai` SDK — LLM streaming. See RESEARCH.md for provider resolution. Not a UI registry concern.
|
||||
|
||||
---
|
||||
|
||||
## Focal Point
|
||||
|
||||
The primary focal point of Phase 22 is the **streaming message in progress** — specifically the assistant message bubble that receives incoming tokens. While tokens arrive, the bubble is the only animated element on screen. The Stop button (rendered inside `ChatInput` in place of Send) is the secondary focal point during streaming.
|
||||
|
||||
---
|
||||
|
||||
## Spacing Scale
|
||||
|
||||
Carried forward from Phase 21 without change. Source: 21-UI-SPEC.md.
|
||||
|
||||
| Token | Value | Usage |
|
||||
|-------|-------|-------|
|
||||
| xs | 4px | Icon gaps (`gap-1`), inline icon + label spacing |
|
||||
| sm | 8px | Compact padding inside list items, badge padding, button icon padding |
|
||||
| sm+ | 12px | Conversation list item vertical padding (`py-3`) — named exception, follows `EntityRow` pattern |
|
||||
| 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) |
|
||||
|
||||
**Phase 22 additions:**
|
||||
|
||||
| Token | Value | Usage |
|
||||
|-------|-------|-------|
|
||||
| agent-badge-gap | 8px (sm) | Gap between agent avatar and agent name in `ChatAgentBadge` |
|
||||
| agent-avatar | 20px | Width and height of the agent avatar circle in `ChatAgentBadge` |
|
||||
| streaming-dot | 8px (sm) | Width and height of the pulsing streaming cursor dot |
|
||||
|
||||
Exceptions (unchanged from Phase 21):
|
||||
- `sm+` (12px): conversation list item vertical padding only.
|
||||
- Touch targets: `min-height: 44px` on coarse-pointer devices (global `index.css` rule).
|
||||
- Chat input bottom padding: `pb-[calc(env(safe-area-inset-bottom)+16px)]` on mobile.
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
Carried forward from Phase 21 without change. Source: 21-UI-SPEC.md.
|
||||
|
||||
Two weights only: regular (400) and semibold (600).
|
||||
|
||||
| Role | Size | Weight | Line Height | Tailwind Class |
|
||||
|------|------|--------|-------------|----------------|
|
||||
| Body | 14px | 400 (regular) | 1.5 | `text-sm` |
|
||||
| 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` |
|
||||
|
||||
**Phase 22 typography additions:**
|
||||
- Agent name in `ChatAgentBadge`: Label (13px / regular / `text-muted-foreground`). Not semibold — the name is contextual metadata, not a heading.
|
||||
- Slash command suggestion label in the command popover: Body (14px / regular).
|
||||
- Agent selector trigger label: Label (13px / regular), truncated with `truncate`.
|
||||
- Stop button label (text inside ChatInput during streaming): Body (14px / regular), no special weight. The Square icon carries the visual signal.
|
||||
|
||||
Rules (unchanged):
|
||||
- Agent message content inside `ChatMarkdownMessage` uses `prose prose-sm` — do not override.
|
||||
- The chat input `Textarea` uses Body (14px / regular).
|
||||
- Timestamps use Meta (12px / regular / muted-foreground), visible on hover only.
|
||||
|
||||
---
|
||||
|
||||
## Color
|
||||
|
||||
All colors reference CSS custom properties already declared for all three themes in `ui/src/index.css`. Never hard-code hex values. Source: 21-UI-SPEC.md + RESEARCH.md Pattern 6.
|
||||
|
||||
| Role | CSS Variable | 60/30/10 | Usage |
|
||||
|------|-------------|----------|-------|
|
||||
| Dominant surface | `--background` | 60% | Chat panel background, message list background |
|
||||
| Secondary surface | `--card` / `--sidebar` | 30% | Conversation list sidebar, code block container |
|
||||
| Accent | `--primary` | 10% | Send button, active conversation border-left, filled pin icon |
|
||||
| Muted | `--muted` | — | Input background, empty state icon container, streaming cursor background |
|
||||
| Muted foreground | `--muted-foreground` | — | Placeholder text, timestamps, agent name in badge, secondary labels |
|
||||
| Destructive | `--destructive` | — | Delete conversation action only (carried from Phase 21) |
|
||||
| Border | `--border` | — | Dividers, chat panel border, input border |
|
||||
|
||||
Accent (`--primary`) reserved for exactly these elements (Phase 22 carries Phase 21 list, no additions):
|
||||
1. The "Send message" primary action button background
|
||||
2. The active conversation in the sidebar (left-border indicator)
|
||||
3. Filled pin icon on pinned conversations
|
||||
|
||||
**Phase 22 agent colors — CSS custom properties (THEME-03):**
|
||||
|
||||
Agent role colors use `--chart-1` through `--chart-5` CSS custom properties already declared for all three themes in `ui/src/index.css`. These are the ONLY variables permitted for agent identity coloring. Implementation via `bg-[hsl(var(--chart-N))]` Tailwind utility.
|
||||
|
||||
| Agent Role | CSS Variable | Tailwind Class |
|
||||
|------------|-------------|----------------|
|
||||
| `ceo` | `--chart-1` | `bg-[hsl(var(--chart-1))]` |
|
||||
| `pm` | `--chart-2` | `bg-[hsl(var(--chart-2))]` |
|
||||
| `engineer` | `--chart-3` | `bg-[hsl(var(--chart-3))]` |
|
||||
| `general` / `generalist` | `--chart-4` | `bg-[hsl(var(--chart-4))]` |
|
||||
| `brainstormer` | `--chart-5` | `bg-[hsl(var(--chart-5))]` |
|
||||
| Unknown / fallback | `--muted` | `bg-muted` |
|
||||
|
||||
Text on all agent color backgrounds: `text-white` (sufficient contrast on all three themes per chart variable definitions). Do not use `text-foreground` — the chart variables are saturated colors and require white text.
|
||||
|
||||
**Stop button color:** The Stop button uses `variant="destructive"` from shadcn Button — it uses `--destructive` background with destructive-foreground text. This signals urgency (cancel an ongoing action) without being confused with a data-destructive action.
|
||||
|
||||
---
|
||||
|
||||
## Component Inventory
|
||||
|
||||
### Modified from Phase 21
|
||||
|
||||
#### ChatInput (modified)
|
||||
- Existing behavior fully preserved (auto-resize, Enter/Shift+Enter, Escape)
|
||||
- **Addition: Stop button during streaming**
|
||||
- When `streaming === true`: Send button is replaced by a Stop button
|
||||
- Stop button: `variant="destructive"`, icon-only (`Square` from lucide, 16px), `aria-label="Stop generation"`
|
||||
- Stop button is NEVER disabled — it must always be clickable during streaming
|
||||
- Layout: same position as Send button (flex row right of Textarea). Dimensions identical to Send button so the layout does not shift when toggling.
|
||||
- **Addition: Slash command popover**
|
||||
- Trigger: when input value starts with `/` and matches a known prefix
|
||||
- Popover appears above the input (`<Popover>` with `side="top" align="start"`)
|
||||
- Contents: list of matching commands using `<Command>` component (already installed)
|
||||
- Command item format: `/{command}` label (14px / regular) + destination agent name (13px / muted-foreground)
|
||||
- Dismiss: Escape, backspace past `/`, or clicking away
|
||||
- Max 5 items displayed; no scrolling inside the popover
|
||||
- Commands list (hardcoded):
|
||||
|
||||
| Command | Label | Agent Destination |
|
||||
|---------|-------|-------------------|
|
||||
| `/brainstorm` | Brainstorm an idea | Brainstormer |
|
||||
| `/ask-pm` | Ask the PM | PM |
|
||||
| `/ask-engineer` | Ask the Engineer | Engineer |
|
||||
| `/task` | Create a task | Engineer |
|
||||
| `/search` | Search conversations | Generalist |
|
||||
|
||||
- **Addition: @mention popover**
|
||||
- Trigger: when input value starts with `@` and has at least 1 character following
|
||||
- Same `<Popover>` + `<Command>` pattern as slash commands
|
||||
- Contents: filtered list of agent names from `useAgents()` cache
|
||||
- Item format: `AgentBadge` (20px avatar) + agent name (14px / regular)
|
||||
- Dismiss: same as slash command popover
|
||||
- **No change to aria-label, placeholder, or keyboard shortcuts**
|
||||
|
||||
#### ChatMessageList (modified)
|
||||
- **Replace flat div with `<VList>` from `virtua` (PERF-03)**
|
||||
- Drop `<ScrollArea>` wrapper — `virtua` manages scroll internally
|
||||
- `<VList style={{ flex: 1 }} ref={listRef}>` wraps all message items
|
||||
- Auto-scroll to bottom logic: `isAtBottom` state derived from VList `onScroll` callback — only auto-scroll when `isAtBottom === true`
|
||||
- Threshold: `isAtBottom = scrollOffset + clientHeight >= scrollSize - 80` (80px tolerance)
|
||||
- **Addition: streaming optimistic message**
|
||||
- When `streaming === true`: append a synthetic `ChatMessageItem` at bottom with:
|
||||
- `role: "assistant"`
|
||||
- `content: partialContent` (from `useStreamMessage` hook)
|
||||
- `isStreaming: true` prop
|
||||
- Streaming indicator: a pulsing dot (`w-2 h-2 rounded-full bg-muted animate-pulse`) appended after the last rendered markdown token
|
||||
- The streaming item has no timestamp (omit timestamp row entirely while `isStreaming`)
|
||||
- The streaming item has no action buttons (edit/retry are not shown on in-progress messages)
|
||||
- **Addition: message action buttons (edit/retry) on existing assistant messages**
|
||||
- Visible on hover only (`opacity-0 group-hover:opacity-100 transition-opacity duration-150`)
|
||||
- Position: below the message bubble, right-aligned (`flex justify-end gap-1 mt-1`)
|
||||
- Retry button: `variant="ghost" size="icon-sm"`, icon `RotateCcw` (14px), `aria-label="Retry response"`
|
||||
- Edit button: on user messages only — `variant="ghost" size="icon-sm"`, icon `Pencil` (14px), `aria-label="Edit message"`
|
||||
- While streaming is in progress (`streaming === true`): hide all action buttons on all messages (prevent conflicting actions)
|
||||
- **Addition: `ChatAgentBadge` above each assistant message bubble**
|
||||
- See new component spec below
|
||||
|
||||
#### ChatPanel (modified)
|
||||
- **Addition: `AgentSelector` in the panel header**
|
||||
- Position: right side of the header bar (flex row, `ml-auto` before close button)
|
||||
- See new component spec below
|
||||
- Header height remains 48px — AgentSelector must fit within this height
|
||||
- No other structural changes
|
||||
|
||||
### New Components for Phase 22
|
||||
|
||||
#### ChatAgentBadge
|
||||
- Rendered above each assistant message bubble in `ChatMessageList`
|
||||
- Layout: `flex items-center gap-2 mb-1` (8px gap — `sm` token; 4px margin-bottom)
|
||||
- Avatar: 20px circle (`w-5 h-5 rounded-full flex items-center justify-center`), background color from `agentRoleColorClass(agent.role)` (see Color section)
|
||||
- If agent has an `icon` value: render `AgentIcon` at 12px size inside the circle
|
||||
- If no icon: render the first letter of `agent.name` at `text-[10px] font-semibold text-white`
|
||||
- Agent name: Label (13px / regular / `text-muted-foreground`), truncated at 120px max-width
|
||||
- Source: `agent.name` and `agent.role` resolved client-side from `useAgents(companyId)` cache keyed by `agentId` on the message
|
||||
- Fallback when agent not found in cache: show `Bot` icon (lucide, 12px) + "Agent" as name, `bg-muted` background — never throw or show nothing
|
||||
|
||||
#### AgentSelector
|
||||
- Location: ChatPanel header, right side before the close button
|
||||
- Component: `<Select>` (shadcn) with a custom trigger styled to match the 48px header height
|
||||
- Trigger: compact, `h-8 px-2 py-1`, shows current agent avatar (16px, same color circle pattern as ChatAgentBadge) + agent name Label (13px / regular)
|
||||
- Dropdown items: each agent as a `<SelectItem>`, showing `ChatAgentBadge`-style avatar (16px) + agent name (14px / regular) in a flex row
|
||||
- Current agent persisted to `conversation.agentId` via `PATCH /api/conversations/:id` — this is the per-conversation default agent
|
||||
- Empty state: if no agents available, show a single disabled item "No agents configured"
|
||||
- Loading state: `<Skeleton className="h-8 w-28" />` while agents are fetching
|
||||
- Tooltip on the selector trigger: "Active agent for this conversation" (shown on hover via `<Tooltip>`)
|
||||
|
||||
---
|
||||
|
||||
## Interaction Contract
|
||||
|
||||
### Streaming lifecycle (CHAT-01, CHAT-12)
|
||||
|
||||
| State | ChatInput | ChatMessageList |
|
||||
|-------|-----------|-----------------|
|
||||
| Idle | Send button enabled; textarea enabled | No streaming indicator |
|
||||
| Sending user message | Send button disabled, `Loader2` spin icon, textarea disabled | User message appears immediately (optimistic insert) |
|
||||
| Streaming in progress | **Stop button** (`variant="destructive"`, Square icon); textarea disabled | Streaming assistant message at bottom with pulsing dot; action buttons hidden on all messages |
|
||||
| Stop clicked | Stop button transitions briefly to `Loader2` for 200ms, then reverts to Send | Streaming message remains in list with last received content (partial); pulsing dot removed; action buttons re-appear |
|
||||
| Stream complete | Reverts to Send button; textarea re-enabled and focused | Streaming indicator removed; persisted message replaces optimistic; `RotateCcw` + other action buttons appear on hover |
|
||||
| Stream error | Reverts to Send button; textarea re-enabled; error toast fires | Streaming message removed from list |
|
||||
|
||||
### Message editing inline (CHAT-10)
|
||||
|
||||
- Trigger: click `Pencil` icon on hover over any user message
|
||||
- The user message bubble text is replaced with a `<Textarea>` pre-filled with the original content
|
||||
- Min height: 40px; max height: 120px; `variant: none` (no extra border — let the bubble container provide the boundary)
|
||||
- Confirm: Enter (without Shift), or a "Regenerate" button (`variant="default"`, 14px label "Regenerate") that appears below the edit area
|
||||
- Cancel: Escape — restores original content, hides textarea
|
||||
- On submit: POST the edited content to `PUT /api/messages/:id`; this re-triggers the stream from that point; messages after the edited message are NOT removed from the UI in Phase 22 (branching is Phase 24 scope)
|
||||
- While editing: all other action buttons in the list are hidden
|
||||
|
||||
### Response regeneration (CHAT-11)
|
||||
|
||||
- Trigger: click `RotateCcw` on hover over any existing assistant message
|
||||
- Confirmation: none — immediate action
|
||||
- Behavior: re-invokes the stream route for that message position; a new streaming message appears below the existing one; the existing assistant message is retained in the list until the new stream completes, at which point the existing message is replaced with the new one in-place
|
||||
- While regenerating: Stop button active in ChatInput; all other action buttons hidden
|
||||
|
||||
### Agent selector interaction (CHAT-08)
|
||||
|
||||
- Trigger: open `AgentSelector` dropdown in ChatPanel header
|
||||
- Selection: clicking an agent item immediately calls `PATCH /api/conversations/:id` with `{ agentId }` and updates local state optimistically
|
||||
- Error: if PATCH fails, revert to previous agent selection and show a toast: "Couldn't update agent. Try again."
|
||||
- The selected agent takes effect on the next message sent — it does NOT retroactively re-label past messages
|
||||
|
||||
### Slash command and @mention routing (INPUT-05, INPUT-06)
|
||||
|
||||
- Slash command: when ChatInput contains `/ask-engineer Hello`, the command prefix is parsed before send; the message body "Hello" is sent with `targetRole: "engineer"` as a per-message agent override; the conversation's `agentId` is NOT changed
|
||||
- @mention: same per-message override behavior; `@engineer Hello` extracts `targetName: "engineer"` and resolves to the matching agent by name (case-insensitive)
|
||||
- If a command is recognized but the target agent does not exist in `useAgents()` cache: show inline toast "No engineer agent found. Message sent to active agent." and fall back to `conversation.agentId`
|
||||
- If the `/` prefix does not match any known command: send as plain text (no routing, no error)
|
||||
- Popover keyboard navigation: arrow keys move selection, Enter selects, Tab accepts, Escape dismisses
|
||||
|
||||
### Auto-scroll behavior during streaming (PERF-03)
|
||||
|
||||
- When `isAtBottom === true` at the time a new token arrives: call `listRef.current?.scrollToIndex(allMessages.length - 1, { smooth: false })` to keep bottom in view
|
||||
- When user has scrolled up (isAtBottom === false): do NOT scroll; show a "Jump to bottom" button: `<Button variant="outline" size="sm">` with a `ChevronDown` icon (16px), positioned `absolute bottom-20 right-4 z-10` relative to the message list container. Clicking it scrolls to bottom and sets `isAtBottom = true`
|
||||
- "Jump to bottom" button disappears immediately when `isAtBottom === true`
|
||||
|
||||
---
|
||||
|
||||
## Copywriting Contract
|
||||
|
||||
Source: Claude's discretion (discuss phase skipped). Follows Phase 21 tone: direct, lowercase preference, no corporate language.
|
||||
|
||||
| Element | Copy |
|
||||
|---------|------|
|
||||
| Stop button tooltip | "Stop generation" |
|
||||
| Retry button tooltip | "Retry response" |
|
||||
| Edit button tooltip (on user message) | "Edit message" |
|
||||
| Edit confirm button | "Regenerate" |
|
||||
| Edit cancel instruction | Press Escape to cancel |
|
||||
| Streaming indicator aria-label | "Response streaming" |
|
||||
| Agent selector tooltip | "Active agent for this conversation" |
|
||||
| Agent selector empty | "No agents configured" |
|
||||
| Jump to bottom button aria-label | "Jump to bottom" |
|
||||
| Slash command popover heading | (none — no heading; list items self-describe) |
|
||||
| @mention popover heading | (none — no heading; agent names self-describe) |
|
||||
| Error: stream failed | "Response failed. Try again." |
|
||||
| Error: edit failed | "Couldn't update message. Try again." |
|
||||
| Error: agent update failed | "Couldn't update agent. Try again." |
|
||||
| Error: target agent not found | "No {role} agent found. Message sent to active agent." |
|
||||
| Error: retry failed | "Retry failed. Try again." |
|
||||
| Toast on stop (if partial message not saved) | (no toast — stopping is expected; silent is correct) |
|
||||
|
||||
No new destructive actions in Phase 22. The Stop generation action is a cancellation, not a destructive action — no confirmation required.
|
||||
|
||||
---
|
||||
|
||||
## Accessibility Contract
|
||||
|
||||
Carried forward from Phase 21. Phase 22 additions:
|
||||
|
||||
- `ChatAgentBadge` avatar: `aria-hidden="true"` (decorative — the agent name provides the text label)
|
||||
- Agent name in `ChatAgentBadge`: `aria-label="Agent: {agent.name}"` on the containing span
|
||||
- `AgentSelector` trigger: `aria-label="Active agent: {currentAgent.name}"` (dynamically updated)
|
||||
- Stop button: `aria-label="Stop generation"`, never `aria-disabled` — always interactive during streaming
|
||||
- Streaming message container: `aria-live="off"` — do NOT use `aria-live="polite"` on the partial content container; the completed persisted message in the `role="log"` container is sufficient for screen reader announcement. Streaming token-by-token announcement would be noisy.
|
||||
- Edit textarea: `aria-label="Edit message"`, `aria-multiline="true"`
|
||||
- "Jump to bottom" button: `aria-label="Jump to bottom"`, `aria-hidden="true"` when not visible
|
||||
- Slash command popover: `role="listbox"`, each item `role="option"`, `aria-selected` on highlighted item
|
||||
- @mention popover: same `role="listbox"` pattern
|
||||
|
||||
---
|
||||
|
||||
## Animation Contract
|
||||
|
||||
All animations use Tailwind utilities or existing CSS transitions. No new animation libraries.
|
||||
|
||||
Carried from Phase 21:
|
||||
|
||||
| Element | Animation | Duration | Easing |
|
||||
|---------|-----------|----------|--------|
|
||||
| Chat panel open/close | `transition-[width]` | 100ms | `ease-out` |
|
||||
| Conversation list item hover | `transition-colors` | 150ms | Tailwind default |
|
||||
|
||||
Phase 22 additions:
|
||||
|
||||
| Element | Animation | Duration | Easing |
|
||||
|---------|-----------|----------|--------|
|
||||
| Streaming cursor dot | `animate-pulse` (Tailwind) | continuous | built-in |
|
||||
| Message action buttons (edit/retry/stop) | `transition-opacity` | 150ms | `ease-out` |
|
||||
| Send → Stop button swap | `transition-opacity` | 100ms | `ease-out` (cross-fade; layout does NOT shift) |
|
||||
| "Jump to bottom" button appear/disappear | `transition-opacity` | 150ms | `ease-out` |
|
||||
| AgentSelector dropdown open | Radix `<Select>` built-in animation | 150ms | Radix default |
|
||||
| Slash/mention popover open | `transition-opacity duration-100` | 100ms | `ease-out` |
|
||||
|
||||
No enter/exit animations for new streaming tokens — tokens append directly with no animation. This keeps PERF-02 (100ms latency) achievable without layout thrashing.
|
||||
|
||||
---
|
||||
|
||||
## Registry Safety
|
||||
|
||||
| Registry | Blocks Used | Safety Gate |
|
||||
|----------|-------------|-------------|
|
||||
| shadcn official | All components from Phase 21 + `Command`, `Popover`, `Select` (already installed) | not required |
|
||||
| Third-party registries | none | not applicable |
|
||||
|
||||
`virtua` is installed via npm (not shadcn registry). npm packages are not subject to the shadcn registry safety gate.
|
||||
|
||||
No third-party shadcn registry blocks are declared for this phase.
|
||||
|
||||
---
|
||||
|
||||
## 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