docs(06): UI design contract
This commit is contained in:
parent
a44f28d71b
commit
4b2e030ca3
1 changed files with 480 additions and 0 deletions
480
.planning/phases/06-analyzer-feed-new/06-UI-SPEC.md
Normal file
480
.planning/phases/06-analyzer-feed-new/06-UI-SPEC.md
Normal file
|
|
@ -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 | `<main>` 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: `<Card>` (shadcn) — no `border-l-4` stripe. The Card is the full surface. [D-11]
|
||||
|
||||
Inner layout: `<CardContent className="px-4 py-4 space-y-1.5">`
|
||||
|
||||
```
|
||||
[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 `<Link href="/mobile/analyzer/[id]">` 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 `<span className="sr-only">` 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
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-[10px] px-1.5 py-0.5 border-0 {bucket-bg} {bucket-text}"
|
||||
>
|
||||
{label}
|
||||
</Badge>
|
||||
```
|
||||
|
||||
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 && (
|
||||
<Badge
|
||||
variant="outline"
|
||||
className="text-[10px] px-1.5 py-0.5 border-0 bg-destructive/10 text-destructive"
|
||||
>
|
||||
Review
|
||||
</Badge>
|
||||
)}
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
```
|
||||
<Card>
|
||||
<CardContent className="px-4 py-4 space-y-1.5">
|
||||
[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
|
||||
</CardContent>
|
||||
</Card>
|
||||
```
|
||||
|
||||
Render 5 instances on initial load: `Array.from({ length: 5 }).map((_, i) => <AnalyzerRowSkeleton key={i} />)` [D-28]
|
||||
|
||||
### Infinite Scroll Sentinel + Load More [D-08, D-09]
|
||||
|
||||
Identical contract to Phase 4 [04-UI-SPEC.md]:
|
||||
|
||||
- Sentinel: `<div ref={sentinelRef} aria-hidden="true" />` at list end
|
||||
- `IntersectionObserver` with `rootMargin: '200px'` fires `fetchNextPage()` when sentinel enters viewport
|
||||
- Guard: no-op if `loadingMore || !hasMore`
|
||||
- Load more button: `w-full py-3 rounded-xl border text-sm font-semibold hover:bg-muted/50 transition-colors disabled:opacity-50` — always rendered when `hasMore`, focusable, `aria-label="Load more analyses"`
|
||||
- Loading more indicator: `Loader2 w-4 h-4 animate-spin text-muted-foreground mx-auto my-2` centered above the Load more button during in-flight requests
|
||||
|
||||
### Detail Page In-Page Header [D-19] — mirrors Phase 4 D-18
|
||||
|
||||
Renders below shell HeaderBar (shell provides sticky `bg-background/95` header — this is an in-page section, not sticky).
|
||||
|
||||
```
|
||||
flex items-center justify-between px-4 py-3 border-b
|
||||
LEFT: <button> ArrowLeft h-4 w-4 + "Analyzer" text → router.back()
|
||||
text-sm text-muted-foreground hover:text-foreground
|
||||
aria-label="Back to Analyzer"
|
||||
CENTER: breadcrumb "Analyzer / #{ticketNumber}"
|
||||
text-sm font-semibold truncate
|
||||
RIGHT: <a> href="/analyzer/analysis/[id]" target="_blank" rel="noopener noreferrer"
|
||||
ExternalLink h-4 w-4 text-muted-foreground hover:text-foreground
|
||||
aria-label="Open full analysis on desktop"
|
||||
```
|
||||
|
||||
[D-19, D-22 — ExternalLink pattern from Phase 2 DRAWER-04]
|
||||
|
||||
### Detail Page Identity Block [D-20]
|
||||
|
||||
```
|
||||
px-4 pt-4 pb-2 space-y-1
|
||||
[ticket number] text-[10px] font-mono bg-muted rounded px-1.5 py-0.5 inline-block
|
||||
[title] text-base font-semibold leading-snug
|
||||
[company name] text-xs text-muted-foreground
|
||||
[completed-at] text-[10px] text-muted-foreground (e.g. "3 days ago")
|
||||
[flex gap-2 items-center mt-1]
|
||||
AnalyzerStagePips (same component as feed row)
|
||||
ConfidenceBadge (same component as feed row)
|
||||
Review pill (same component as feed row, if applicable)
|
||||
```
|
||||
|
||||
### Detail Page Content Sections [D-21]
|
||||
|
||||
Three sections in order: Summary, Next Step, Next Step Rationale.
|
||||
|
||||
Each section:
|
||||
|
||||
```
|
||||
<section className="px-4 py-4 space-y-2">
|
||||
<h2 className="text-sm font-semibold">{section heading}</h2>
|
||||
<p className="text-sm font-normal leading-relaxed text-foreground whitespace-pre-wrap">
|
||||
{field value}
|
||||
</p>
|
||||
</section>
|
||||
```
|
||||
|
||||
Null fallback for each field: render `"Summary not available."` / `"Next step not available."` / `"Rationale not available."` in `text-sm text-muted-foreground`. [D-21]
|
||||
|
||||
Sections separated by `<Separator />` (shadcn) between them for visual clarity.
|
||||
|
||||
### Detail Page Footer Link [D-22]
|
||||
|
||||
```
|
||||
px-4 py-4 border-t
|
||||
<a href="/analyzer/analysis/[id]"
|
||||
target="_blank" rel="noopener noreferrer"
|
||||
className="flex items-center gap-1.5 text-sm font-semibold text-primary hover:underline min-h-[44px]">
|
||||
View full analysis
|
||||
<ExternalLink className="h-4 w-4" />
|
||||
</a>
|
||||
```
|
||||
|
||||
Copy: exactly "View full analysis" — not "View on desktop", not "Open analysis". [D-22, CONTEXT.md §specifics]
|
||||
|
||||
---
|
||||
|
||||
## Interaction Contracts
|
||||
|
||||
### Infinite Scroll [D-08, D-09] — mirrors Phase 4
|
||||
|
||||
- Page size: 25 rows (server-capped) [D-05]
|
||||
- Cursor: base64(JSON(`{ completed_at: ISO, id: uuid }`)). [D-06]
|
||||
- API response: `{ analyses: AnalyzerFeedRow[], nextCursor: string | null, hasMore: boolean }` [D-07]
|
||||
- Client state: `analyses: AnalyzerFeedRow[]` (appended), `nextCursor: string | null`, `hasMore: boolean`
|
||||
- No URL cursor persistence (no filter state to sync — feed has no filters this phase) [D-38]
|
||||
|
||||
### Detail Page Navigation [D-12, D-18, D-19]
|
||||
|
||||
- Tap on feed row → Next.js `<Link href="/mobile/analyzer/[id]">` navigation (not `router.push`)
|
||||
- Detail back button → `router.back()` (returns to the feed at last scroll position via browser history)
|
||||
- "View full analysis" → `target="_blank"` external link to `/analyzer/analysis/[id]`
|
||||
- The detail page ID is the `analyzer_analyses` UUID (shareable URL) [D-18]
|
||||
|
||||
### Loading States
|
||||
|
||||
| Phase | What renders |
|
||||
|-------|-------------|
|
||||
| Initial load | 5 `AnalyzerRowSkeleton` instances — no text, no spinner |
|
||||
| Load more in-flight | `Loader2 animate-spin` above Load more button; button disabled |
|
||||
| Detail page loading | Skeleton blocks: identity block (4 Skeleton lines) + 3 section skeletons |
|
||||
| Error on initial load | `toast.error("Failed to load analyses")` + inline retry affordance (see Copywriting) |
|
||||
| Error on load more | `toast.error("Failed to load more analyses")` + Load more button label → "Retry" |
|
||||
|
||||
### Accessibility
|
||||
|
||||
- Feed rows: each `<Link>` has implicit `role="link"`; title is the accessible name
|
||||
- AnalyzerStagePips: visually-hidden `<span className="sr-only">` with text description (e.g., "Stages completed: Triage, Analyze")
|
||||
- Sentinel div: `aria-hidden="true"`
|
||||
- Load more button: `aria-label="Load more analyses"`
|
||||
- Detail back button: `aria-label="Back to Analyzer"`
|
||||
- Detail external link: `aria-label="Open full analysis on desktop"`
|
||||
- Confidence badge: `aria-label="Confidence: High"` (etc.) on the `<Badge>` element
|
||||
- Review pill: `aria-label="Needs human review"` on the `<Badge>` element
|
||||
|
||||
---
|
||||
|
||||
## Copywriting Contract
|
||||
|
||||
| Element | Copy | Source |
|
||||
|---------|------|--------|
|
||||
| Page H1 | "Analyzer" | [D-35] |
|
||||
| Feed row — null summary fallback | "—" (em dash) | [D-10] |
|
||||
| Summary null fallback | "Summary not available." | [D-21] |
|
||||
| Next Step null fallback | "Next step not available." | [D-21] |
|
||||
| Next Step Rationale null fallback | "Rationale not available." | [D-21] |
|
||||
| Empty state heading | "No analyses yet" | [D-31] |
|
||||
| Empty state body | "Completed AI ticket analyses will appear here." | [D-31] |
|
||||
| Empty state CTA link label | "Open desktop Analyzer" | [D-31] |
|
||||
| Empty state CTA destination | `/analyzer/tickets` | [D-31] |
|
||||
| Initial load state | 5 skeleton rows (no text) | [D-28] |
|
||||
| Load more button (idle) | "Load more" | [D-09] |
|
||||
| Load more button (loading) | "Loading…" (Loader2 spinner, button disabled) | [D-29] |
|
||||
| Load more button (error/retry) | "Retry" | [D-30] |
|
||||
| Error toast — initial load | "Failed to load analyses" | [D-30] |
|
||||
| Error toast — load more | "Failed to load more analyses" | [D-30] |
|
||||
| Detail breadcrumb | "Analyzer / #{ticketNumber}" | [D-19] |
|
||||
| Detail back button | "Analyzer" (with ArrowLeft icon) | [D-19] |
|
||||
| Detail external link | "Open full analysis on desktop" (aria-label) | [D-22] |
|
||||
| Detail footer link visible label | "View full analysis" (with ExternalLink icon) | [D-22] |
|
||||
| Detail section heading — Summary | "Summary" | [D-21] |
|
||||
| Detail section heading — Next Step | "Next Step" | [D-21] |
|
||||
| Detail section heading — Rationale | "Next Step Rationale" | [D-21] |
|
||||
| Review pill label | "Review" | [D-17] |
|
||||
| Confidence badge labels | "High" / "Medium" / "Low" | [D-15] |
|
||||
| Stage pip sr-only | "Stages completed: {list}" | default — accessibility |
|
||||
|
||||
Destructive actions: None. Feed and detail are fully read-only. No confirmation dialogs, no destructive buttons. [D-23, ANL-05]
|
||||
|
||||
---
|
||||
|
||||
## Component Files to Create
|
||||
|
||||
Following the Phase 3/4/5 pattern (kebab-case files, `components/mobile/` directory):
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `components/mobile/AnalyzerFeedRow.tsx` | Feed row Card: header/title/summary/footer. Receives `AnalyzerFeedRow` type from route. |
|
||||
| `components/mobile/AnalyzerStagePips.tsx` | Three dots + caret separators, driven by `haiku_used`, `sonnet_used`, `opus_used`. Pure presentational. |
|
||||
| `components/mobile/ConfidenceBadge.tsx` | shadcn Badge with bucket color logic. Renders nothing when `score` is null. |
|
||||
| `components/mobile/AnalyzerRowSkeleton.tsx` | Skeleton placeholder matching analyzer row shape (no priority stripe). |
|
||||
| `app/mobile/analyzer/page.tsx` | Replace placeholder — 'use client', feed list + IntersectionObserver + Load more. |
|
||||
| `app/mobile/analyzer/[id]/page.tsx` | New detail page — 'use client', MobileDetailHeader, identity block, 3 sections, footer link. |
|
||||
| `app/api/mobile/analyzer/feed/route.ts` | GET handler — requireAuth, cursor-paginated query, exports `AnalyzerFeedRow` and `AnalyzerFeedResponse` types. |
|
||||
|
||||
`MobileDetailHeader` extraction (back chevron + breadcrumb + external link row): extract as `components/mobile/MobileDetailHeader.tsx` if the diff from Phase 4's detail header is trivial (same three-slot layout); mirror inline otherwise. Decision deferred to executor per CONTEXT.md Claude's Discretion.
|
||||
|
||||
Component comment block convention (Phase 3/4/5 pattern):
|
||||
```typescript
|
||||
/* ComponentName — phase 06 (ANL-NN).
|
||||
* Purpose: one-line description.
|
||||
* Props: ... */
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Shape Contract
|
||||
|
||||
The route file exports TypeScript interfaces for the page to `import type`. Mirrors Phase 4 pattern. [D-26, D-27]
|
||||
|
||||
```typescript
|
||||
// app/api/mobile/analyzer/feed/route.ts — exported interfaces
|
||||
|
||||
export interface AnalyzerFeedRow {
|
||||
id: string; // analyzer_analyses UUID
|
||||
ticketNumber: string;
|
||||
title: string;
|
||||
companyName: string;
|
||||
summary: string | null;
|
||||
confidenceScore: number | null;
|
||||
haikuUsed: boolean;
|
||||
sonnetUsed: boolean;
|
||||
opusUsed: boolean;
|
||||
needsHumanReview: boolean;
|
||||
completedAt: string; // ISO string
|
||||
analysisVersion: number;
|
||||
}
|
||||
|
||||
export interface AnalyzerFeedResponse {
|
||||
analyses: AnalyzerFeedRow[];
|
||||
nextCursor: string | null;
|
||||
hasMore: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
Detail page imports `PersistedAnalysis` from `lib/types/analyzer.ts` directly via the existing `GET /api/analyzer/analyses/[id]` endpoint — no parallel type. [D-27]
|
||||
|
||||
---
|
||||
|
||||
## Registry Safety
|
||||
|
||||
| Registry | Blocks Used | Safety Gate |
|
||||
|----------|-------------|-------------|
|
||||
| shadcn official | `Card`, `CardContent`, `Badge`, `Skeleton`, `Button`, `Separator` | not required |
|
||||
|
||||
No third-party registries. All components are either shadcn official primitives or purpose-built in `components/mobile/`. [D-36, D-37, D-38]
|
||||
|
||||
---
|
||||
|
||||
## What Stays Unchanged
|
||||
|
||||
Per D-36, D-37, D-38:
|
||||
- Desktop analyzer routes (`/analyzer/*`, `/api/analyzer/*`) — untouched
|
||||
- `lib/services/analyzer/**` pipeline, persistence, worker — untouched
|
||||
- `getMobileCompanyFilter()` scoping helper in `app/api/mobile/tickets/route.ts` — duplicate inline in the new feed route; do not abstract yet (D-04)
|
||||
- `relTime()` helper — duplicate inline; extract shared only when third caller appears (D-04 note)
|
||||
- `app/mobile/layout.tsx` (Phase 2 shell) — untouched; analyzer pages dock inside it
|
||||
- BottomNav active-tab detection (`pathname.startsWith('/mobile/analyzer')`) — already correct; `/mobile/analyzer/[id]` highlights Analyzer tab automatically
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
---
|
||||
|
||||
*Phase: 06-analyzer-feed-new*
|
||||
*UI-SPEC created: 2026-05-04*
|
||||
*Source decisions: 06-CONTEXT.md D-01 through D-39 (all locked)*
|
||||
*Typography/spacing/color mirrors: 04-UI-SPEC.md (Phase 4 approved contract)*
|
||||
Loading…
Add table
Add a link
Reference in a new issue