import { useCallback, useEffect, useState } from "react"; import { Link } from "react-router-dom"; import { listRuns } from "../api"; import type { RunSummary } from "../types"; import { useSse } from "../hooks/useSse"; function formatDuration(milliseconds?: number): string { if (!milliseconds) { return "-"; } if (milliseconds < 1000) { return `${milliseconds}ms`; } const seconds = milliseconds / 1000; if (seconds < 60) { return `${seconds.toFixed(1)}s`; } const minutes = Math.floor(seconds / 60); const remainingSeconds = Math.round(seconds - minutes * 60); return `${minutes}m${remainingSeconds}s`; } function formatStartedAt(value: string): string { const date = new Date(value); if (Number.isNaN(date.getTime())) { return value; } return date.toLocaleString(); } function basename(specPath: string): string { const index = specPath.lastIndexOf("/"); return index >= 0 ? specPath.slice(index + 1) : specPath; } export default function RunList() { const [runs, setRuns] = useState(null); const [error, setError] = useState(null); const refresh = useCallback(() => { listRuns() .then(setRuns) .catch((failure: unknown) => { setError(failure instanceof Error ? failure.message : String(failure)); }); }, []); useEffect(() => { refresh(); }, [refresh]); useSse("runs.changed", refresh); if (error) { return
failed to load runs: {error}
; } if (!runs) { return
loading runs...
; } if (runs.length === 0) { return
no runs yet. run `sanderling test` to produce some.
; } return ( {runs.map((run) => ( ))}
started spec seed platform duration steps violations
{formatStartedAt(run.started_at)} {basename(run.spec_path)} {run.seed} {run.platform} {formatDuration(run.duration_millis)} {run.step_count} {run.violation_count > 0 ? ( {run.violation_count} ) : ( "0" )} {run.in_progress ? in progress : null}
); }