+
+ {viewport && {viewport}}
+ {dimensions && {dimensions}}
+ {preview.type === 'image' ? 'Screenshot' : 'Video'}
+
+ ;
+}
/**
- * Visual evidence published by this specific task run, in a fixed-height canvas: the framed capture
- * on the left, what it shows on the right, a switcher between captures and a lightbox for the pixels.
+ * Visual evidence published by this specific task run, stacked for inspection: a header with the
+ * capture switcher and Fit/100% controls, a one-line context strip, and a full-width imaging stage
+ * that shows the whole capture (Fit) or its pixels 1:1 in a scrollable pane (100%).
* Renders nothing when the run published none.
*/
export default function TaskVisualPreviews({ previews, className = 'border-b border-slate-200 px-4 py-3' }: {
@@ -43,93 +148,65 @@ export default function TaskVisualPreviews({ previews, className = 'border-b bor
// Tracked by url, not index: polling refreshes reorder and replace the list.
const [selectedUrl, setSelectedUrl] = useState(null);
const [lightboxUrl, setLightboxUrl] = useState(null);
+ const [zoomMode, setZoomMode] = useState('fit');
const opener = useRef(null);
+ const stageRef = useRef(null);
const selected = media.find(preview => preview.url === selectedUrl) ?? media[0];
- // Videos keep their native controls in the frame; only images form the lightbox sequence.
+ // Videos keep their native controls on the stage; only images form the lightbox sequence.
const images = media.filter(preview => preview.type === 'image');
const lightboxIndex = lightboxUrl === null ? -1 : images.findIndex(image => image.url === lightboxUrl);
useEffect(() => {
if (lightboxUrl !== null && lightboxIndex < 0) setLightboxUrl(null);
}, [lightboxUrl, lightboxIndex]);
+
+ const dimensions = useCapturedSize(stageRef, selected?.url);
if (!selected) return null;
const openLightbox = (trigger: HTMLButtonElement) => {
opener.current = trigger;
setLightboxUrl(selected.url);
};
- const viewport = captureViewport(selected);
- const width = captureWidth(selected);
const isImage = selected.type === 'image';
+ const actual = isImage && zoomMode === 'actual';
return
-