fix: jobless video assets no longer 500 the library click

Legacy/edge-case video_assets rows without a processing job threw 'has no
processing job' from getVideoStatus — the library detail click 500ed.
VideoStatus.processingJob is now nullable end to end (api type, DTO, web
consumers null-guarded); a jobless asset renders as queued.
This commit is contained in:
glass beadian 2026-10-18 08:19:42 +00:00
parent 215952fe5e
commit 31c97a0b0a
13 changed files with 95 additions and 25 deletions

View file

@ -119,7 +119,7 @@ export default function VideoPage(): React.ReactElement {
const applyStatus = useCallback((status: VideoStatusDto): void => {
const entry: VideoLibraryEntryDto = {
videoAsset: status.videoAsset,
processingState: status.processingJob.state,
processingState: status.processingJob?.state ?? "queued",
publications: [],
};
setItems((current) => [entry, ...(current ?? []).filter((e) => e.videoAsset.id !== entry.videoAsset.id)]);

View file

@ -247,9 +247,9 @@ export function ProducerPanel({ entry, live, canManage }: Props): React.ReactEle
}
};
const producedInFlight = producedStatus !== null && IN_FLIGHT.includes(producedStatus.processingJob.state);
const producedComplete = producedStatus?.processingJob.state === "complete";
const producedFailed = producedStatus?.processingJob.state === "failed";
const producedInFlight = producedStatus !== null && IN_FLIGHT.includes(producedStatus.processingJob?.state ?? "queued");
const producedComplete = producedStatus?.processingJob?.state === "complete";
const producedFailed = producedStatus?.processingJob?.state === "failed";
return (
<section>
@ -494,7 +494,7 @@ export function ProducerPanel({ entry, live, canManage }: Props): React.ReactEle
)}
{producedFailed && (
<p className="text-2xs text-danger">
The produced asset failed: {producedStatus?.processingJob.error ?? "see the job above."}
The produced asset failed: {producedStatus?.processingJob?.error ?? "see the job above."}
</p>
)}
{producedInFlight && (

View file

@ -173,7 +173,7 @@ export function UploadDropzone({ live, canUpload, onStarted, onSettled, onUpload
const status = await uploadOne(item, (progress) => patch(item.id, { progress }));
// The bytes landed; a failed probe is a processing result, not an
// upload failure — the library row carries it, so surface it inline.
const processingError = status.processingJob.state === "failed" ? status.processingJob.error ?? null : null;
const processingError = status.processingJob?.state === "failed" ? status.processingJob?.error ?? null : null;
patch(item.id, {
state: "done",
progress: 100,

View file

@ -49,7 +49,7 @@ export function VideoDetailDrawer({
}, [onClose]);
// Poll the job while it is in flight; the library page refreshes from this too.
const inFlight = IN_FLIGHT.includes(status?.processingJob.state ?? entry.processingState);
const inFlight = IN_FLIGHT.includes(status?.processingJob?.state ?? entry.processingState);
useEffect(() => {
let cancelled = false;
const fetchStatus = (): void => {
@ -129,7 +129,7 @@ export function VideoDetailDrawer({
>
<span className={`h-1.5 w-1.5 shrink-0 rounded-full ${done || current ? "bg-current" : "bg-border"}`} />
{step.label}
{current && job.progressPercent !== undefined && (
{current && job?.progressPercent !== undefined && (
<span className="ml-auto font-mono text-2xs">{job.progressPercent}%</span>
)}
</li>
@ -138,11 +138,11 @@ export function VideoDetailDrawer({
{state === "failed" && (
<li className="rounded-md border border-danger/30 bg-danger/10 px-2.5 py-1.5 text-xs text-danger">
<span className="font-medium">failed</span>
{job.error !== undefined && <span className="ml-1 font-mono text-2xs">{job.error}</span>}
{job?.error !== undefined && <span className="ml-1 font-mono text-2xs">{job.error}</span>}
</li>
)}
</ol>
{job.processingNote !== undefined && <p className="mt-2 text-2xs text-text-faint">{job.processingNote}</p>}
{job?.processingNote !== undefined && <p className="mt-2 text-2xs text-text-faint">{job.processingNote}</p>}
</section>
)}

View file

@ -54,7 +54,7 @@ export interface VideoLibraryPageDto {
export interface VideoStatusDto {
videoAsset: VideoAssetDto;
processingJob: VideoProcessingJobDto;
processingJob: VideoProcessingJobDto | null;
}
/**