nexus/.planning/phases/42-wallpapers-social-format-conversion-voice/42-UI-SPEC.md

307 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
phase: 42
slug: wallpapers-social-format-conversion-voice
status: draft
shadcn_initialized: true
preset: new-york / neutral / cssVariables / lucide
created: 2026-04-04
---
# Phase 42 — UI Design Contract
> Visual and interaction contract for Phase 42: Wallpapers, Social, Format Conversion & Voice.
> Generated by gsd-ui-researcher. Verified by gsd-ui-checker.
---
## Design System
| Property | Value | Source |
|----------|-------|--------|
| Tool | shadcn | components.json detected |
| Style | new-york | components.json |
| Preset | neutral base color, cssVariables, radius=0 | ui/src/index.css |
| Component library | Radix UI (via shadcn) | components.json |
| Icon library | lucide-react | components.json |
| Font | System UI (inherited; no custom font loaded) | index.css |
**Existing components available (no reinstall needed):**
`avatar`, `badge`, `breadcrumb`, `button`, `card`, `checkbox`, `collapsible`, `command`, `dialog`, `dropdown-menu`, `input`, `label`, `popover`, `progress`, `scroll-area`, `select`, `separator`, `sheet`, `skeleton`, `tabs`, `textarea`, `toggle`, `tooltip`
**New shadcn components needed for Phase 42:**
- None. All required primitives were installed in Phase 41.
**Third-party/custom dependencies:**
- `VoiceMicButton`, `VoiceWaveform`, `VoiceModeToggle` — already exist in `ui/src/components/`. Reuse directly; do not rebuild.
---
## Spacing Scale
Declared values (multiples of 4). Inherited from Phase 41; no changes.
| Token | Value | Usage |
|-------|-------|-------|
| xs | 4px | Icon gaps, badge padding, inline chip gaps |
| sm | 8px | Compact element spacing, button icon gap |
| md | 16px | Default card padding, form field spacing |
| lg | 24px | Section padding, panel gaps |
| xl | 32px | Layout column gaps, page section breaks |
| 2xl | 48px | Major section breaks |
| 3xl | 64px | Not used in Phase 42 |
**Exceptions (Phase 42 specific):**
- Drag-drop upload zone: minimum height 120px, padding 24px, dashed border `2px dashed var(--border)`.
- Format selector chips/badges: 8px horizontal padding, 4px vertical padding, `rounded-full`.
- Platform dimension labels (e.g. "2560 × 1440"): display inline in `text-xs font-mono text-muted-foreground`, right-aligned in the option row.
- VoiceMicButton: 32×32px (h-8 w-8) — existing contract from Phase 37; no change.
---
## Typography
Source: Phase 41 contract. No additions needed.
| Role | Size | Weight | Line Height | Usage |
|------|------|--------|-------------|-------|
| Body | 15px (0.9375rem) | 400 (regular) | 1.6 | Conversion UI descriptions, wallpaper prompt text |
| Label | 14px (0.875rem) | 400 (regular) | 1.5 | Form labels, format chips, panel section titles |
| Heading | 20px (1.25rem) | 600 (semibold) | 1.3 | Panel titles ("Generate Wallpaper", "Convert File") |
| Display | 28px (1.75rem) | 600 (semibold) | 1.2 | Not used in Phase 42 |
**Declared weights: 2 — 400 (regular) and 600 (semibold).** Identical to Phase 41.
**Monospace:** `ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace` at 14px, weight 400, line-height 1.6. Used for platform dimension labels, file MIME type display, and converted file name display.
---
## Color
Source: Phase 41 contract — Catppuccin Latte (light) + Catppuccin Mocha (dark). No new tokens introduced.
| Role | Light value | Dark value | Usage |
|------|-------------|------------|-------|
| Dominant (60%) | `#eff1f5` (--background) | `#1e1e2e` (--background) | Page background, wallpaper canvas background |
| Secondary (30%) | `#e6e9ef` (--card) | `#181825` (--card) | Format conversion panel card, image result card, social post preview card |
| Muted surface | `#ccd0da` (--secondary) | `#313244` (--secondary) | Drag-drop zone background, platform selector bg, AI fallback notice bg |
| Accent (10%) | `#bcc0cc` (--accent) | `#45475a` (--accent) | Hover states on non-primary interactive elements |
| Primary | `#1e66f5` (--primary) | `#89b4fa` (--primary) | See "Accent reserved for" below |
| Destructive | `#d20f39` (--destructive) | `#f38ba8` (--destructive) | MIME validation rejection error, destructive actions |
| Border | `#ccd0da` (--border) | `#313244` (--border) | Panel edges, drag-drop zone border, input outlines |
| Muted foreground | `#9ca0b0` (--muted-foreground) | `#6c7086` (--muted-foreground) | Platform dimension labels, helper text, secondary format labels |
**Accent (--primary) reserved for:**
1. Primary CTA buttons ("Generate Wallpaper", "Convert File", "Generate Post")
2. Job progress bar fill
3. Active tab indicator (ContentStudio Tabs, conversion format selector active state)
4. VoiceMicButton active/recording ring (`ring-2 ring-primary` — existing Phase 37 pattern)
5. Selected format chip highlight
**Drag-drop zone states:**
- Idle: `bg-secondary` background, `border-border` dashed border
- Dragover: `bg-accent/30` background, `border-primary` dashed border (primary color signals acceptance)
- Error (MIME rejection): `bg-destructive/10` background, `border-destructive` dashed border
---
## Component Inventory (Phase 42)
### ContentStudio Tab Extensions
Extend the existing `ContentStudio.tsx` Tabs component. Add three new tabs to the existing `<TabsList>`:
- "Wallpapers" (value: `wallpapers`)
- "Social" (value: `social`)
- "Convert" (value: `convert`)
Voice mic integration is in `ChatInput` / `ChatPanel` (not ContentStudio). Phase 42 does not add a "Voice" tab.
---
### Wallpaper & Social Image Panel
**WallpaperGeneratePanel** — Card with:
- Prompt textarea (4 rows, placeholder: "Describe the scene, mood, or concept…")
- Platform selector (Select component). Options grouped:
- Desktop: "Desktop HD (2560 × 1440)", "Desktop FHD (1920 × 1080)", "Desktop 4K (3840 × 2160)"
- Mobile: "Mobile Portrait (1080 × 1920)", "Mobile Landscape (1920 × 1080)"
- Social: "OG Image (1200 × 630)", "Twitter Card (1200 × 628)", "Instagram Post (1080 × 1080)", "Instagram Banner (1080 × 566)", "LinkedIn Banner (1584 × 396)"
- App: "App Icon (1024 × 1024)", "Favicon (32 × 32)"
- Dimension label renders inline in each Select option: `text-xs font-mono text-muted-foreground` right-aligned.
- "Generate Wallpaper" Button (primary, full-width of card).
- Progress bar below button (shadcn `progress`, primary fill) — same SSE job pattern as Phase 41.
**WallpaperPreview** — After job `ready`:
- Image renders in a constrained container (`max-h-80 object-contain`).
- Below image: "Download PNG" Button (primary) + resolution badge (text-xs, monospace, muted).
- If job type is app icon / favicon: shows a multi-size grid. Each cell: size label (32×32, 64×64, etc.) + Download link.
---
### Social Post Panel
**SocialPostPanel** — Card with:
- Prompt textarea (4 rows, placeholder: "Describe the topic or paste existing content to adapt…")
- Platform selector (Select). Options: "Twitter/X", "LinkedIn", "Instagram Caption", "Instagram Carousel"
- Character count indicator (below textarea, right-aligned): `text-xs text-muted-foreground`. Turns `text-destructive` when over platform limit.
- "Generate Post" Button (primary).
**SocialPostResult** — After job `ready`:
- Post text in a read-only card (`bg-card`, `rounded-lg`, `p-4`).
- Character count badge inline (text-xs, monospace, muted).
- "Copy Post" Button (secondary, full-width).
- Hashtag section below copy button: chips in `rounded-full` badges (`bg-muted text-muted-foreground`). Each chip is clickable to copy the hashtag individually.
- For carousel: shows a numbered list (slide 1, slide 2…) in collapsible sections (shadcn `collapsible`).
---
### Format Conversion Panel
**ConvertPanel** — Page layout (not nested in ContentStudio tabs; lives at `/convert` route with deep-link support `/convert/:sourceFormat/:targetFormat`).
Layout: two-column on desktop (source column left, target column right), single column on mobile.
**ConvertSourceZone** — Left column:
- Drag-drop zone (`min-h-[120px]`, `p-6`, dashed border). Copy: "Drop a file here or click to browse".
- `<input type="file" hidden>` triggered by zone click.
- After file selected: shows file name (`text-sm font-medium`), file size (`text-xs text-muted-foreground`), detected MIME type (`text-xs font-mono text-muted-foreground`).
- MIME validation error (magic-byte mismatch): replaces file metadata with a destructive error inline: "File extension does not match content. Got {actualMime}, expected {claimedMime}."
**ConvertTargetSelector** — Right column:
- Grouped format chips. Groups: Images, Audio/Video, Documents, Data.
- Each chip: `rounded-full px-3 py-1 text-xs font-medium`. Idle: `bg-muted text-muted-foreground`. Selected: `bg-primary text-primary-foreground`.
- Unavailable formats (direct converter not detected at startup): chip still shown and selectable — falls through to AI-bridged conversion rather than disabled.
- AI fallback indicator: when selected pair has no direct converter, a muted notice renders below chips: "No direct converter for this pair — AI bridge will be used." (`text-xs text-muted-foreground`, Info icon 14px, `bg-secondary` background, `rounded-md p-3`).
**ConvertActionBar** — Spans full width below both columns:
- "Convert File" Button (primary, disabled until source file selected + target format selected).
- Progress bar below button (same SSE job pattern).
- After ready: "Download {filename}.{ext}" Button (primary) replaces progress bar.
**Deep-link pre-selection:** When route is `/convert/png/svg`, source format chip "PNG" and target format chip "SVG" are pre-highlighted on mount. Drag-drop zone shows "Drop a PNG file here or click to browse" with source format in the copy.
---
### Voice Integration (Web Chat)
Voice UI components (`VoiceMicButton`, `VoiceWaveform`, `VoiceModeToggle`) were built in Phase 37. Phase 42 wires the local Whisper offline model to the existing `VoiceMicButton` — no new component surface.
**Integration contract (no new visual components):**
- `VoiceMicButton` already handles idle / recording / transcribing states with correct aria-labels.
- Phase 42 ensures the button renders in `ChatInput` when voice mode is `voice_input` or `full_voice`.
- Offline capability badge: if `WHISPER_MODEL=local`, a `text-xs text-muted-foreground` badge "Offline" renders inline next to the mic button (Wifi-off icon 12px, no tooltip needed — badge text is sufficient).
---
## Interaction Contracts
### Job Progress (shared pattern — identical to Phase 41)
1. User submits → Button shows spinner + "Generating…" label (disabled). No separate overlay.
2. SSE events arrive → Progress bar (`progress` component, primary fill) animates 0→100%.
3. On `ready` → progress bar fades out (200ms), result panel slides down (300ms ease-out). Button reverts to "Generate again" (secondary variant).
4. On `error` → progress bar fills destructive color, error inline: "Render failed — {detail}. Try again." Button reverts to primary (enabled).
5. Silent SSE reconnect on disconnect; no user-facing error unless render ultimately fails.
### Drag-Drop File Upload
1. User drags file over zone → zone border changes to `border-primary`, background to `bg-accent/30` (no animation, instant).
2. User drops file → zone shows file metadata row (name, size, MIME).
3. Magic-byte validation runs immediately (client sends file to `/api/convert/validate` or server rejects on job submit). If mismatch: zone border `border-destructive`, background `bg-destructive/10`, error copy inline.
4. User can replace file by clicking zone again while file is selected.
### Format Deep-Link
1. On mount, read `:sourceFormat` and `:targetFormat` from URL params.
2. Pre-select corresponding chips (case-insensitive, e.g. `png` matches "PNG" chip).
3. Update drag-drop zone copy to mention source format.
4. If format params are invalid or not found: silently ignore (no error shown), render default state.
### Social Character Count
1. Character count updates on every keystroke (no debounce — immediate feedback is important for limit enforcement).
2. When over limit: count turns `text-destructive`, CTA button remains enabled (AI may trim on generate; do not block submission).
3. Limit constants per platform: Twitter/X = 280, LinkedIn = 3000, Instagram Caption = 2200, Instagram Carousel (per slide) = 300.
### Hashtag Copy
- Clicking a hashtag chip copies the text (including `#`) to clipboard.
- Chip briefly shows a check icon (CheckCheck, 12px) for 1.5s, then reverts to original text.
- No toast. Inline feedback on the chip itself is sufficient.
---
## Copywriting Contract
| Element | Copy |
|---------|------|
| Wallpaper CTA | "Generate Wallpaper" |
| Wallpaper generating state | "Generating…" |
| Wallpaper download | "Download PNG" |
| Wallpaper empty state heading | "No image yet" |
| Wallpaper empty state body | "Describe a scene or mood and pick a platform size to generate a ready-to-use image." |
| Wallpaper render error | "Render failed — {detail}. Try again." |
| Social CTA | "Generate Post" |
| Social generating state | "Generating…" |
| Social copy CTA | "Copy Post" |
| Social copied state | "Copied!" (reverts after 2s) |
| Social hashtag copy (chip tooltip / accessible label) | "Copy hashtag" |
| Social empty state heading | "No post yet" |
| Social empty state body | "Describe your topic and choose a platform to generate a ready-to-publish post." |
| Social render error | "Generation failed — {detail}. Try again." |
| Character count within limit | "{N} / {limit}" |
| Character count over limit | "{N} / {limit} — over limit" |
| Convert drag-drop idle | "Drop a file here or click to browse" |
| Convert drag-drop with source format | "Drop a {FORMAT} file here or click to browse" |
| Convert drag-drop dragover | "Release to select" |
| Convert MIME mismatch error | "File extension does not match content. Got {actualMime}, expected {claimedMime}." |
| Convert AI fallback notice | "No direct converter for this pair — AI bridge will be used." |
| Convert CTA | "Convert File" |
| Convert converting state | "Converting…" |
| Convert download | "Download {filename}.{ext}" |
| Convert empty state heading | "No conversion yet" |
| Convert empty state body | "Upload a file and choose a target format to convert." |
| Convert render error | "Conversion failed — {detail}. Try again." |
| Voice offline badge | "Offline" |
| Voice mic idle (aria-label) | "Start voice input" |
| Voice mic recording (aria-label) | "Recording — speak now" |
| Voice mic transcribing (aria-label) | "Transcribing..." |
**Destructive actions in Phase 42:** None. No file deletions or irreversible actions in scope. MIME rejection is an error state, not a destructive confirmation.
---
## Registry Safety
| Registry | Blocks Used | Safety Gate |
|----------|-------------|-------------|
| shadcn official | all blocks from Phase 41 (reused) | not required |
No new shadcn blocks needed. No third-party registries declared for Phase 42.
---
## Accessibility
- Drag-drop zone: `role="button"` with `tabIndex={0}`, `aria-label="Upload file — drop here or press Enter to browse"`. `onKeyDown` handler for Enter/Space triggers file picker.
- Format chips: `role="radio"` within a `role="radiogroup"` container. Each chip: `aria-checked={selected}`, `aria-label="{format name}"`.
- Platform selector: standard shadcn `Select` component — accessible by default.
- Progress bar: `role="progressbar"` with `aria-valuenow`, `aria-valuemin=0`, `aria-valuemax=100` (same contract as Phase 41).
- Hashtag chips: `role="button"`, `aria-label="Copy hashtag {tag}"`.
- Character count: `aria-live="polite"` on the count element so screen readers announce changes without interrupting.
- VoiceMicButton: existing aria-labels from Phase 37 are correct — do not change.
- Offline badge next to mic: `aria-label="Voice input is offline (local model)"` on the badge element.
- `prefers-reduced-motion`: disable drag-drop zone color transition, disable result panel slide animation. Keep progress bar animation (functional feedback).
- Wallpaper image result: `alt="{prompt text truncated to 100 chars}"` on `<img>`.
---
## 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