From 4151e7cd25b36e5af170b59b2e160d0b1d121c59 Mon Sep 17 00:00:00 2001 From: jlightner Date: Tue, 31 Mar 2026 02:16:36 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20deep-link=20Inspect=20Pipeline=20button?= =?UTF-8?q?=20=E2=80=94=20auto-expand=20and=20scroll=20to=20video?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TechniquePage's 'Inspect pipeline' button passes ?video= 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. --- frontend/src/pages/AdminPipeline.tsx | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/AdminPipeline.tsx b/frontend/src/pages/AdminPipeline.tsx index 6f10b80..c503b30 100644 --- a/frontend/src/pages/AdminPipeline.tsx +++ b/frontend/src/pages/AdminPipeline.tsx @@ -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([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(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(null); const [debugMode, setDebugModeState] = useState(null); + const videoRefs = useRef>(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= 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) => ( -
+
{ if (el) videoRefs.current.set(video.id, el); }}>
toggleExpand(video.id)}