Skip to content

Review fixes for #274: wire folder-row generation + frontend tsc + tests - #1

Open
amal66 wants to merge 3 commits into
ecarjat:codex/folder-grouped-tabular-reviewsfrom
Open-Legal-Products:review-fixes/pr274-folder-rows
Open

Review fixes for #274: wire folder-row generation + frontend tsc + tests#1
amal66 wants to merge 3 commits into
ecarjat:codex/folder-grouped-tabular-reviewsfrom
Open-Legal-Products:review-fixes/pr274-folder-rows

Conversation

@amal66

@amal66 amal66 commented Aug 1, 2026

Copy link
Copy Markdown

Implements the review feedback from Open-Legal-Products#274. Folder grouping persisted rows, but nothing consumed them — this wires folder rows end to end: /generate, /regenerate-cell and PATCH now address cells by row_id (folder cells have document_id NULL), the UI renders folder rows, the frontend typecheck is fixed, the migration is re-dated so upgrade-by-filename-order still applies it, and tests cover the new paths.

  • 7452d01 — re-dates the folder-rows migration to 20260801_01 so it sorts after migrations already shipped on main, and orders the legacy-row backfill by filename to match the create path.
  • 0ac1a53 — generation iterates rows instead of documents (folder rows concatenate their source documents' markdown, capped at 120k chars with per-file headings); regenerate-cell accepts row_id; PATCH reconciles the row tables so grouped reviews don't desync; Supabase read errors now throw instead of silently returning empty. Adds backend integration tests, including the folder-cell "stuck on pending" regression.
  • 00a7725 — renders folder rows in the review table (folder label + source-document list, row_id-keyed cells and regeneration) and fixes the three string | null tsc errors that broke tsc --noEmit.

Verified on this branch: backend tsc clean, 268 backend tests pass (260 baseline + the new ones); frontend tsc --noEmit clean; 41 frontend tests pass.

The branch is based directly on your codex/folder-grouped-tabular-reviews — no merge of main. Rebasing onto current main is intentionally left to you, including resolving schema.sql and backend/src/routes/tabular.ts against main's paginated-reviews RPC rework (d6b35c9).

amal66 and others added 3 commits August 1, 2026 19:58
WHY THIS MATTERS
README's upgrade procedure tells operators to "apply the migrations dated
AFTER the version of Mike you currently have deployed, in filename order."
This migration was named 20260724_02, which sorts BEFORE two migrations
already on main (20260726_01 pagination, 20260727_01 ids overview). An
operator who deployed at/after 20260727 would skip 20260724 entirely — the
new tabular_review_rows / _sources tables would never be created, and the
folder-rows feature would fail at runtime against a schema that silently
lacks its tables.

WHAT IS A DATE-ORDERED MIGRATION LOG
The repo has no migration-state table; the only ordering signal is the
filename date. "Apply everything newer than my deploy" is only correct if new
work always carries a date later than everything already shipped. A back-dated
file is invisible to that rule — a classic migration-ordering trap.

HOW IT WORKS
- Rename 20260724_02_tabular_folder_rows.sql -> 20260801_01_… so it sorts
  after every migration currently on main.
- Add the repo-convention "-- Migration date: 2026-08-01" header comment
  (every other migration carries one; the linter/reader relies on it).
- Order the legacy-row backfill by filename (matching the create path, which
  sorts rows by label) instead of by opaque document_id, so upgraded reviews
  get the same row order as freshly created ones.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WHY THIS MATTERS
Folder grouping persisted rows but nothing consumed them: /generate,
/regenerate-cell and PATCH all keyed cells on document_id. A folder row's
cells carry document_id NULL and a row_id, so those cells could never be
addressed — a folder-grouped review sat on "pending" forever, and PATCH
edited cells without ever touching the row tables, desyncing them. This wires
the whole write path to rows so the feature actually works end to end.

WHAT IS A "ROW" HERE
A tabular_review_rows row is the unit a cell belongs to. A document review has
one row per document (row_id + document_id both set). A folder review has one
row per project subfolder (row_id set, document_id NULL) whose source
documents live in tabular_review_row_sources. Every cell now hangs off a
row_id (the migration backfills legacy cells), so row_id — not document_id —
is the correct join key for generation and edits.

HOW IT WORKS
- /generate iterates rows (loadReviewRows), not documents. For each row it
  concatenates its source documents' extracted markdown, updates cells by
  row_id, and emits SSE events carrying BOTH row_id and document_id (the
  latter for backward-compatible clients). A legacy document-only fallback
  keeps pre-rows reviews generating.
- buildRowMarkdown caps the concatenated text at ROW_MARKDOWN_MAX_CHARS
  (120k) — a folder row can hold N documents, and without a ceiling one row
  could blow past the model context window / the per-cell 120k slice. Each
  source is headed by its filename so the model can tell them apart.
- /regenerate-cell accepts row_id (folder cells) as well as document_id
  (unchanged for document cells); either identifier resolves the same cell.
- PATCH reconciles the row tables instead of poking cells by document_id:
  removed docs delete fully-emptied rows (FK cascade wipes their cells +
  sources) and trim partially-emptied folder rows' sources; added docs are
  planned via the shared planRows() and either merged into an existing folder
  row or inserted as new rows; then one pending cell is ensured per
  (live row x active column), keyed on row_id — so grouped reviews no longer
  desync and new cells always carry a row_id.
- fetchSourceDocuments / getFolderPathMap / loadReviewRows now throw on the
  Supabase error instead of returning empty. A swallowed transient read would
  otherwise create a review with zero rows/cells and no signal; throwing lets
  the create-path rollback (delete the review) run.

WHAT IS AN UNCHECKED SUPABASE ERROR
supabase-js resolves (never rejects) with { data, error }. Destructuring only
`data` turns a failed query into a silent empty result. Reading `error` and
throwing converts that into a real failure the caller can compensate for.

TESTS
- folder-row generation reaches status "done" for a document_id-NULL cell
  (the F1 regression), asserted on the SSE stream.
- create-path rollback when the rows insert (and when the source-doc read)
  fails.
- GET returns rows with resolved source_document_ids.
- PATCH inserts a new column's cell with a row_id, and creates a review row
  when a document is added (proving the row tables stay in sync).
- nested-folder path labels ("Parent / Child") + "Unknown folder" fallback.
- regenerate-cell addressed by row_id.
- tabular_review_rows + _sources added to the stack test's PUBLIC_TABLES so
  the deny-all RLS lockdown on them is CI-enforced.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WHY THIS MATTERS
Two problems shipped together. (1) The PR widened TabularCell.document_id to
`string | null`, which broke three call sites in TabularReviewView (TS2345) —
frontend `tsc` exited 1 and, because next.config doesn't ignore build errors,
`next build` failed outright. (2) The table rendered from `documents[]` and
matched cells by document_id, so folder rows (document_id NULL) never appeared
and their cells never populated even once the backend generated them. This
makes the client render and drive reviews by row.

WHAT IS THE ROW/CELL MATCH RULE
A cell belongs to a row by row_id first (folder cells have no document_id),
falling back to document_id so optimistic and legacy cells still line up.
cellMatchesRow() centralises this predicate and every mutation path — the
table, the generate SSE handler, regenerate, delete/clear — uses it, so what
renders and what updates can never diverge.

HOW IT WORKS
- TRTable takes a `rows: TableRow[]` prop and renders one row each. Folder
  rows show their label with no per-row checkbox (no single document to
  select); document rows behave exactly as before. getCell() uses the match
  rule above.
- TabularReviewView loads the `rows` payload into state and derives
  `tableRows` (falling back to one-row-per-document for pre-rows reviews). The
  generate SSE handler matches cell_update by row_id (with document_id
  fallback); regenerate sends row_id for folder cells; add/delete-documents
  keep the optimistic row list in sync; the detail side panel falls back to a
  folder row's first source document so citations still have a document to
  show (a documented v1 limitation).
- regenerateTabularCell() sends { row_id } for folder cells, else
  { document_id } — matching the backend's either-identifier contract.
- The three TS2345 sites are null-guarded (a NULL document_id is simply never
  "included" in a document-id selection), turning tsc — and the production
  build — green.
- Remove the dead `document_grouping` field from updateTabularReview's payload
  type: the PATCH endpoint never reads it (grouping is fixed at create time),
  so advertising it in the client was misleading.

TESTS
- TRTable renders a folder row and matches its row_id-keyed, document_id-NULL
  cell (proving the render fix).
- NewTRModal passes document_grouping "folder" when the subfolder checkbox is
  ticked and "document" when left unticked.
- frontend `tsc --noEmit` and `next build` both pass; full vitest suite green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@amal66
amal66 force-pushed the review-fixes/pr274-folder-rows branch from 3008bf9 to 00a7725 Compare August 2, 2026 03:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant