diff --git a/drb-frontend/components/live/FilterBar.tsx b/drb-frontend/components/live/FilterBar.tsx index b6e3b24..e13724a 100644 --- a/drb-frontend/components/live/FilterBar.tsx +++ b/drb-frontend/components/live/FilterBar.tsx @@ -53,7 +53,9 @@ export function FilterBar({ const [q, setQ] = useState(filter.q); const committed = useRef(filter.q); useEffect(() => { - if (filter.q !== committed.current) { committed.current = filter.q; setQ(filter.q); } + // Compare trimmed: the hash stores q trimmed, so a just-typed trailing + // space ("main ") must not read as an outside change and get eaten. + if (filter.q !== committed.current.trim()) { committed.current = filter.q; setQ(filter.q); } }, [filter.q]); const filterRef = useRef(filter); filterRef.current = filter; diff --git a/drb-frontend/lib/liveFilter.ts b/drb-frontend/lib/liveFilter.ts index 22d13b6..d5a6b75 100644 --- a/drb-frontend/lib/liveFilter.ts +++ b/drb-frontend/lib/liveFilter.ts @@ -120,6 +120,13 @@ const TRAFFIC_TAGS = ["mva", "mvc", "crash", "collision", "accident", "rollover" const tagHit = (tags: string[] | undefined, terms: readonly string[]) => (tags ?? []).some((t) => { const l = t.toLowerCase(); return terms.some((term) => l.includes(term)); }); +/** Preset tag terms match at the start of a word ("downed-tree", "trees"), so + * "tree" does not pull in "street-racing" or "street-fight". */ +const wordStart = (terms: readonly string[]) => new RegExp(`(?:^|[^a-z])(?:${terms.join("|")})`); +const STORM_RE = wordStart(STORM_TAGS); +const TRAFFIC_RE = wordStart(TRAFFIC_TAGS); +const presetTagHit = (tags: string[] | undefined, re: RegExp) => (tags ?? []).some((t) => re.test(t.toLowerCase())); + const callText = (c: CallRecord) => `${c.transcript_corrected || c.transcript || ""} ${c.location ?? ""}`; interface MatchCtx { @@ -137,8 +144,8 @@ function presetMatch(p: PresetKey, inc: IncidentRecord, ctx: MatchCtx): boolean return (inc.talkgroup_ids ?? []).some((t) => ctx.fireTgs.has(String(t))) || (ctx.calls.get(inc.incident_id) ?? []).some((c) => FIRE_CHANNEL.test(c.talkgroup_name ?? "")); } - if (p === "storm") return tagHit(inc.tags, STORM_TAGS); - return inc.type === "accident" || tagHit(inc.tags, TRAFFIC_TAGS); + if (p === "storm") return presetTagHit(inc.tags, STORM_RE); + return inc.type === "accident" || presetTagHit(inc.tags, TRAFFIC_RE); } function lastActivity(inc: IncidentRecord): number {