From b105b3f2b4af12f8968f9244bc9d87facff30595 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 12 Nov 2025 08:02:16 -0600 Subject: [PATCH 1/5] don't flatten the search result cache when updating this would cause an infinite swr fetch if something was mutated and then fetch was called again --- .../overlay/detail/SearchDetailDialog.tsx | 74 ++++++++++--------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx index ae60ed1c6..b716bab28 100644 --- a/web/src/components/overlay/detail/SearchDetailDialog.tsx +++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx @@ -683,6 +683,22 @@ function ObjectDetailsTab({ const mutate = useGlobalMutation(); + // Helper to map over SWR cached search results while preserving + // either paginated format (SearchResult[][]) or flat format (SearchResult[]) + const mapSearchResults = useCallback( + ( + currentData: SearchResult[][] | SearchResult[] | undefined, + fn: (event: SearchResult) => SearchResult, + ) => { + if (!currentData) return currentData; + if (Array.isArray(currentData[0])) { + return (currentData as SearchResult[][]).map((page) => page.map(fn)); + } + return (currentData as SearchResult[]).map(fn); + }, + [], + ); + // users const isAdmin = useIsAdmin(); @@ -810,17 +826,12 @@ function ObjectDetailsTab({ (key.includes("events") || key.includes("events/search") || key.includes("events/explore")), - (currentData: SearchResult[][] | SearchResult[] | undefined) => { - if (!currentData) return currentData; - // optimistic update - return currentData - .flat() - .map((event) => - event.id === search.id - ? { ...event, data: { ...event.data, description: desc } } - : event, - ); - }, + (currentData: SearchResult[][] | SearchResult[] | undefined) => + mapSearchResults(currentData, (event) => + event.id === search.id + ? { ...event, data: { ...event.data, description: desc } } + : event, + ), { optimisticData: true, rollbackOnError: true, @@ -843,7 +854,7 @@ function ObjectDetailsTab({ ); setDesc(search.data.description); }); - }, [desc, search, mutate, t]); + }, [desc, search, mutate, t, mapSearchResults]); const regenerateDescription = useCallback( (source: "snapshot" | "thumbnails") => { @@ -915,9 +926,8 @@ function ObjectDetailsTab({ (key.includes("events") || key.includes("events/search") || key.includes("events/explore")), - (currentData: SearchResult[][] | SearchResult[] | undefined) => { - if (!currentData) return currentData; - return currentData.flat().map((event) => + (currentData: SearchResult[][] | SearchResult[] | undefined) => + mapSearchResults(currentData, (event) => event.id === search.id ? { ...event, @@ -928,8 +938,7 @@ function ObjectDetailsTab({ }, } : event, - ); - }, + ), { optimisticData: true, rollbackOnError: true, @@ -963,7 +972,7 @@ function ObjectDetailsTab({ ); }); }, - [search, apiHost, mutate, setSearch, t], + [search, apiHost, mutate, setSearch, t, mapSearchResults], ); // recognized plate @@ -992,9 +1001,8 @@ function ObjectDetailsTab({ (key.includes("events") || key.includes("events/search") || key.includes("events/explore")), - (currentData: SearchResult[][] | SearchResult[] | undefined) => { - if (!currentData) return currentData; - return currentData.flat().map((event) => + (currentData: SearchResult[][] | SearchResult[] | undefined) => + mapSearchResults(currentData, (event) => event.id === search.id ? { ...event, @@ -1005,8 +1013,7 @@ function ObjectDetailsTab({ }, } : event, - ); - }, + ), { optimisticData: true, rollbackOnError: true, @@ -1040,7 +1047,7 @@ function ObjectDetailsTab({ ); }); }, - [search, apiHost, mutate, setSearch, t], + [search, apiHost, mutate, setSearch, t, mapSearchResults], ); // speech transcription @@ -1102,17 +1109,12 @@ function ObjectDetailsTab({ (key.includes("events") || key.includes("events/search") || key.includes("events/explore")), - (currentData: SearchResult[][] | SearchResult[] | undefined) => { - if (!currentData) return currentData; - // optimistic update - return currentData - .flat() - .map((event) => - event.id === search.id - ? { ...event, plus_id: "new_upload" } - : event, - ); - }, + (currentData: SearchResult[][] | SearchResult[] | undefined) => + mapSearchResults(currentData, (event) => + event.id === search.id + ? { ...event, plus_id: "new_upload" } + : event, + ), { optimisticData: true, rollbackOnError: true, @@ -1120,7 +1122,7 @@ function ObjectDetailsTab({ }, ); }, - [search, mutate], + [search, mutate, mapSearchResults], ); const popoverContainerRef = useRef(null); From 16ac0b3f5e47b7f6f0dfd0212285409d5868b682 Mon Sep 17 00:00:00 2001 From: Nicolas Mowen Date: Wed, 12 Nov 2025 07:09:22 -0700 Subject: [PATCH 2/5] Properly sort keys for recording summary in StorageMetrics --- web/src/views/system/StorageMetrics.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/web/src/views/system/StorageMetrics.tsx b/web/src/views/system/StorageMetrics.tsx index b91e66986..e99b5bf54 100644 --- a/web/src/views/system/StorageMetrics.tsx +++ b/web/src/views/system/StorageMetrics.tsx @@ -70,10 +70,9 @@ export default function StorageMetrics({ ]); const earliestDate = useMemo(() => { - const keys = Object.keys(recordingsSummary || {}); + const keys = Object.keys(recordingsSummary || {}).sort(); return keys.length - ? new TZDate(keys[keys.length - 1] + "T00:00:00", timezone).getTime() / - 1000 + ? new TZDate(keys[0] + "T00:00:00", timezone).getTime() / 1000 : null; }, [recordingsSummary, timezone]); From 29a464f2b53c3ec26269a28653a2b8bfa31bab82 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Wed, 12 Nov 2025 08:11:50 -0600 Subject: [PATCH 3/5] tracked object description box tweaks --- .../overlay/detail/SearchDetailDialog.tsx | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/web/src/components/overlay/detail/SearchDetailDialog.tsx b/web/src/components/overlay/detail/SearchDetailDialog.tsx index b716bab28..8bd90d51f 100644 --- a/web/src/components/overlay/detail/SearchDetailDialog.tsx +++ b/web/src/components/overlay/detail/SearchDetailDialog.tsx @@ -1505,7 +1505,7 @@ function ObjectDetailsTab({ ) : (