From 2bbabccaaf089da53368ff1d6701cbeb0df37ae0 Mon Sep 17 00:00:00 2001 From: Kalle <38327916+Sendouc@users.noreply.github.com> Date: Wed, 5 Aug 2026 21:50:10 +0300 Subject: [PATCH] Only show ungrouped events as fallback --- .../scanner/components/EventsSummary.tsx | 7 +- app/features/scanner/components/LivePage.tsx | 88 ++++++++++--------- app/features/scanner/components/VodPage.tsx | 10 ++- app/features/scanner/store/events.ts | 16 ---- 4 files changed, 55 insertions(+), 66 deletions(-) diff --git a/app/features/scanner/components/EventsSummary.tsx b/app/features/scanner/components/EventsSummary.tsx index 695161f7c..f5babfff1 100644 --- a/app/features/scanner/components/EventsSummary.tsx +++ b/app/features/scanner/components/EventsSummary.tsx @@ -1,7 +1,8 @@ /** - * One light line summarizing a scan's raw detections as per-type counts, - * with a toggle for the full event card feed — the matches are the main - * view, the events stay one click away. + * One light line summarizing raw detections as per-type counts, with a + * toggle for their event card feed. Callers pass only events not covered + * by a match card — a fallback for detections not yet grouped, hidden + * entirely once everything lives in a match. */ import { DEATH_EVENT_TYPE } from "../core/detectors/death/index"; diff --git a/app/features/scanner/components/LivePage.tsx b/app/features/scanner/components/LivePage.tsx index 077245aeb..0ca736126 100644 --- a/app/features/scanner/components/LivePage.tsx +++ b/app/features/scanner/components/LivePage.tsx @@ -207,6 +207,10 @@ export function LivePage({ } }, [deviceId, refreshFeed, send]); + const builtMatches = buildScannerMatches(feed); + const groupedEvents = new Set(builtMatches.flatMap((b) => b.sources)); + const ungroupedFeed = feed.filter((e) => !groupedEvents.has(e)); + const stop = useCallback(() => { stopRef.current?.(); stopRef.current = null; @@ -314,55 +318,53 @@ export function LivePage({ {feed.length === 0 ? (

No detections yet.

) : null} - {[...buildScannerMatches(feed)] - .reverse() - .map((built, reverseIndex) => { - const id = built.sources[0]!.id!; - const ingestable = isIngestableMatch(built.match); - return ( - void send(matchContaining(id), { manual: true }) - : undefined - } - > - {withoutRepeatEvents(built.sources).map((e) => ( - loadEventFrame(e.id!) - : undefined - } - /> - ))} - - ); - })} - {feed.length > 0 ? ( + {[...builtMatches].reverse().map((built, reverseIndex) => { + const id = built.sources[0]!.id!; + const ingestable = isIngestableMatch(built.match); + return ( + void send(matchContaining(id), { manual: true }) + : undefined + } + > + {withoutRepeatEvents(built.sources).map((e) => ( + loadEventFrame(e.id!) + : undefined + } + /> + ))} + + ); + })} + {ungroupedFeed.length > 0 ? ( setEventsOpen(!eventsOpen)} /> ) : null} {eventsOpen - ? feed.map((e) => ( + ? ungroupedFeed.map((e) => ( m.event)); const vodMatchByEvent = new Map(matches.map((m) => [m.event, m] as const)); + const groupedEvents = new Set(builtMatches.flatMap((b) => b.sources)); + const ungroupedMatches = matches.filter((m) => !groupedEvents.has(m.event)); // "Upload as results" sends the whole scan in one go, so its outcome maps // onto every ingestable card; a partial failure (some chunks sent, some @@ -607,20 +609,20 @@ export function VodPage({ ); })} - {matches.length > 0 ? ( + {ungroupedMatches.length > 0 ? ( m.event)} + events={ungroupedMatches.map((m) => m.event)} open={eventsOpen} onToggle={() => setEventsOpen(!eventsOpen)} /> ) : null} {/* newest detection on top; storage keeps ascending video-time order */} {eventsOpen - ? [...matches] + ? [...ungroupedMatches] .reverse() .map((m, i) => ( { - return db().then( - (database) => - new Promise((resolve, reject) => { - const transaction = database.transaction( - [EVENTS_STORE, FRAMES_STORE], - "readwrite", - ); - transaction.objectStore(EVENTS_STORE).delete(id); - transaction.objectStore(FRAMES_STORE).delete(id); - transaction.oncomplete = () => resolve(); - transaction.onerror = () => reject(transaction.error); - }), - ); -} - export function listEvents(): Promise { return tx( EVENTS_STORE,