Tabular review filtering by flag, verified state, and per-column text
nwhitehouse added a filter bar above the tabular review table that lets reviewers narrow rows by flag color, whether a cell has been manually verified, and per-column text predicates. The filter logic is pure and unit-tested; four commits address UX issues - mostly popover rendering against a sticky-column z-index stack - found through smoke testing.
Migration 006_tabular_cells_verified.sql adds verified bool default false, verified_at timestamptz, and verified_by uuid to tabular_cells - all nullable or defaulted, safe to apply against live data. PATCH /tabular-review/:reviewId/cells/verify toggles a single cell and records who and when, giving a future audit story something to work with without a schema change.
trFilterPredicate.ts is the clean part: a pure (cell, filter) => boolean function plus computeVisibleDocIds(docs, cells, filters) => Set<docId>, with AND semantics across filters. Text predicates support contains, does_not_contain, is, is_not, is_empty, and is_not_empty. The module ships 11 unit tests.
TRFilterBar.tsx renders active filters as dismissible pills with a "+ Add filter" popover. Filter state is localStorage-backed per (review, device).
The four-commit bug sequence is worth reading as a cautionary tale. First: both the filter popover and the ColumnVisibilityMenu were at z-30; the sticky document-name column in TRTable runs at z-[60] and painted over the middle of both popovers. Fix: bump to z-[80]. Second: adding the same Flag or Verified filter twice ANDed them (Flag=yellow AND Flag=unverified is logically empty), so the fix limits those two filter types to one each - adding a second replaces the first. Text predicates can still stack across columns. The same commit also anchors the popover at right-0 so it grows leftward from the trigger. Third: right-0 always clips the popover when the trigger is at the left edge of an empty filter row. The final fix uses useLayoutEffect to measure getBoundingClientRect on open and picks left-0 if there's at least 320px of space to the right, otherwise right-0.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?