Page and route#
/workspaces/:id/review, the index route inside the review prefix (speedway/app/routes.ts:110-111). Rendered by speedway/app/routes/workspaces/review/review.tsx.
What the reader sees#
Every attribute, part type, or content field a module could not fill on its own, across every job in the workspace, one row per open item. A toolbar filters by job, module, error kind, user, and how complete a part's critical attributes are; a row expands into an editor to fix or ignore the item in place. Selecting several rows opens a bar that approves or ignores them as one batch.
Layer 1: shell#
Same layout as the jobs list: speedway/app/routes/shell.tsx's ShellFrame renders WorkspaceNav, ShellCrumbs, and OrgActivityRail around the outlet (shell.tsx:84-106). review.tsx declares handle.nav = { active: "review", layout: "list" } (review.tsx:68-71), the same viewport-bounded fit-viewport treatment as the jobs list.
Layer 2: composition#
docs/design-language/09-page-composition.md §C names this page directly as the archetype's own namesake: "work surface | no-rail, full-width, viewport-bound; the archetype's namesake" (09-page-composition.md:52). ReviewHeader (review.tsx:399-408) sits outside the Suspense boundary the same way JobsHeader does on the jobs page, so the title never blanks while the queue streams in; the fallback is WorkspaceSkeleton again (review.tsx:413-420). Inside the resolved view, a Card wraps the toolbar, an optional bulk-selection bar, and the table or empty state as one flush surface (review.tsx:1044-1208).
Layer 3: primitives#
| Primitive | Props as called | file:line | Contract doc |
|---|---|---|---|
PageTitle | Icon={PAGE_META.review.Icon}, title="Manual review", subtitle="List of errors that Versable's modules could not resolve..." | review.tsx:401-407 | packages/ui/docs/page-title.md |
Card | wraps the toolbar, bulk bar, and table as one surface | review.tsx:1044-1050 | packages/ui/docs/card.md |
EmptyState | three call sites: indexMissing (a missing Firestore index), filtered ("Nothing matches"), true-empty ("Your queue is clear") | review.tsx:1078-1155 | packages/ui/docs/empty-state.md |
DataTable<RowItem> | clickSelect={false}, rowKey={(i) => \${i.jobId}:${i.id}`}, copyCells, renderRowDetail, detailIndicator={false}` | review.tsx:1159-1204 | packages/ui/docs/table.md |
SelectAllCell / SelectionCell | hand-placed as the table's own __select column, outside DataTable's built-in selection model | review.tsx:867-873 | packages/ui/docs/table.md |
StatusPill | the Error column, kind from reasonKeyOf, plus a Tooltip-wrapped success dot for a suggested value | review.tsx:966-993 | packages/ui/docs/status.md |
Select | ValuePicker's accepted-values picker, searchable, paired with a hidden native input for form posting | review.tsx:1789-1808 | packages/ui/docs/select.md |
Alert | tone="error", the sub-row's save failure | review.tsx:1706 | packages/ui/docs/alert.md |
Layer 4: patterns#
- Bulk selection action bar (
/patterns/bulk-selection-bar):ReviewBulkBar(review.tsx:1490-1520), rendered whenselection.selectedRows.length > 0(review.tsx:1069-1076), carries the live selected count and one Ignore action. - Claimable peek panel (
/patterns/peek-panel):ProductPeekPanel(review.tsx:1210-1217) opens from the Part number column's click handler, cross-job because each row carries its ownjobId. - Attribute / spec grid (
/patterns/attribute-spec-grid):ReviewSubRow's "What we found"dl(review.tsx:1424-1476), a labeled grid of module, error, candidates, and job-name rows, is speedway's own instance of this pattern's shape, though it is hand-built rather than composed from a shared kit primitive.
Layer 5: canon rules in force#
docs/design-language/06-tables.md§A6 (:32-33): selection scope is explicit, and the review queue is the doc's own named example of "select all in filter" living outside the model with hand-placedSelectAllCell/SelectionCellplusclickSelect={false}.- Same doc §C (
:71): "Review queue: 2000-row server window..., fixed px widths on every column, detailIndicator off (the Fix caret owns it)," matchingreview.tsx:1201-1203directly. docs/design-language/04-loading-and-states.md§A7 (:33): an empty list is not one state; it requires a true/filtered/in-progress branch.docs/design-language/09-page-composition.md§A1 (:14): the work-surface archetype, no activity rail, no reading-width cap.
Lapses#
The empty state splits two ways where the canon requires three. review.tsx:1078-1155 branches on indexMissing (an infrastructure error, not one of the canon's three legs) and then on filtered (canon's filtered-empty leg) versus the plain "Your queue is clear" message (canon's true-empty leg). No branch here says the queue is empty because a job is still running, the canon's in-progress-empty leg (docs/design-language/04-loading-and-states.md §A7, :33). The sibling file packages/ui/docs/empty-state.md cites speedway/app/routes/workspaces/jobs/parts.tsx:336-368 as the reference implementation of the full three-way branch, in the same app, one directory over from this page. A review queue genuinely can sit empty because a job's normalize stage hasn't finished yet, and today that reads identically to a queue that is truly clear. Fix, if revisited: add an in-progress branch keyed off whether any filtered job is still running/ingesting, following parts.tsx's shape.
Not a lapse, worth naming. NativeSelect (review.tsx:1713-1742) renders a raw <select> instead of the kit's Select, with the reason stated directly in a comment above it: "the kit Select is a button popover, and these live inside a form the browser posts." This is a considered exception, not an oversight, and the comment is the evidence for that.
See also#
docs/breakdowns/01-speedway-jobs.md, the same shell under an unfiltered work surface.packages/ui/docs/table.md§"FacetBar: the standing-facets toolbar" (table.md:159-174): this page'sFilterBar(speedway/app/components/FilterBar.tsx) predates the kit's ownFacetBar, and both real apps are named there as still on bespoke toolbars pending their next deliberate kit upgrade.packages/ui/docs/empty-state.md, the three-way branch this page's Lapses entry measures against.