feat: deep-link Inspect Pipeline button — auto-expand and scroll to video

TechniquePage's 'Inspect pipeline' button passes ?video=<id> to AdminPipeline.
Previously the query param was ignored. Now AdminPipeline reads it on mount,
auto-expands the matching video row, and smooth-scrolls it into view.
This commit is contained in:
jlightner 2026-03-31 02:16:36 +00:00
parent 4b0914b12b
commit 4151e7cd25

View file

@ -3,7 +3,8 @@
* expandable event log with token usage and collapsible JSON viewer.
*/
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { useSearchParams } from "react-router-dom";
import {
fetchPipelineVideos,
fetchPipelineEvents,
@ -448,6 +449,7 @@ function StatusFilter({
// ── Main Page ────────────────────────────────────────────────────────────────
export default function AdminPipeline() {
const [searchParams] = useSearchParams();
const [videos, setVideos] = useState<PipelineVideoItem[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
@ -456,6 +458,8 @@ export default function AdminPipeline() {
const [actionMessage, setActionMessage] = useState<{ id: string; text: string; ok: boolean } | null>(null);
const [activeFilter, setActiveFilter] = useState<string | null>(null);
const [debugMode, setDebugModeState] = useState<boolean | null>(null);
const videoRefs = useRef<Map<string, HTMLDivElement>>(new Map());
const deepLinked = useRef(false);
const load = useCallback(async () => {
setLoading(true);
@ -474,6 +478,22 @@ export default function AdminPipeline() {
void load();
}, [load]);
// Deep-link: auto-expand and scroll to ?video=<id> on first load
useEffect(() => {
if (deepLinked.current || loading || videos.length === 0) return;
const targetVideoId = searchParams.get("video");
if (!targetVideoId) return;
const match = videos.find((v) => v.id === targetVideoId);
if (!match) return;
deepLinked.current = true;
setExpandedId(targetVideoId);
// Scroll after the expanded detail renders
requestAnimationFrame(() => {
const el = videoRefs.current.get(targetVideoId);
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
});
}, [loading, videos, searchParams]);
useEffect(() => {
let cancelled = false;
fetchDebugMode()
@ -568,7 +588,7 @@ export default function AdminPipeline() {
{videos
.filter((v) => activeFilter === null || v.processing_status === activeFilter)
.map((video) => (
<div key={video.id} className="pipeline-video">
<div key={video.id} className="pipeline-video" ref={(el) => { if (el) videoRefs.current.set(video.id, el); }}>
<div
className="pipeline-video__header"
onClick={() => toggleExpand(video.id)}