307 lines
16 KiB
Markdown
307 lines
16 KiB
Markdown
---
|
||
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
|