--- phase: 44-video-presentations plan: 03 type: execute wave: 2 depends_on: ["44-01"] files_modified: - ui/src/hooks/useContentJob.ts - ui/src/components/PresentationPanel.tsx - ui/src/pages/ContentStudio.tsx autonomous: true requirements: - PRES-01 - PRES-02 - PRES-03 - PRES-04 must_haves: truths: - "User can enter a prompt and select pitch-deck or demo-video type to generate a presentation" - "Progress bar updates in real-time during Remotion render via SSE content_job.progress events" - "Completed presentation shows an MP4 video player and download button" - "ContentStudio has a Presentations tab that renders PresentationPanel" artifacts: - path: "ui/src/components/PresentationPanel.tsx" provides: "Presentation generation UI with prompt, type selector, progress, video player" min_lines: 80 - path: "ui/src/hooks/useContentJob.ts" provides: "Extended SSE handler that reads data.progress for fine-grained progress" contains: "data.progress" - path: "ui/src/pages/ContentStudio.tsx" provides: "Presentations tab in Content Studio" contains: "presentations" key_links: - from: "ui/src/components/PresentationPanel.tsx" to: "ui/src/hooks/useContentJob.ts" via: "useContentJob hook" pattern: "useContentJob" - from: "ui/src/pages/ContentStudio.tsx" to: "ui/src/components/PresentationPanel.tsx" via: "PresentationPanel import" pattern: "PresentationPanel" --- Extend useContentJob to surface fine-grained progress, create the PresentationPanel UI component, and add a Presentations tab to ContentStudio. Purpose: Complete the user-facing experience for generating presentations and videos with real-time progress feedback. Output: PresentationPanel.tsx, updated useContentJob.ts, updated ContentStudio.tsx @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/44-video-presentations/44-RESEARCH.md @.planning/phases/44-video-presentations/44-01-SUMMARY.md @ui/src/hooks/useContentJob.ts @ui/src/pages/ContentStudio.tsx @ui/src/components/BrandKitPanel.tsx (panel pattern reference) @ui/src/components/DocumentGeneratePanel.tsx (panel pattern reference) From ui/src/hooks/useContentJob.ts: ```typescript type JobStatus = "idle" | "queued" | "running" | "done" | "failed"; interface ContentJobState { jobId: string | null; status: JobStatus; progress: number; resultAssetId: string | null; errorMessage: string | null; } export function useContentJob(companyId: string | null): ContentJobState & { submit, reset }; ``` From ui/src/api/contentJobs.ts: ```typescript export async function submitContentJob(...): Promise<{ jobId: string }>; export async function getContentJobAsset(companyId: string, assetId: string): Promise; ``` From server/src/services/renderers/types.ts (Plan 01): ```typescript export interface PresentationBundle { type: "presentation-bundle"; presentationType: "pitch-deck" | "demo-video"; title: string; slideCount: number; durationInFrames: number; fps: number; mp4Base64: string; inputProps: Record; } ``` Task 1: Extend useContentJob to surface fine-grained SSE progress ui/src/hooks/useContentJob.ts - ui/src/hooks/useContentJob.ts (full file — understand current SSE event handler) Modify the `es.addEventListener("status", ...)` handler in `useContentJob.ts` to check for a `progress` field in the SSE payload. Update the `data` type assertion inside the status event handler to include `progress?: number`: ```typescript const data = JSON.parse(e.data as string) as { status?: string; progress?: number; resultAssetId?: string | null; errorMessage?: string | null; }; ``` Change the progress calculation to prefer the fine-grained value when present: ```typescript const progress = typeof data.progress === "number" ? data.progress : statusToProgress(status); ``` This is backward-compatible — non-video jobs that don't send `data.progress` still use the coarse `statusToProgress` mapping. Video jobs that send progress 0-100 via content_job.progress will update the progress bar in real-time. No other changes to the hook. The existing `state.progress` field is already a number 0-100. cd /opt/nexus && grep -c "data.progress" ui/src/hooks/useContentJob.ts - grep -q "progress?: number" ui/src/hooks/useContentJob.ts - grep -q "typeof data.progress" ui/src/hooks/useContentJob.ts - grep -q "statusToProgress" ui/src/hooks/useContentJob.ts useContentJob reads data.progress from SSE events for fine-grained video render progress, falls back to coarse statusToProgress for other job types Task 2: Create PresentationPanel and add Presentations tab to ContentStudio ui/src/components/PresentationPanel.tsx, ui/src/pages/ContentStudio.tsx - ui/src/components/BrandKitPanel.tsx (full — panel structure pattern) - ui/src/components/DocumentGeneratePanel.tsx (first 80 lines — simpler panel pattern) - ui/src/pages/ContentStudio.tsx (full — tab structure) - ui/src/api/contentJobs.ts (getContentJobAsset function) **ui/src/components/PresentationPanel.tsx:** Create following the BrandKitPanel pattern. Props: `{ companyId: string }`. State: - `prompt` (string) — user's description - `videoType` ("pitch-deck" | "demo-video") — defaults to "pitch-deck" - `bundle` (PresentationBundle | null) — parsed result Define `PresentationBundle` interface locally (same as server types but only the fields needed by UI): ```typescript interface PresentationBundle { type: "presentation-bundle"; presentationType: "pitch-deck" | "demo-video"; title: string; slideCount: number; durationInFrames: number; fps: number; mp4Base64: string; inputProps: Record; } ``` Use `useContentJob(companyId)` hook. Submit with `job.submit("presentation", { prompt, videoType, title: prompt.slice(0, 60) })`. When `job.status === "done"` and `job.resultAssetId` is set, fetch the asset via `getContentJobAsset(companyId, job.resultAssetId)`, fetch the URL, parse JSON as `PresentationBundle`, set to `bundle` state. Same pattern as BrandKitPanel. Layout (using shadcn Card, Button, Textarea, Progress, Select components): 1. **Card header:** "Generate Presentation" 2. **Prompt textarea:** 5 rows, placeholder "Describe the presentation you want — topic, audience, key points..." 3. **Type selector:** Two radio buttons or a Select dropdown with options "Pitch Deck" (value "pitch-deck") and "Demo Video" (value "demo-video"). Import `Select, SelectContent, SelectItem, SelectTrigger, SelectValue` from `@/components/ui/select`. 4. **Generate button:** Disabled when no prompt or generating. Shows Loader2 spinner when generating. 5. **Progress bar:** Show `` when status is "queued" or "running". Display percentage text next to it: `{job.progress}%`. This is where the fine-grained SSE progress from PRES-04 is visible. 6. **Error display:** If `job.status === "failed"`, show error message in red text. 7. **Result section (when bundle is set):** - Title and slide count info - An HTML5 ` cd /opt/nexus && grep -c "PresentationPanel" ui/src/components/PresentationPanel.tsx && grep -c "presentations" ui/src/pages/ContentStudio.tsx && grep -c "PresentationPanel" ui/src/pages/ContentStudio.tsx - grep -q "PresentationPanel" ui/src/components/PresentationPanel.tsx - grep -q "useContentJob" ui/src/components/PresentationPanel.tsx - grep -q "pitch-deck" ui/src/components/PresentationPanel.tsx - grep -q "demo-video" ui/src/components/PresentationPanel.tsx - grep -q "mp4Base64" ui/src/components/PresentationPanel.tsx - grep -q "video/mp4" ui/src/components/PresentationPanel.tsx - grep -q "job.progress" ui/src/components/PresentationPanel.tsx - grep -q 'value="presentations"' ui/src/pages/ContentStudio.tsx - grep -q "PresentationPanel" ui/src/pages/ContentStudio.tsx PresentationPanel renders prompt input, type selector, real-time progress bar, MP4 video player with download; ContentStudio has Presentations tab - `grep -q "data.progress" ui/src/hooks/useContentJob.ts` confirms fine-grained progress - `grep -q "PresentationPanel" ui/src/components/PresentationPanel.tsx` confirms panel exists - `grep -q 'value="presentations"' ui/src/pages/ContentStudio.tsx` confirms tab exists - `grep -q "video/mp4" ui/src/components/PresentationPanel.tsx` confirms MP4 playback - `pnpm --filter @paperclipai/ui exec -- npx tsc --noEmit 2>&1 | tail -3` passes - useContentJob reads data.progress for fine-grained SSE progress (PRES-04) - PresentationPanel allows prompt + type selection for pitch-deck and demo-video (PRES-01, PRES-03) - Progress bar shows real-time render percentage (PRES-04) - Completed render shows MP4 video player with download (PRES-02) - ContentStudio has 8 tabs including Presentations - All changes follow existing codebase patterns (Card, useContentJob, getContentJobAsset) After completion, create `.planning/phases/44-video-presentations/44-03-SUMMARY.md`