* [7f2c881a] Project-scope video pipeline + re-render + preview proxy (#386) (#396) * [7f2c881a] feat(video): scope on-demand video requests + render loop to project_id Require project_id on VideoRequestBody (404 when unresolvable or not opted into the video engine), thread it through VideoEngine.open_video_task via a shared resolve_authoring_project helper, and resolve the render loop's motion/ workspace from the authoring task's own project_id instead of the hardcoded self_heal_project_slug. * [7f2c881a] fix(video): cast task.id to UUID before VideoEngine.rerender calls mypy flagged task.id as sqlalchemy.sql.sqltypes.UUID[Any] rather than uuid.UUID in the three rerender tests; cast to UUID per the codebase's established idiom (cast("UUID", obj.id)) used elsewhere for the same SQLAlchemy Mapped-attribute inference gap. * [7f2c881a] docs(video): API endpoints for project-scoped requests, re-render, and preview proxy Add comprehensive API documentation for the new project-scoped video engine endpoints: - POST /api/video/request: on-demand video authoring scoped to project_id (breaking change) - POST /api/video/pipeline/{task_id}/rerender: CEO-triggered re-render with idempotency key clearing - GET /api/video/preview/{task_id}/{file_path}: CEO preview proxy with path-traversal confinement Document project-scoping architecture: authoring tasks and render loop now resolve from task's own project_id instead of hardcoded self_heal_project_slug. Add migration guide covering breaking change to VideoRequestBody schema (project_id now required), error handling changes (404 on unresolvable/non-opted-in projects), and client migration steps. --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> * [8f959c3b] docs(ux_ui): add project picker, re-render control, and composition preview panel spec (#381) (#398) Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> * [1fb5b5cb] Project picker, re-render button, and composition preview panel (#397) (#402) * [1fb5b5cb] feat(video): project picker, re-render button, and composition preview panel * [1fb5b5cb] docs(video): add comprehensive guide for project picker, re-render button, and composition preview panel --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> * [a512f364] Add video_engine_enabled to ProjectSummaryResponse (#412) (#414) * [a512f364] feat(api): surface video_engine_enabled on ProjectSummaryResponse * [a512f364] docs(api): document video_engine_enabled on ProjectSummaryResponse --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> * [03607ab9] Fix re-render control gating/placement and project picker filter (#434) * [f2f3e89f] Fix RerenderControl gating/placement across queue and strip views (#431) * [f2f3e89f] feat(video): widen RerenderControl gating and share it across queue/strip views Extracts RerenderControl into a shared panel/src/components/dashboard/ video-rerender-control.tsx component, widens its gate from render_status === 'failed' to source_task_id + composition_id present (matching what the backend rerender endpoint actually requires), adds a confirm dialog before firing the mutation, and wires the same component into video-pipeline-strip.tsx for still-in-flight rendering/render_failed rows. * [f2f3e89f] docs(video): enhance RerenderControl JSDoc with gating logic and usage examples Add comprehensive JSDoc to the RerenderControl component covering its purpose, gating logic (render for any source_task_id + composition_id, regardless of render_status), three visual button states (idle/loading/ error), confirm-dialog guard behavior, and usage examples for both video-post-queue.tsx and video-pipeline-strip.tsx contexts. Explains why the backend's rerender endpoint doesn't require a failed render and how the component prevents accidental re-renders. --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> * [404d8ed3] Filter project picker to video-engine-enabled projects (#432) * [404d8ed3] feat(panel): filter video-request project picker to opted-in projects Add video_engine_enabled to the client ProjectSummary type, give ProjectSelector a videoEngineOnly filter prop, default RequestVideoDialog's picker to the current video-enabled project with a friendly empty-state when none exist, and cover the filter with a new project-selector test. * [404d8ed3] docs(panel): add ProjectSelector component API reference with videoEngineOnly filter Document the reusable ProjectSelector component with its props, filtering behavior, and new videoEngineOnly filter for video-engine-enabled projects. Follows the existing component documentation pattern from page-refresh-provider. --------- Co-authored-by: Frontend Developer 2 <fe-dev-2@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> * [519a4088] fix(panel): import missing RerenderControl in video-post-queue and correct stale doc (#436) Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> --------- Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> Co-authored-by: Frontend Developer 2 <fe-dev-2@roboco.tech> * [8e912c3e] Reflow hard-wrapped video UX design doc to pass quality gate (#439) * [ccfe2015] docs(ux_ui): reflow video request composition-controls spec to one line per paragraph (#438) Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> * [99c3ed9c] docs(backend): reflow hard-wrapped prose in video-engine-endpoints.md and video-project-scoping.md (#442) Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> * [c2e98fc0] docs(backend): strip stray trailing whitespace in video-engine-endpoints.md fence (#451) Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> * [9c7bc11a] Reflow all 3 hard-wrapped docs on this branch and verify quality gate (#457) * [9c7bc11a] test(scripts): guard reflow_md.py --check wiring into make quality * [9c7bc11a] docs(standards): document markdown reflow quality gate workflow and verification Added comprehensive guide explaining the one-logical-unit-per-line markdown prose standard, how the reflow check integrates into make quality, the three reflowed files (video-engine-endpoints.md, video-project-scoping.md, composition-controls spec), and the regression test added to ensure wiring stability. This task verifies all three ACs are satisfied: reflow_md.py --check exits 0, make quality passes (non-DB portions), and the three files are whitespace-only reflowed. --------- Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> Co-authored-by: UX/UI Documenter <ux-doc@roboco.tech> --------- Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> Co-authored-by: UX/UI Documenter <ux-doc@roboco.tech> * [002f0cdd] docs(rag): document reflow-check zero-diff troubleshooting path (#459) (#460) Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> * [e4ed92d6] fix rerender missing-task test — assert the empty queue it creates The test never seeds; the trailing assertion expected a phantom video post. Broken since the branch's first commit but never executed — every earlier CI run short-circuited at a pre-pytest gate step. --------- Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech> Co-authored-by: Backend Documenter <be-doc@roboco.tech> Co-authored-by: UX/UI Developer 1 <ux-dev-1@roboco.tech> Co-authored-by: Frontend Developer 1 <fe-dev-1@roboco.tech> Co-authored-by: Frontend Documenter <fe-doc@roboco.tech> Co-authored-by: Frontend Developer 2 <fe-dev-2@roboco.tech> Co-authored-by: UX/UI Documenter <ux-doc@roboco.tech> Co-authored-by: Renn F <rennf93@users.noreply.github.com>
7.5 KiB
ProjectSelector component
A reusable dropdown selector for picking a project, with optional filtering by team and video-engine opt-in status.
Purpose
Panels and dialogs that need to let users pick a project use ProjectSelector to display and filter the list. The component fetches the project list via the useProjects hook, groups projects by their assigned team, and optionally filters by team membership or video-engine enablement.
Files
| File | Role |
|---|---|
panel/src/components/projects/project-selector.tsx |
Main component and ProjectSelectorProps type. |
panel/src/components/projects/__tests__/project-selector.test.tsx |
Component tests (filter, grouping). |
panel/src/lib/api/projects.ts |
useProjects hook and mock-mode project data. |
panel/src/types/index.ts |
ProjectSummary type (includes video_engine_enabled). |
API
ProjectSelectorProps
interface ProjectSelectorProps {
// The currently selected project ID (null for unselected)
value: string | null;
// Called when the user selects a project
onChange: (projectId: string | null) => void;
// Placeholder text shown when no project is selected
placeholder?: string;
// Filter to projects assigned to a specific team (optional)
filterByTeam?: Team;
// Disable the selector (optional, default: false)
disabled?: boolean;
// Whether to clear the selection when the user deselects (optional, default: true)
allowClear?: boolean;
// Restrict the list to projects with video_engine_enabled = true (optional, default: false)
videoEngineOnly?: boolean;
}
Props explained:
value— the currently selected project's ID, ornullif unselected. The selector updates this viaonChange.onChange— callback fired when the user selects a project (receives the project ID) or clears the selection (receivesnullifallowClearis true).placeholder— text shown in the trigger button whenvalueisnull. Defaults to "Select a project...".filterByTeam— if set, only show projects assigned to this team (e.g.Team.FRONTEND). Useful when an operation is scoped to a single team.disabled— if true, the dropdown is unclickable and grayed out.allowClear— if true (default), the user can click a clear button to setvaluetonulland triggeronChange(null). If false, a selection is mandatory.videoEngineOnly— if true, filter the project list to only projects withvideo_engine_enabled === true. Used by video-authoring flows (e.g.RequestVideoDialog) to show only opted-in projects.
ProjectSummary
The shape of each project in the list:
interface ProjectSummary {
id: string;
name: string;
slug: string;
git_url: string;
assigned_cell: Team;
is_active: boolean;
has_workspace: boolean;
has_git_token: boolean;
video_engine_enabled: boolean;
}
video_engine_enabled— whether the project has opted into the video engine. Used by thevideoEngineOnlyfilter.
How to use
Basic usage
Pick any project in the list:
"use client";
import { useState } from "react";
import { ProjectSelector } from "@/components/projects/project-selector";
export default function MyComponent() {
const [projectId, setProjectId] = useState<string | null>(null);
return (
<div>
<ProjectSelector
value={projectId}
onChange={setProjectId}
placeholder="Select a project..."
/>
{projectId && <p>You picked: {projectId}</p>}
</div>
);
}
Filter by team
Show only projects in a specific team:
import { Team } from "@/types";
<ProjectSelector
value={projectId}
onChange={setProjectId}
filterByTeam={Team.FRONTEND}
placeholder="Select a frontend project..."
/>
Filter by video-engine opt-in
Show only projects that have opted into the video engine (for video-authoring dialogs):
<ProjectSelector
value={projectId}
onChange={setProjectId}
videoEngineOnly
placeholder="Select a project with video enabled..."
/>
Mandatory selection
Disable the clear button so the user must pick a project:
<ProjectSelector
value={projectId}
onChange={setProjectId}
allowClear={false}
placeholder="Project (required)"
/>
Combine filters
Both team and video-engine filters can be used together:
<ProjectSelector
value={projectId}
onChange={setProjectId}
filterByTeam={Team.BACKEND}
videoEngineOnly
placeholder="Backend projects with video enabled..."
/>
Filtering order
Filters are applied in this order:
- Video-engine filter (if
videoEngineOnlyis true) — keep only projects withvideo_engine_enabled === true. - Team filter (if
filterByTeamis set) — keep only projects assigned to the given team. - Grouping — group the remaining projects by their assigned team for display.
This ensures a video-engine-filtered list still groups correctly, and a team-filtered list can still have the video-engine filter applied on top.
Empty states
The component handles empty states gracefully:
- No projects in the data source — the dropdown shows the placeholder and is disabled.
- All projects filtered out — the dropdown shows the placeholder and is disabled. This happens when all available projects are filtered by
videoEngineOnlyorfilterByTeam. - No projects with video enabled (when
videoEngineOnlyis true) — a calling dialog (likeRequestVideoDialog) should checkvideoProjects.lengthand show a friendly empty-state message to the user.
Loading state
The selector uses the useProjects hook, which may return isLoading: true while fetching the project list. The selector passes this through as a disabled state until the data arrives.
Design decisions
- Radix UI Select: built on Radix's
<Select>primitive for accessibility (keyboard navigation, ARIA labels). - TanStack Query caching: projects are fetched once via
useProjectsand cached, so multiple selectors on the same page share the same query. - Usable without TypeScript: the component type-checks all inputs but can be used from plain JS contexts (e.g. test fixtures) by omitting types.
- Memoized grouping: the grouping computation is memoized with a dependency array so re-renders don't re-compute groups unless the filter props change.
Testing
Run the selector tests with:
cd panel
pnpm test project-selector
Covered behaviors:
videoEngineOnlyfilters out projects withvideo_engine_enabled === false.- Without
videoEngineOnly, all projects appear in the list. - Team filtering works independently and in combination with video-engine filtering.
- Selecting a project calls
onChangewith the project ID. - Clearing a selection (when
allowClearis true) callsonChange(null). - The selector is disabled when
disabledprop is true or while data is loading. - The selector is disabled when all projects are filtered out.
Related work
- RequestVideoDialog (
panel/src/components/dashboard/video-post-queue.tsx) — usesvideoEngineOnlyto show only projects that have opted into the video engine for on-demand video requests. - ProjectsPage (
app/(dashboard)/projects/page.tsx) — uses the base selector without filters to list and manage all projects.
Migration / rollout
No breaking changes. The new videoEngineOnly prop defaults to false, so existing uses of ProjectSelector are unaffected. New code that needs to filter by video-engine opt-in status should pass videoEngineOnly={true}.