# Video Post Queue: Project Picker, Re-render, and Composition Preview The video posting flow consists of three major components: project selection for on-demand video requests, a re-render retry control for failed drafts, and a live composition preview panel. All three integrate into the VideoPostRow and RequestVideoDialog components in `panel/src/components/dashboard/video-post-queue.tsx`. ## Project Picker in RequestVideoDialog The "Request a video" dialog now requires a project selection before the CEO can submit. The picker is built on the existing `ProjectSelector` component (from `panel/src/components/projects/project-selector.tsx`) that provides a combobox populated via `projectsApi.list()`. ### Implementation - **Location**: RequestVideoDialog component, first form field - **State**: `projectId` (string | null), initialized to `null` - **Binding**: The `ProjectSelector` renders with `value={projectId}` and `onChange={setProjectId}`, and the Request button is disabled until `projectId` is truthy - **Payload**: The `project_id` is passed as a string to `videoApi.requestVideo()` in the mutation body ### Usage in RequestVideoDialog ```typescript const [projectId, setProjectId] = useState(null); // Inside the form:
// Submit guard: const canSubmit = !!projectId && occasion.trim().length > 0 && brief.trim().length > 0 && platforms.length > 0; ``` The picker prevents submission of the video request until a project is explicitly selected, ensuring every on-demand video is scoped to a specific project. ## Re-render Control for Any Composition-Bearing Draft The `RerenderControl` component (`panel/src/components/dashboard/video-rerender-control.tsx`) appears on any draft that has both an authoring task and a proposed composition, giving the CEO a way to retry — or deliberately redo — a render without creating a new request. ### When It Appears - Rendered in `VideoPostRow` when both conditions hold: - `post.source_task_id` is truthy (the authoring task exists) - `post.composition_id` is truthy (a composition was proposed) - This is regardless of `render_status` — the backend's rerender endpoint only requires a completed authoring task with a proposed composition, not a failed render, so a healthy render can be deliberately redone too - Positioned in the draft header row, right-aligned after the occasion badge ### Visual States 1. **Idle** ("Re-render" button) — ready to click 2. **Loading** ("Re-rendering...") — mutation in flight, button disabled, spinner animating 3. **Error** ("Retry re-render") — the retry itself failed, button text and border turn red (`text-destructive` / `border-destructive`), button re-enables so the CEO can try again ### API Interaction - Calls `videoApi.rerender(authoringTaskId)` where `authoringTaskId` is the draft's `source_task_id` (the video-authoring task, NOT the draft's own task_id) - The backend endpoint is `POST /video/pipeline/{task_id}/rerender` and clears the render idempotency keys so the render loop picks up the task on its next cycle - On success, invalidates the `["video", "pipeline"]` query key and shows a success toast: "Re-render queued — it will re-pick up on the next cycle." - On error, shows an error toast with the backend message ### Implementation Detail The component uses `useMutation` from @tanstack/react-query and mirrors the pattern in the "Reject draft" dialog's mutation (error state re-enables the button, allowing the CEO to retry). ## Composition Preview Panel The `CompositionPreviewPanel` displays a live, read-only preview of the video composition (the actual HyperFrames HTML render) alongside the platform captions so the CEO can see exactly what will post before approving. ### When It Appears - Rendered in `VideoPostRow` immediately above the MP4 player - Only shows when BOTH of these are true: - `post.composition_id` is truthy - `post.source_task_id` is truthy This degrades gracefully: older drafts or drafts from versions before the backend exposed these fields simply render no preview panel (not a crash). ### Layout The panel is a two-column grid on desktop (`sm:grid-cols-2`) that stacks on mobile: 1. **Left column (desktop) / Top (mobile)**: - An `