Files
PaperClipAI/ui/src/components/issue-output/OutputVideoPlayer.tsx
T
DottaandPaperclip 96d266109b Add issue Output UI for artifact playback (PAP-10168)
Surface attachment-backed artifact work products as a first-class
Output section on the issue detail page so cloud users can watch and
download agent-generated videos without host filesystem access.

- ui/src/lib/issue-output.ts: formatBytes/formatDuration/getOutputFileGlyph
  helpers + getIssueOutputs selector that validates the Phase-2 attachment
  artifact metadata contract and tolerates malformed metadata (degraded).
- issue-output components: IssueOutputSection, OutputPrimaryCard (native
  <video>/image/generic), OutputRow, OutputVideoPlayer, OutputFileTile.
- IssueDetail: fetch work products and render the Output section between
  Documents and Attachments; reuse formatBytes in the attachments list.
- DesignGuide: showcase multiple-output, degraded, and empty states.
- Focused tests for video output, empty state, multiple outputs, and
  failed attachment metadata (15 tests).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-30 20:40:35 +00:00

34 lines
1.1 KiB
TypeScript

import { cn } from "@/lib/utils";
interface OutputVideoPlayerProps {
src: string;
poster?: string | null;
className?: string;
/** Accessible label, typically the filename. */
title?: string;
}
/**
* Thin wrapper around the native HTML5 `<video>` element with sensible
* defaults for issue outputs. We deliberately rely on the browser's native
* controls (play/pause/scrub/fullscreen/PiP) rather than building a custom
* scrubber — the backend serves byte ranges so seeking works.
*
* A fixed 16:9 box reserves height before metadata loads to avoid layout jump.
*/
export function OutputVideoPlayer({ src, poster, className, title }: OutputVideoPlayerProps) {
return (
<div className={cn("relative w-full overflow-hidden rounded-md bg-black aspect-video", className)}>
<video
src={src}
poster={poster ?? undefined}
controls
preload="metadata"
playsInline
aria-label={title ? `Video output: ${title}` : "Video output"}
className="absolute inset-0 h-full w-full"
/>
</div>
);
}