From 83cc7105d62c41922bd6464aa2b63aba6393f9d5 Mon Sep 17 00:00:00 2001 From: Hugh Kaznowski Date: Sat, 5 Sep 2026 19:32:36 +0100 Subject: [PATCH] Add regex cheat-sheet icon next to regex inputs Adds RegexHelpIcon, a hover/click popover (modeled on HelpIcon) showing a compact cheat sheet for the Rust `regex` crate's actual syntax, and places it next to every user-facing regex field: Params/Metrics message match regex, Alerts field regex, and FilterBar's regex(...) helper. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o --- webapp/src/components/Alerts.tsx | 6 ++- webapp/src/components/FilterBar.tsx | 2 + webapp/src/components/Metrics.tsx | 6 ++- webapp/src/components/Params.tsx | 6 ++- webapp/src/components/RegexHelpIcon.tsx | 67 +++++++++++++++++++++++++ 5 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 webapp/src/components/RegexHelpIcon.tsx diff --git a/webapp/src/components/Alerts.tsx b/webapp/src/components/Alerts.tsx index 43fefc8..515197b 100644 --- a/webapp/src/components/Alerts.tsx +++ b/webapp/src/components/Alerts.tsx @@ -11,6 +11,7 @@ import { } from '../lib/api' import { sectionHeaderClassName, sectionHeaderLeftClassName } from './CollapsibleSection' import { HelpIcon } from './HelpIcon' +import { RegexHelpIcon } from './RegexHelpIcon' import { queryInputClassName } from './FilterBar' type ConditionKind = AlertCondition['kind'] @@ -323,7 +324,10 @@ export function Alerts() { {form.kind === 'newvalue' && (
- +
+ {collapseToggle}
- +
- + ...)', meaning: 'Named capturing group' }, + { tokens: '(?:...)', meaning: 'Non-capturing group' }, + { tokens: '(?i) (?m) (?s)', meaning: 'Inline flags: case-insensitive, multi-line, dot-matches-newline' }, +] + +// "@" icon next to regex inputs; shows a compact cheat sheet for the regex flavor the server actually +// uses (Rust `regex` crate — no lookaround/backreferences), modeled on HelpIcon's button styling. +export function RegexHelpIcon({ label }: { label?: string }) { + const [open, setOpen] = useState(false) + const ref = useRef(null) + + useEffect(() => { + if (!open) return + function onClickOutside(e: MouseEvent) { + if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false) + } + document.addEventListener('mousedown', onClickOutside) + return () => document.removeEventListener('mousedown', onClickOutside) + }, [open]) + + return ( +
setOpen(true)} onMouseLeave={() => setOpen(false)}> + + {open && ( +
+

Regex syntax (Rust regex crate)

+ + + {CHEAT_SHEET.map(({ tokens, meaning }) => ( + + + + + ))} + +
{tokens}{meaning}
+

No lookaround or backreferences (not supported by the Rust regex crate).

+
+ )} +
+ ) +}