16 KiB
| 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 inui/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:
- Primary CTA buttons ("Generate Wallpaper", "Convert File", "Generate Post")
- Job progress bar fill
- Active tab indicator (ContentStudio Tabs, conversion format selector active state)
- VoiceMicButton active/recording ring (
ring-2 ring-primary— existing Phase 37 pattern) - Selected format chip highlight
Drag-drop zone states:
- Idle:
bg-secondarybackground,border-borderdashed border - Dragover:
bg-accent/30background,border-primarydashed border (primary color signals acceptance) - Error (MIME rejection):
bg-destructive/10background,border-destructivedashed 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-foregroundright-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. Turnstext-destructivewhen 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-fullbadges (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-secondarybackground,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):
VoiceMicButtonalready handles idle / recording / transcribing states with correct aria-labels.- Phase 42 ensures the button renders in
ChatInputwhen voice mode isvoice_inputorfull_voice. - Offline capability badge: if
WHISPER_MODEL=local, atext-xs text-muted-foregroundbadge "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)
- User submits → Button shows spinner + "Generating…" label (disabled). No separate overlay.
- SSE events arrive → Progress bar (
progresscomponent, primary fill) animates 0→100%. - On
ready→ progress bar fades out (200ms), result panel slides down (300ms ease-out). Button reverts to "Generate again" (secondary variant). - On
error→ progress bar fills destructive color, error inline: "Render failed — {detail}. Try again." Button reverts to primary (enabled). - Silent SSE reconnect on disconnect; no user-facing error unless render ultimately fails.
Drag-Drop File Upload
- User drags file over zone → zone border changes to
border-primary, background tobg-accent/30(no animation, instant). - User drops file → zone shows file metadata row (name, size, MIME).
- Magic-byte validation runs immediately (client sends file to
/api/convert/validateor server rejects on job submit). If mismatch: zone borderborder-destructive, backgroundbg-destructive/10, error copy inline. - User can replace file by clicking zone again while file is selected.
Format Deep-Link
- On mount, read
:sourceFormatand:targetFormatfrom URL params. - Pre-select corresponding chips (case-insensitive, e.g.
pngmatches "PNG" chip). - Update drag-drop zone copy to mention source format.
- If format params are invalid or not found: silently ignore (no error shown), render default state.
Social Character Count
- Character count updates on every keystroke (no debounce — immediate feedback is important for limit enforcement).
- When over limit: count turns
text-destructive, CTA button remains enabled (AI may trim on generate; do not block submission). - 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"withtabIndex={0},aria-label="Upload file — drop here or press Enter to browse".onKeyDownhandler for Enter/Space triggers file picker. - Format chips:
role="radio"within arole="radiogroup"container. Each chip:aria-checked={selected},aria-label="{format name}". - Platform selector: standard shadcn
Selectcomponent — accessible by default. - Progress bar:
role="progressbar"witharia-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