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 (
)}
+ {document &&
+ sortedItems.length > 0 &&
+ filteredItems.length === 0 &&
+ !pending && (
+
+ No matches.
+
+ )}
- {sortedItems.map((entry, i) => {
+ {filteredItems.map((entry, i) => {
const slotForCapture = pendingInsertIndex === i;
return (
@@ -314,7 +381,7 @@ export function EvidenceSidebar(props: EvidenceSidebarProps) {
);
})}
- {pendingInsertIndex === sortedItems.length && (
+ {pendingInsertIndex === filteredItems.length && (
-