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

16 KiB
Raw Blame History

phase slug status shadcn_initialized preset created
42 wallpapers-social-format-conversion-voice draft true new-york / neutral / cssVariables / lucide 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.
  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