nwhitehouse gives tabular review a verified column and real filters

Reviewers can now mark individual cells as checked, and filter the table down to what still needs eyes.

contract-reviewworkflow

The tabular review surface - the grid view where you read across many documents at once - gets a hover-revealed checkmark on each cell, recording who verified it and when. Paired with that is a proper filter bar: by flag, by verified-or-not, and by free-text-per-column, with operators like contains, is, and is empty. Filters stack with AND, and your filter state sticks per review on the device you're using.

Three follow-up commits are the giveaway that this got real smoke testing: a popover hiding behind a sticky column, a Verified-and-Unverified filter combination that silently meant "show nothing", and a filter menu that ran off the screen at the edge of the row. All fixed.

The underlying filter logic is split out from the UI and covered by unit tests, which makes this an unusually clean piece to lift.

So what Anyone building a review tool over LLM output at any volume should look here - verified state plus filtering is what reviewers ask for the moment the table stops fitting on one screen.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

4 commits from nwhitehouse/mike, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
80763429 [feat-023] Tabular review filtering + cell verified state Nick Whitehouse 2026-05-07 ↗ GitHub
commit body
User-facing: filter the table by flag, verified state, and per-column
text predicates. Mark cells verified with a hover ✓ button so the
verified-state filter has something to bite on. Critical when reviews
have more than ~50 docs and the user is hunting for "just the red
ones" or "the cells I haven't checked yet".

Backend:
- Migration 006: tabular_cells gains verified bool (default false),
  verified_at timestamptz, verified_by uuid → auth.users. Additive,
  safe against live data.
- PATCH /tabular-review/:reviewId/cells/verify - toggles a single
  cell's verified state. Records who/when. Reuses ensureReviewAccess.

Frontend:
- trFilterPredicate.ts - pure (cell, filter) → boolean +
  computeVisibleDocIds(docs, cells, filters) → Set<docId>. Operators:
  contains / does not contain / is / is not / is empty / is not empty.
  AND across multiple filters. Flag + verified filters union per-doc;
  text filters scoped to their column. 11 unit tests pass.
- TRFilterBar.tsx - pill row above the table. Active-filter pills with
  ✕ removal. + Add filter popover with Flag (multi-select) / Verified
  (radio) / Column-value (column → operator → value) tabs.
  "Showing N of M documents matching K filters" caption. Clear all link.
- TabularCell.tsx - hover-revealed ✓ button (top-left). Always visible
  when verified (subtle green check); fades in on hover when not.
- TabularReviewView wires filter state in localStorage per
  (review, device); applies via computeVisibleDocIds composed on top of
  the existing doc-name search; mounts TRFilterBar above the table when
  any columns exist; passes onToggleVerifyCell to TRTable.
- TRTable threads the verify handler through to TabularCell and grows
  pl-4 padding when verify is enabled so the ✓ doesn't overlap content.
- TabularCell type extended with verified / verified_at / verified_by;
  no API mapping change needed (Supabase JS auto-includes selected
  columns, and TabularReviewDetailOut → TabularCell[] passes through).

mikeApi: setTabularCellVerified helper added.

Verified: tsc clean both sides; 11 new frontend predicate tests pass via
bun test; 16 backend tests still pass; migration 006 applied locally
(verified column non-null with default false; verified_at + verified_by
nullable).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
e95c36c7 [feat-023] Lift filter + column popovers above sticky table cells Nick Whitehouse 2026-05-07 ↗ GitHub
commit body
User reported the Add filter popover rendered with empty middle -
turned out the sticky doc-name column in TRTable runs at z-[60] and
the popovers were at z-30, so the table cells were painting over the
popover content. Bumped both popovers (TRFilterBar add-filter +
ColumnVisibilityMenu) to z-[80] so they sit above the sticky cells
but below modal overlays at z-[100]+.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
c6c92bc7 [feat-023] Filter UX: dedupe single-kind filters + anchor popover right Nick Whitehouse 2026-05-07 ↗ GitHub
commit body
Two real bugs from smoke testing:

- Adding the same flag/verified filter multiple times stacked them and
  silently AND'd the conditions (Flag: Yellow + Flag: Yellow,Red →
  matches docs that have BOTH a yellow cell AND a {yellow|red} cell).
  Worse, Verified-only AND Unverified-only is logically empty (no cell
  is both).
  Fix: at most one Flag filter and one Verified filter. Selecting
  either kind in the Add Filter popover replaces the existing one
  rather than stacking. Text predicates can still stack (different
  columns are useful AND-combinations).
  The popover form pre-fills from the existing filter of the same
  kind, so re-opening edits in place.

- The popover anchored at left-0 of the trigger; with many active
  filters the trigger sits at the right end of the row and the
  popover ran off the right edge of the viewport, half-clipped.
  Fix: anchor at right-0 so it grows leftward from the trigger and
  always stays in-viewport.

Bonus: the popover's default tab now picks the *missing* kind
(Flag → Verified → Column value), so building a multi-facet filter
set takes one fewer click.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
515592d0 [feat-023] Add-filter popover: dynamic side based on trigger position Nick Whitehouse 2026-05-07 ↗ GitHub
commit body
The earlier fix flipped the popover to right-0 always, which clipped
off the LEFT edge when the trigger was at the start of the row (the
common no-filters-yet case). Now measures the trigger's
getBoundingClientRect on open: if there's >= 320px of room to the
right of the trigger use left-0; otherwise right-0. Recomputed on
every open since the trigger moves as filters wrap.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-153.md from inside the repo you want the changes in.

⬇ Download capture-thread-153.md