From 9beb63f9212531787c97eb6df2b3dca49b9571ba Mon Sep 17 00:00:00 2001 From: mccahan Date: Thu, 22 Jan 2026 15:30:30 -0700 Subject: [PATCH] Deep link the tabs in the recording view --- web/src/pages/Events.tsx | 8 +++++- web/src/views/recording/RecordingView.tsx | 34 ++++++++++++++++++++--- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/web/src/pages/Events.tsx b/web/src/pages/Events.tsx index 02b3eee1c..af947a365 100644 --- a/web/src/pages/Events.tsx +++ b/web/src/pages/Events.tsx @@ -111,7 +111,7 @@ export default function Events() { const [recording, setRecording] = useOverlayState( "recording", undefined, - false, + true, // preserveSearch: keep URL params for deep linking ); // Wrapper to update URL with review ID for deep linking when opening a recording. @@ -145,6 +145,12 @@ export default function Events() { useState("timeline"); useSearchEffect("tab", (tab: string) => { + // Don't process or strip tab param when viewing a recording or deep linking + // to one - the tab param is used for RecordingView's Timeline/Events/Detail tabs + if (recording || searchParams.has("id")) { + return false; + } + if (tab === "timeline" || tab === "events" || tab === "detail") { setNotificationTab(tab as TimelineType); } diff --git a/web/src/views/recording/RecordingView.tsx b/web/src/views/recording/RecordingView.tsx index 86e5777ea..d0f5d15f6 100644 --- a/web/src/views/recording/RecordingView.tsx +++ b/web/src/views/recording/RecordingView.tsx @@ -101,11 +101,12 @@ export function RecordingView({ const [searchParams] = useSearchParams(); const contentRef = useRef(null); - // Navigate back while clearing the review id param to prevent + // Navigate back while clearing recording-specific params (id, tab) to prevent // useSearchEffect from re-opening the recording const handleBack = useCallback(() => { const updated = new URLSearchParams(searchParams); updated.delete("id"); + updated.delete("tab"); const search = updated.toString(); navigate( { @@ -163,9 +164,34 @@ export function RecordingView({ false, ); - const [timelineType, setTimelineType] = useOverlayState( - "timelineType", - recording?.timelineType ?? "timeline", + // Timeline type from URL search params for deep linking + const timelineType = useMemo(() => { + const tabParam = searchParams.get("tab"); + if (tabParam === "events" || tabParam === "detail" || tabParam === "timeline") { + return tabParam; + } + return recording?.timelineType ?? "timeline"; + }, [searchParams, recording?.timelineType]); + + const setTimelineType = useCallback( + (type: TimelineType, replace: boolean = false) => { + const updated = new URLSearchParams(searchParams); + if (type === "timeline") { + updated.delete("tab"); // timeline is default, no need to include in URL + } else { + updated.set("tab", type); + } + const search = updated.toString(); + navigate( + { + pathname: location.pathname, + search: search ? `?${search}` : "", + hash: location.hash, + }, + { replace, state: location.state }, + ); + }, + [searchParams, navigate, location.pathname, location.hash, location.state], ); const chunkedTimeRange = useMemo(