From 664d7da100e5fc09c265b38231947e260d729da1 Mon Sep 17 00:00:00 2001 From: tegwick Date: Thu, 30 Jul 2026 19:41:40 +0200 Subject: [PATCH] Add Evidence sidebar text filter for CE-WP-0010. Filter input next to the Evidence caption; case-insensitive match on quote, commentary, and status when the query has three or more characters. --- src/work/EvidenceSidebar.tsx | 85 ++++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 9 deletions(-) diff --git a/src/work/EvidenceSidebar.tsx b/src/work/EvidenceSidebar.tsx index 6a6731c..8bda2f4 100644 --- a/src/work/EvidenceSidebar.tsx +++ b/src/work/EvidenceSidebar.tsx @@ -104,6 +104,22 @@ function annotationOrderKey(annotation: Annotation | null): number { return docOrderKey(annotation.selectors); } +const FILTER_MIN_CHARS = 3; + +function matchesEvidenceFilter( + query: string, + quote: string, + commentary: string | undefined, + status: string, +): boolean { + const q = query.trim(); + if (q.length < FILTER_MIN_CHARS) return true; + const needle = q.toLowerCase(); + return [quote, commentary ?? "", status].some((p) => + p.toLowerCase().includes(needle), + ); +} + export function EvidenceSidebar(props: EvidenceSidebarProps) { const engine = useEngine(); const { document } = useActiveDocument(); @@ -117,6 +133,8 @@ export function EvidenceSidebar(props: EvidenceSidebarProps) { const annotationUpdateTick = useEngineEventTick("AnnotationUpdated"); const revision = useEngineRevision(); + const [filterQuery, setFilterQuery] = useState(""); + // Build the sorted view-model: each item gets its order key + the // first annotation up-front so the render below doesn't have to // re-resolve them inside the map. @@ -139,6 +157,17 @@ export function EvidenceSidebar(props: EvidenceSidebarProps) { revision, ]); + const filteredItems = useMemo(() => { + return sortedItems.filter(({ item, annotation }) => + matchesEvidenceFilter( + filterQuery, + annotation?.quote ?? "", + item.commentary, + item.status, + ), + ); + }, [sortedItems, filterQuery]); + const pendingOrder = useMemo(() => { if (!pending) return Number.POSITIVE_INFINITY; const c = pending.capture; @@ -148,15 +177,15 @@ export function EvidenceSidebar(props: EvidenceSidebarProps) { return c.startOffset; }, [pending]); - // Find the insert position for the pending capture form: first index - // whose order > pendingOrder, or sortedItems.length to append. + // Insert pending capture among the *unfiltered* document order so a + // narrow filter cannot hide the in-progress capture form. const pendingInsertIndex = useMemo(() => { if (!pending) return -1; - for (let i = 0; i < sortedItems.length; i++) { - if (sortedItems[i]!.order > pendingOrder) return i; + for (let i = 0; i < filteredItems.length; i++) { + if (filteredItems[i]!.order > pendingOrder) return i; } - return sortedItems.length; - }, [pending, pendingOrder, sortedItems]); + return filteredItems.length; + }, [pending, pendingOrder, filteredItems]); const [openExportFor, setOpenExportFor] = useState(null); const [editingId, setEditingId] = useState(null); @@ -249,6 +278,7 @@ export function EvidenceSidebar(props: EvidenceSidebarProps) { return (