nexus/.planning/phases/43-documents-branding/43-03-PLAN.md

291 lines
13 KiB
Markdown

---
phase: 43-documents-branding
plan: 03
type: execute
wave: 3
depends_on: ["43-01", "43-02"]
files_modified:
- ui/src/components/DocumentGeneratePanel.tsx
- ui/src/components/BrandKitPanel.tsx
- ui/src/components/BrandKitResult.tsx
- ui/src/pages/ContentStudio.tsx
autonomous: false
requirements: [DOC-01, DOC-02, DOC-03, BRAND-01, BRAND-02, BRAND-03, BRAND-04, BRAND-05, BRAND-06]
must_haves:
truths:
- "User can select document type (report, invoice, one-pager, api-docs) and generate a PDF"
- "Generated PDF can be downloaded from the UI"
- "User can describe a brand and generate a full brand kit"
- "Brand kit result shows logo, avatars, social images, signature, letterhead previews"
- "User can download the entire brand kit as a ZIP file"
- "ContentStudio has Documents and Brand tabs alongside existing tabs"
artifacts:
- path: "ui/src/components/DocumentGeneratePanel.tsx"
provides: "Document type selection, prompt input, PDF generation trigger, download"
min_lines: 60
- path: "ui/src/components/BrandKitPanel.tsx"
provides: "Brand prompt input, generation trigger, result display"
min_lines: 50
- path: "ui/src/components/BrandKitResult.tsx"
provides: "Brand kit display: logo, avatars, social images, templates, guidelines, ZIP download"
min_lines: 80
- path: "ui/src/pages/ContentStudio.tsx"
provides: "Documents and Brand tabs added"
contains: "documents"
key_links:
- from: "ui/src/components/DocumentGeneratePanel.tsx"
to: "useContentJob"
via: "submit('pdf-document', { docType, prompt, title })"
pattern: "pdf-document"
- from: "ui/src/components/BrandKitPanel.tsx"
to: "useContentJob"
via: "submit('brand-kit', { prompt })"
pattern: "brand-kit"
- from: "ui/src/pages/ContentStudio.tsx"
to: "ui/src/components/DocumentGeneratePanel.tsx"
via: "import + TabsContent"
pattern: "DocumentGeneratePanel"
---
<objective>
UI panels for document generation and brand kit, wired into ContentStudio tabs.
Purpose: Give users a UI to generate PDFs (reports, invoices, one-pagers, API docs) and complete brand identity kits from a prompt. Adds "Documents" and "Brand" tabs to ContentStudio.
Output: DocumentGeneratePanel, BrandKitPanel, BrandKitResult components, updated ContentStudio.
</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/STATE.md
@.planning/phases/43-documents-branding/43-RESEARCH.md
@.planning/phases/43-documents-branding/43-01-SUMMARY.md
@.planning/phases/43-documents-branding/43-02-SUMMARY.md
@ui/src/pages/ContentStudio.tsx
@ui/src/components/SocialPostPanel.tsx
@ui/src/hooks/useContentJob.ts
@ui/src/api/contentJobs.ts
<interfaces>
<!-- Key types and patterns the executor needs -->
From server/src/services/renderers/types.ts (Plan 01):
```typescript
export interface PdfDocumentBundle {
type: "pdf-document-bundle";
docType: string;
title: string;
pdfBase64: string;
}
export interface BrandKitBundle {
type: "brand-kit-bundle";
spec: { name: string; tagline: string; primaryColor: string; secondaryColor: string; fontStyle: string; industry: string; };
logoSvgBase64: string;
avatarPngs: Record<string, string>; // "512"|"256"|"128"|"64"|"32" -> base64
socialImages: Record<string, string>; // "twitter-profile" etc -> base64
signatureHtml: string;
letterheadHtml: string;
guidelinesPdfBase64: string;
zipBase64: string;
}
```
From ui/src/hooks/useContentJob.ts:
```typescript
export function useContentJob(companyId: string): {
submit: (jobType: string, input: Record<string, unknown>) => void;
status: string | null;
bundle: unknown;
resultAssetId: string | null;
error: string | null;
};
```
From ui/src/api/contentJobs.ts:
```typescript
export function getContentJobAsset(companyId: string, assetId: string): Promise<{ url: string }>;
```
UI pattern from SocialPostPanel.tsx:
- useContentJob for submit + SSE progress
- When status="done" && resultAssetId && !bundle -> fetch asset -> JSON.parse -> setBundle
- Display result component
- Download via URL.createObjectURL + <a>.click()
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Create DocumentGeneratePanel and BrandKitPanel + BrandKitResult components</name>
<files>ui/src/components/DocumentGeneratePanel.tsx, ui/src/components/BrandKitPanel.tsx, ui/src/components/BrandKitResult.tsx</files>
<read_first>
- ui/src/components/SocialPostPanel.tsx (full file — reference pattern for useContentJob, asset fetch, bundle state, download trigger)
- ui/src/components/SocialPostResult.tsx (result display pattern)
- ui/src/hooks/useContentJob.ts (hook API)
- ui/src/api/contentJobs.ts (getContentJobAsset)
- ui/src/components/WallpaperGeneratePanel.tsx (another panel example for download pattern)
</read_first>
<action>
**DocumentGeneratePanel.tsx:**
- Props: `{ companyId: string }`
- State: prompt (string), title (string), docType ("report" | "invoice" | "one-pager" | "api-docs"), bundle (PdfDocumentBundle | null)
- Define PdfDocumentBundle type locally (same pattern as SocialPostBundle in SocialPostPanel — bundle types are defined locally in panel files per project convention for wallpaper/social, BUT research says to use types.ts for these. Follow research: import is server-side only. Define locally in UI file matching the server shape.)
- useContentJob(companyId) for submit + status tracking
- UI layout using shadcn Card, CardHeader, CardContent:
- Select dropdown for docType with 4 options: Report, Invoice, One-Pager, API Documentation
- Input for title
- Textarea for prompt/content description
- Generate button (disabled when loading, shows Loader2 spinner)
- Progress bar when status is "running" or "queued"
- When status="done" && resultAssetId && !bundle: fetch asset via getContentJobAsset, parse JSON, setBundle
- When bundle exists: show title, docType badge, and a "Download PDF" button
- Download PDF: decode pdfBase64 -> Uint8Array -> Blob("application/pdf") -> URL.createObjectURL -> <a download="{title}.pdf">.click() -> revokeObjectURL
**BrandKitPanel.tsx:**
- Props: `{ companyId: string }`
- State: prompt (string), bundle (BrandKitBundle | null)
- Define BrandKitBundle type locally (matching server shape)
- useContentJob(companyId) for submit + status
- UI layout:
- Textarea for brand description prompt (placeholder: "Describe the brand you want to create -- name, industry, style preferences, colors...")
- Generate button with loading state
- Progress bar during generation
- When done: fetch asset, parse JSON, setBundle, render BrandKitResult
**BrandKitResult.tsx:**
- Props: `{ bundle: BrandKitBundle }`
- Display in a grid layout:
- **Logo section**: SVG preview via `<img src="data:image/svg+xml;base64,{logoSvgBase64}" />`
- **Avatars section**: Grid of 5 avatar sizes as `<img src="data:image/png;base64,{avatarPngs[size]}" />` with size labels
- **Social images section**: Grid of social platform images with platform name labels
- **Templates section**: "Email Signature" and "Letterhead" cards with HTML preview in sandboxed iframes (`srcdoc={signatureHtml}`)
- **Guidelines**: "Brand Guidelines" card with a "Download PDF" button (decode guidelinesPdfBase64 -> blob download)
- **Full Kit Download**: Prominent "Download Brand Kit (ZIP)" button at the bottom
- ZIP download function: same base64-to-blob pattern but with type "application/zip" and `.zip` extension (use the downloadZip pattern from RESEARCH.md)
- PDF download for guidelines: same base64-to-blob pattern with type "application/pdf"
Use shadcn components: Card, CardHeader, CardTitle, CardContent, Button, Textarea, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Progress. Import Loader2 from lucide-react.
</action>
<acceptance_criteria>
- grep -q "DocumentGeneratePanel" ui/src/components/DocumentGeneratePanel.tsx
- grep -q "pdf-document" ui/src/components/DocumentGeneratePanel.tsx
- grep -q "BrandKitPanel" ui/src/components/BrandKitPanel.tsx
- grep -q "brand-kit" ui/src/components/BrandKitPanel.tsx
- grep -q "BrandKitResult" ui/src/components/BrandKitResult.tsx
- grep -q "zipBase64" ui/src/components/BrandKitResult.tsx
- grep -q "Download Brand Kit" ui/src/components/BrandKitResult.tsx
</acceptance_criteria>
<verify>
<automated>pnpm --filter @paperclipai/ui exec tsc --noEmit</automated>
</verify>
<done>Document and brand kit panels render with correct form inputs, submit correct job types, display results, and provide download functionality for PDF and ZIP.</done>
</task>
<task type="auto">
<name>Task 2: Add Documents and Brand tabs to ContentStudio</name>
<files>ui/src/pages/ContentStudio.tsx</files>
<read_first>
- ui/src/pages/ContentStudio.tsx (current tab structure — 5 tabs: diagrams, icons, themes, wallpapers, social)
</read_first>
<action>
1. Add imports at the top of ContentStudio.tsx:
```typescript
import { DocumentGeneratePanel } from "../components/DocumentGeneratePanel";
import { BrandKitPanel } from "../components/BrandKitPanel";
```
2. Add two new TabsTrigger entries in the TabsList, AFTER the "Social" trigger:
```tsx
<TabsTrigger value="documents">Documents</TabsTrigger>
<TabsTrigger value="brand">Brand</TabsTrigger>
```
3. Add two new TabsContent blocks after the social TabsContent:
```tsx
<TabsContent value="documents" className="mt-4">
{companyId ? (
<DocumentGeneratePanel companyId={companyId} />
) : (
<p className="text-sm text-muted-foreground">Select a company to get started.</p>
)}
</TabsContent>
<TabsContent value="brand" className="mt-4">
{companyId ? (
<BrandKitPanel companyId={companyId} />
) : (
<p className="text-sm text-muted-foreground">Select a company to get started.</p>
)}
</TabsContent>
```
Keep `defaultValue="diagrams"` unchanged. Use stable string values "documents" and "brand" for tab IDs.
</action>
<acceptance_criteria>
- grep -q "DocumentGeneratePanel" ui/src/pages/ContentStudio.tsx
- grep -q "BrandKitPanel" ui/src/pages/ContentStudio.tsx
- grep -q '"documents"' ui/src/pages/ContentStudio.tsx
- grep -q '"brand"' ui/src/pages/ContentStudio.tsx
</acceptance_criteria>
<verify>
<automated>pnpm --filter @paperclipai/ui exec tsc --noEmit</automated>
</verify>
<done>ContentStudio has 7 tabs: Diagrams, Icons, Themes, Wallpapers, Social, Documents, Brand. TypeScript compiles cleanly.</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<name>Task 3: Visual verification of Documents and Brand tabs</name>
<files>ui/src/pages/ContentStudio.tsx</files>
<action>
Human verifies the complete document generation and brand kit UI end-to-end.
How to verify:
1. Navigate to Content Studio in the Nexus UI
2. Verify 7 tabs visible: Diagrams, Icons, Themes, Wallpapers, Social, Documents, Brand
3. Click "Documents" tab:
- Select "Report" from dropdown
- Enter a title and prompt
- Click Generate — verify spinner/progress appears
- When done, verify "Download PDF" button appears
4. Click "Brand" tab:
- Enter a brand description
- Click Generate — verify progress appears
- When done, verify logo SVG preview, avatar grid, social images, template previews, and "Download Brand Kit (ZIP)" button
5. Download the ZIP — verify it contains logo/, social/, templates/, guidelines.pdf
Resume signal: Type "approved" or describe issues.
</action>
<verify>
<automated>pnpm --filter @paperclipai/ui exec tsc --noEmit</automated>
</verify>
<done>User confirms Documents and Brand tabs work end-to-end: PDF generation + download, brand kit generation + preview + ZIP download.</done>
</task>
</tasks>
<verification>
- `pnpm --filter @paperclipai/ui exec tsc --noEmit` compiles without errors
- ContentStudio renders 7 tabs
- Document generation flow works end-to-end
- Brand kit generation shows all asset previews and ZIP download
</verification>
<success_criteria>
- Documents tab: user can select type, enter prompt, generate PDF, download it
- Brand tab: user can enter description, generate full brand kit, see all previews, download ZIP
- All 7 ContentStudio tabs render correctly
- TypeScript clean
</success_criteria>
<output>
After completion, create `.planning/phases/43-documents-branding/43-03-SUMMARY.md`
</output>