nexus/.planning/phases/42-wallpapers-social-format-conversion-voice/42-05-PLAN.md

237 lines
12 KiB
Markdown

---
phase: 42-wallpapers-social-format-conversion-voice
plan: 05
type: execute
wave: 3
depends_on: [42-02]
files_modified:
- ui/src/components/WallpaperGeneratePanel.tsx
- ui/src/components/WallpaperPreview.tsx
- ui/src/components/SocialPostPanel.tsx
- ui/src/components/SocialPostResult.tsx
- ui/src/pages/ContentStudio.tsx
autonomous: true
requirements: [WALL-01, WALL-02, WALL-03, WALL-04, SOCIAL-01, SOCIAL-02, SOCIAL-03]
must_haves:
truths:
- "User can type a prompt, select a platform, and generate a wallpaper/social image"
- "Platform selector shows all 12 platform options grouped by Desktop/Mobile/Social/App"
- "Wallpaper preview shows the image with download button after job completes"
- "App icon/favicon shows multi-size grid with individual download links"
- "Social post panel shows character count that turns red over limit"
- "Generated post shows copy button and clickable hashtag chips"
- "Instagram carousel shows numbered collapsible slide sections"
- "ContentStudio has Wallpapers and Social tabs"
artifacts:
- path: "ui/src/components/WallpaperGeneratePanel.tsx"
provides: "Prompt input + platform selector + generate button"
contains: "PLATFORM_DIMENSIONS"
- path: "ui/src/components/WallpaperPreview.tsx"
provides: "Image display + download button + multi-size grid for icons"
contains: "Download PNG"
- path: "ui/src/components/SocialPostPanel.tsx"
provides: "Prompt input + platform selector + character count"
contains: "PLATFORM_CHAR_LIMITS"
- path: "ui/src/components/SocialPostResult.tsx"
provides: "Post display + copy button + hashtag chips + carousel slides"
contains: "Copy Post"
- path: "ui/src/pages/ContentStudio.tsx"
provides: "Wallpapers and Social tabs added to TabsList"
contains: "wallpapers"
key_links:
- from: "ui/src/components/WallpaperGeneratePanel.tsx"
to: "ui/src/hooks/useContentJob.ts"
via: "useContentJob hook for SSE job tracking"
pattern: "useContentJob"
- from: "ui/src/components/SocialPostPanel.tsx"
to: "ui/src/hooks/useContentJob.ts"
via: "useContentJob hook for SSE job tracking"
pattern: "useContentJob"
- from: "ui/src/pages/ContentStudio.tsx"
to: "ui/src/components/WallpaperGeneratePanel.tsx"
via: "TabsContent for wallpapers"
pattern: "WallpaperGeneratePanel"
---
<objective>
Build the Wallpaper and Social UI panels and extend ContentStudio with new tabs.
Purpose: This surfaces the wallpaper and social renderers (Plan 02) to users through the ContentStudio interface.
Output: Four new UI components + updated ContentStudio tabs.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/42-wallpapers-social-format-conversion-voice/42-RESEARCH.md
@.planning/phases/42-wallpapers-social-format-conversion-voice/42-UI-SPEC.md
@.planning/phases/42-wallpapers-social-format-conversion-voice/42-02-SUMMARY.md
@ui/src/pages/ContentStudio.tsx
@ui/src/components/DiagramGeneratePanel.tsx
@ui/src/components/IconGeneratePanel.tsx
@ui/src/hooks/useContentJob.ts
@ui/src/api/contentJobs.ts
</context>
<interfaces>
From server/src/services/renderers/wallpaper-renderer.ts (Plan 02):
```typescript
export const PLATFORM_DIMENSIONS: Record<string, { width: number; height: number; label: string }>;
export const APP_ICON_SIZES: readonly [1024, 512, 256, 64, 32];
```
From server/src/services/renderers/social-renderer.ts (Plan 02):
```typescript
export const PLATFORM_CHAR_LIMITS: Record<string, number>;
// "twitter-x": 280, "linkedin": 3000, "instagram-caption": 2200, "instagram-carousel": 300
```
From ui/src/hooks/useContentJob.ts:
```typescript
export function useContentJob(companyId: string | null): {
job: ContentJob | null;
submit: (jobType: string, input: Record<string, unknown>) => Promise<void>;
status: string;
};
```
Bundle types (job.bundle after status === 'done'):
```typescript
interface WallpaperBundle { type: "wallpaper-bundle"; platform: string; width: number; height: number; pngBase64: string; prompt: string; }
interface AppIconBundle { type: "app-icon-bundle"; sizes: Array<{ size: number; pngBase64: string }>; prompt: string; }
interface SocialPostBundle { type: "social-post-bundle"; platform: string; post: string; hashtags: string[]; slides?: string[]; charLimit: number; }
```
</interfaces>
<tasks>
<task type="auto">
<name>Task 1: Create WallpaperGeneratePanel and WallpaperPreview components</name>
<files>ui/src/components/WallpaperGeneratePanel.tsx, ui/src/components/WallpaperPreview.tsx</files>
<read_first>ui/src/components/DiagramGeneratePanel.tsx, ui/src/components/IconGeneratePanel.tsx, ui/src/hooks/useContentJob.ts, ui/src/api/contentJobs.ts</read_first>
<action>
1. Create WallpaperGeneratePanel.tsx following the DiagramGeneratePanel pattern:
- Props: { companyId: string }
- Define PLATFORM_DIMENSIONS as a local constant (same values as server — 12 entries). Group into categories: Desktop, Mobile, Social, App.
- State: prompt (string), platform (string, default "desktop-hd")
- Use useContentJob(companyId) hook for job submission and tracking
- Render a Card with:
- Textarea (4 rows, placeholder: "Describe the scene, mood, or concept...")
- Select component for platform. Use optgroup-style grouping (shadcn Select supports groups via SelectGroup + SelectLabel). Each option shows the label with dimensions in text-xs font-mono text-muted-foreground.
- "Generate Wallpaper" Button (primary, full-width). Disabled while job is in progress. Shows spinner + "Generating..." when active.
- Progress bar (shadcn progress) below button — driven by job.progress from SSE
- On submit: call job.submit("wallpaper", { prompt, platform })
- When job.status === "done": render WallpaperPreview with job.bundle
2. Create WallpaperPreview.tsx:
- Props: { bundle: WallpaperBundle | AppIconBundle }
- For WallpaperBundle (type === "wallpaper-bundle"):
- Render image in `max-h-80 object-contain` container
- Image alt text: prompt truncated to 100 chars
- Below image: "Download PNG" Button (primary) + resolution badge (text-xs font-mono text-muted-foreground showing "{width} x {height}")
- Download: create blob URL from base64, trigger download via anchor click
- For AppIconBundle (type === "app-icon-bundle"):
- Render a grid of size entries. Each cell: size label (e.g. "32 x 32") + Download link
- Grid uses responsive columns: 2 cols on mobile, 3 on md, 5 on lg
- Empty state: heading "No image yet", body "Describe a scene or mood and pick a platform size to generate a ready-to-use image."
Per UI spec: all copy matches the Copywriting Contract exactly.
</action>
<verify>
<automated>cd /opt/nexus/ui && npx tsc --noEmit 2>&1 | head -20</automated>
</verify>
<acceptance_criteria>
- grep "PLATFORM_DIMENSIONS" ui/src/components/WallpaperGeneratePanel.tsx
- grep "Generate Wallpaper" ui/src/components/WallpaperGeneratePanel.tsx
- grep "useContentJob" ui/src/components/WallpaperGeneratePanel.tsx
- grep "Download PNG" ui/src/components/WallpaperPreview.tsx
- grep "app-icon-bundle" ui/src/components/WallpaperPreview.tsx
- grep "No image yet" ui/src/components/WallpaperPreview.tsx
- grep "2560" ui/src/components/WallpaperGeneratePanel.tsx
</acceptance_criteria>
<done>Wallpaper panel allows prompt + platform selection, shows progress, displays result with download. App icon shows multi-size grid.</done>
</task>
<task type="auto">
<name>Task 2: Create SocialPostPanel, SocialPostResult, and extend ContentStudio tabs</name>
<files>ui/src/components/SocialPostPanel.tsx, ui/src/components/SocialPostResult.tsx, ui/src/pages/ContentStudio.tsx</files>
<read_first>ui/src/pages/ContentStudio.tsx, ui/src/components/DiagramGeneratePanel.tsx</read_first>
<action>
1. Create SocialPostPanel.tsx following DiagramGeneratePanel pattern:
- Props: { companyId: string }
- Define PLATFORM_CHAR_LIMITS as local constant: { "twitter-x": 280, "linkedin": 3000, "instagram-caption": 2200, "instagram-carousel": 300 }
- State: prompt (string), platform (string, default "twitter-x"), charCount (number tracking textarea length)
- Use useContentJob(companyId) hook
- Render Card with:
- Textarea (4 rows, placeholder: "Describe the topic or paste existing content to adapt...")
- onChange: update prompt and charCount
- Platform selector (Select): "Twitter/X", "LinkedIn", "Instagram Caption", "Instagram Carousel"
- Character count below textarea, right-aligned: `text-xs text-muted-foreground`. Format: "{N} / {limit}". When over limit: add `text-destructive` class and append " - over limit"
- Character count has `aria-live="polite"` for screen readers
- "Generate Post" Button (primary). Enabled even when over limit (AI may trim). Shows spinner + "Generating..." when active.
- Progress bar below button
- On submit: call job.submit("social-post", { prompt, platform })
- When job.status === "done": render SocialPostResult with job.bundle
2. Create SocialPostResult.tsx:
- Props: { bundle: SocialPostBundle }
- Post text in read-only Card (bg-card, rounded-lg, p-4)
- Character count badge inline (text-xs font-mono text-muted-foreground)
- "Copy Post" Button (secondary, full-width). On click: copy bundle.post to clipboard. Change text to "Copied!" for 2 seconds then revert.
- Hashtag section below copy button: render bundle.hashtags as chips in rounded-full badges (bg-muted text-muted-foreground). Each chip has role="button" and aria-label="Copy hashtag {tag}". On click: copy tag to clipboard. Show CheckCheck icon (12px) for 1.5s then revert to text.
- For carousel (bundle.slides exists and length > 0): render numbered collapsible sections using shadcn Collapsible. "Slide 1", "Slide 2" etc. Each section shows the slide text.
- Empty state: heading "No post yet", body "Describe your topic and choose a platform to generate a ready-to-publish post."
3. Update ContentStudio.tsx:
- Add two new TabsTrigger entries to the existing TabsList: "Wallpapers" (value: wallpapers), "Social" (value: social)
- Add two new TabsContent sections:
- wallpapers: renders WallpaperGeneratePanel with companyId
- social: renders SocialPostPanel with companyId
- Import WallpaperGeneratePanel and SocialPostPanel
- Follow the exact same pattern as the existing diagrams/icons/themes tabs
Per UI spec copywriting contract: match ALL copy strings exactly.
</action>
<verify>
<automated>cd /opt/nexus/ui && npx tsc --noEmit 2>&1 | head -20</automated>
</verify>
<acceptance_criteria>
- grep "PLATFORM_CHAR_LIMITS" ui/src/components/SocialPostPanel.tsx
- grep "Generate Post" ui/src/components/SocialPostPanel.tsx
- grep "aria-live" ui/src/components/SocialPostPanel.tsx
- grep "Copy Post" ui/src/components/SocialPostResult.tsx
- grep "hashtags" ui/src/components/SocialPostResult.tsx
- grep "Collapsible" ui/src/components/SocialPostResult.tsx
- grep "wallpapers" ui/src/pages/ContentStudio.tsx
- grep "social" ui/src/pages/ContentStudio.tsx
- grep "WallpaperGeneratePanel" ui/src/pages/ContentStudio.tsx
- grep "SocialPostPanel" ui/src/pages/ContentStudio.tsx
</acceptance_criteria>
<done>Social panel with character count, hashtag chips, carousel support. ContentStudio extended with Wallpapers and Social tabs.</done>
</task>
</tasks>
<verification>
- `cd /opt/nexus/ui && npx tsc --noEmit` passes
- ContentStudio shows 5 tabs: Diagrams, Icons, Themes, Wallpapers, Social
- All UI copy matches the UI spec copywriting contract
</verification>
<success_criteria>
- Wallpaper panel: prompt + 12-platform selector + generate + download
- Social panel: prompt + 4-platform selector + live character count + generate
- Social result: copy post + hashtag chips + carousel collapsibles
- ContentStudio has Wallpapers and Social tabs
- tsc compiles cleanly
</success_criteria>
<output>
After completion, create `.planning/phases/42-wallpapers-social-format-conversion-voice/42-05-SUMMARY.md`
</output>