diff --git a/.planning/phases/06-analyzer-feed-new/06-UI-SPEC.md b/.planning/phases/06-analyzer-feed-new/06-UI-SPEC.md new file mode 100644 index 0000000..3492468 --- /dev/null +++ b/.planning/phases/06-analyzer-feed-new/06-UI-SPEC.md @@ -0,0 +1,480 @@ +--- +phase: 6 +slug: analyzer-feed-new +status: draft +shadcn_initialized: true +preset: new-york / neutral base / CSS variables +created: 2026-05-04 +--- + +# Phase 6 — UI Design Contract: Analyzer Feed (NEW) + +> Visual and interaction contract for the mobile Analyzer feed list and per-analysis summary detail page. +> Generated by gsd-ui-researcher. Consumed by gsd-ui-checker, gsd-planner, gsd-executor. + +All decisions tagged `[D-NN]` are LOCKED in `06-CONTEXT.md` and must not be re-litigated. + +--- + +## Design System + +| Property | Value | +|----------|-------| +| Tool | shadcn/ui (new-york style) | +| Preset | `components.json` — new-york, neutral base, CSS variables, lucide icons | +| Component library | Radix UI (via shadcn) | +| Icon library | lucide-react | +| Font | IBM Plex Sans (sans), IBM Plex Mono (numeric/ID fields) | + +Source: `components.json` (confirmed present), `DESIGN.md §2`, `app/styles/brand.css`. + +--- + +## Viewport Contract + +| Property | Value | +|----------|-------| +| Reference device | iPhone 15 Pro — 393 × 852 CSS pixels | +| Max width constraint | `max-w-lg mx-auto` (from `app/mobile/layout.tsx` — Phase 2) | +| Shell chrome | HeaderBar (sticky, h-14 + pt-safe) + BottomNav (fixed h-16 + pb-safe) | +| Scrollable content area | `
` in layout — bottom padding = `calc(theme(spacing.16)+env(safe-area-inset-bottom))` | +| No in-page sticky zone | No filter strip — analyzer feed has no collapsible filter row (deferred, out of scope) | + +--- + +## Spacing Scale + +Declared values (multiples of 4). Mirrors Phase 4/5 contract exactly. + +| Token | Value | Usage in this phase | +|-------|-------|---------------------| +| xs | 4px | Badge internal padding (`px-1.5 py-0.5`), pip gap (`gap-1`), icon gap | +| sm | 8px | Row internal gaps (`gap-2`), pip-to-badge gap (`gap-2`) | +| sm+ | 12px (3 × 4) | Pip flex container gap (`gap-1.5`), secondary spacing | +| md | 16px | Horizontal page padding (`px-4`), Card vertical padding (`py-4`) | +| lg | 24px | Vertical section gap on detail page (`gap-6`) | +| xl | 32px | Empty state vertical padding (`py-8`) | +| 2xl | 48px | Full empty-state screen centering (`py-12`) | + +Touch-target exception: "Load more" fallback button minimum `min-h-[44px]` tap target (use `py-3` padding). The `ExternalLink` footer link on the detail page must also reach `min-h-[44px]`. + +Exceptions: Stage pip dots are decorative visual indicators (`h-1.5 w-1.5`), not interactive targets — they do not need touch padding. [D-14] + +--- + +## Typography + +Two weights only: `font-normal` (400) and `font-semibold` (600). `font-medium` (500) is NOT used. [D-32] + +Three sizes. [D-33] + +| Role | Size class | Weight | Line Height | Font | Usage | +|------|-----------|--------|-------------|------|-------| +| Row title / section heading | `text-sm` (14px) | `font-semibold` (600) | `leading-snug` (1.375) | IBM Plex Sans | Ticket title (1-line truncate), detail section headings, page H1 | +| Body / secondary | `text-xs` (12px) | `font-normal` (400) | `leading-normal` (1.5) | IBM Plex Sans | Summary clamp (2-line), company name, section body text | +| Badge / time-ago / ticket number / pip labels | `text-[10px]` (10px) | `font-normal` (400) | `leading-normal` | IBM Plex Mono (ticket#) / IBM Plex Sans (badges, time) | Ticket number badge (mono), confidence badge text, "Review" pill, time-ago, pip caret labels | + +Page H1 "Analyzer": `text-base font-semibold` (renders in page body, not in the shell HeaderBar). [D-35] + +Detail page long-form text (Summary, Next Step, Next Step Rationale body): `text-sm font-normal leading-relaxed whitespace-pre-wrap`. [D-21] + +--- + +## Color + +All colors use CSS variable tokens from `app/globals.css` + `app/styles/brand.css`. Direct Tailwind palette references are used only for semantic status colors per `DESIGN.md §2`. + +| Role | Token / Class | Usage | +|------|--------------|-------| +| Dominant surface (60%) | `bg-background` | Page background, detail page background | +| Secondary surface (30%) | `bg-muted` / `bg-muted/50` | Ticket number badge (`bg-muted rounded px-1.5 py-0.5`), row hover (`hover:bg-muted/50`) | +| Primary accent (10%) | `text-primary` | Active BottomNav tab only (inherited from Phase 2 shell) | +| Muted text | `text-muted-foreground` | Summary 2-line clamp, company name, time-ago, pip labels, caret separator | +| Card surface | `bg-card` / `border` | shadcn Card wrapping each feed row and detail sections | +| Destructive | `text-destructive` / `bg-destructive/10` | "Review" pill (`needs_human_review`), error toast, Retry button label | + +Accent reserved for: active BottomNav tab indicator only (the Analyzer tab `text-primary` state). Not used for row elements, badges, or hover states. + +### Stage Pip Colors [D-13, D-14] + +| State | Class | Semantic | +|-------|-------|----------| +| Used (filled) | `bg-primary` | Haiku / Sonnet / Opus stage was executed | +| Unused (empty) | `bg-muted-foreground/30` | Stage was not reached | + +### Confidence Badge Colors [D-15, D-16] + +| Bucket | Condition | Background | Text | Label | +|--------|-----------|------------|------|-------| +| High | `confidence_score >= 0.85` | `bg-green-500/10` | `text-green-700` | "High" | +| Medium | `0.65 <= score < 0.85` | `bg-amber-500/10` | `text-amber-700` | "Medium" | +| Low | `score < 0.65` | `bg-slate-500/10` | `text-slate-600` | "Low" | +| Absent | `confidence_score IS NULL` | — | — | (render nothing) | + +Dark mode: Use `dark:text-green-400` / `dark:text-amber-400` / `dark:text-slate-400` for badge text in dark context — the `/10` background holds in both modes via opacity. + +### Review Pill Colors [D-17] + +| State | Classes | +|-------|---------| +| `needs_human_review = true` | `bg-destructive/10 text-destructive` | +| `needs_human_review = false` | (render nothing — no empty pill placeholder) | + +--- + +## Component Inventory + +### Primary Visual Anchor + +Each feed row's primary focal point is the ticket title on line 2 — `text-sm font-semibold` with 1-line truncate. Readers land on the title first, then scan down to the summary clamp. The footer pip row and confidence badge are secondary metadata; they must not visually compete with the title. + +### Feed Row Card [D-10, D-11, D-12] — `AnalyzerFeedRow` + +Outer wrapper: `` (shadcn) — no `border-l-4` stripe. The Card is the full surface. [D-11] + +Inner layout: `` + +``` +[Line 1 — header row] flex justify-between items-center + LEFT: ticket number bg-muted rounded px-1.5 py-0.5 text-[10px] font-mono + RIGHT: time-ago text-[10px] text-muted-foreground + +[Line 2 — title] + text-sm font-semibold leading-snug truncate + +[Line 3 — summary clamp] + text-xs text-muted-foreground line-clamp-2 + Null fallback: render "—" + +[Footer — flex justify-between items-center mt-1] + LEFT: AnalyzerStagePips component + RIGHT: flex gap-2 items-center + ConfidenceBadge component (or nothing if null) + "Review" pill (or nothing if false) +``` + +Tap target: entire Card is wrapped in `` with `cursor-pointer hover:bg-muted/50 transition-colors active:bg-muted/50` on the Card. [D-12] + +Row list container: `space-y-3` between cards. [D-34] + +### Stage Pips — `AnalyzerStagePips` [D-13, D-14] + +``` +flex items-center gap-1.5 + [dot h-1.5 w-1.5 rounded-full {filled|muted}] Triage (haiku_used) + [caret › text-[10px] text-muted-foreground] + [dot h-1.5 w-1.5 rounded-full {filled|muted}] Analyze (sonnet_used) + [caret › text-[10px] text-muted-foreground] + [dot h-1.5 w-1.5 rounded-full {filled|muted}] Deep Review (opus_used) +``` + +Filled state: `bg-primary` (used). Muted state: `bg-muted-foreground/30` (not reached). [D-13] + +No animation, no hover states, no tooltip — static visual indicator only. [D-13, CONTEXT.md §specifics] + +Accessibility: render a visually-hidden `` describing stages used, e.g., `"Stages: Triage, Analyze"` for screen readers. + +### Confidence Badge — `ConfidenceBadge` [D-15, D-16] + +Uses shadcn `Badge` from `components/ui/badge.tsx`. Render as custom variant via inline `className` override (not a new variant — match the `Badge` prop signature). + +```tsx + + {label} + +``` + +No `border` on the badge (set `border-0` via className). Background and text color from the confidence bucket table above. [D-16] + +When `confidence_score` is null: render `null` (no element). [D-15] + +### Review Pill [D-17] + +Inline with confidence badge in the footer right slot: + +```tsx +{needsHumanReview && ( + + Review + +)} +``` + +Copy: exactly "Review" — no icon, no exclamation mark. [D-17, CONTEXT.md §specifics] + +### Skeleton Row — `AnalyzerRowSkeleton` [D-28] + +Mirrors `TicketRowSkeleton` shape without the `border-l-4` stripe. Uses shadcn `Skeleton`. + +``` + + + [flex justify-between] + [Skeleton h-3 w-16] ← ticket number + [Skeleton h-3 w-10] ← time-ago + [Skeleton h-4 w-3/4 mt-0.5] ← title + [Skeleton h-3 w-full mt-1] ← summary line 1 + [Skeleton h-3 w-2/3] ← summary line 2 + [flex justify-between mt-2] + [Skeleton h-2 w-20] ← pip row + [Skeleton h-3 w-12] ← badge + + +``` + +Render 5 instances on initial load: `Array.from({ length: 5 }).map((_, i) => )` [D-28] + +### Infinite Scroll Sentinel + Load More [D-08, D-09] + +Identical contract to Phase 4 [04-UI-SPEC.md]: + +- Sentinel: `