Page and route#
/workspaces/:id/jobs, the index route inside the jobs prefix (speedway/app/routes.ts:74-75). Rendered by speedway/app/routes/workspaces/jobs/jobs.tsx.
What the reader sees#
Every module run in the workspace, one row per job, each showing its current stage and how many parts still need review. A "New job" menu opens the create flow for a full workflow or a single module. A workspace with no jobs yet shows a create prompt instead of an empty table.
Layer 1: shell#
Every signed-in route sits inside one layout, speedway/app/routes/shell.tsx, registered once at routes.ts:24-123. Its ShellFrame component reads the deepest matched route's handle and renders WorkspaceNav, ShellCrumbs, and an OrgActivityRail around the page's own outlet (shell.tsx:84-106). jobs.tsx declares handle.nav = { active: "jobs", layout: "list" } (jobs.tsx:30-33), which does two things: it tells WorkspaceNav which sidebar item is active, explicitly rather than by a path-prefix match, and it tells ShellFrame to add the fit-viewport class to .ws-shell (shell.tsx:85), which bounds the page to the browser viewport so the table owns its own scroll instead of the whole page scrolling.
WorkspaceNav composes the kit's standalone Sidebar primitive directly (speedway/app/components/WorkspaceNav.tsx:13), not the kit's AppShell composite. packages/ui/docs/app-shell.md:17 names this choice explicitly: speedway "built its own two-layer chrome... before or instead of adopting" AppShell, and imports zero symbols from it. ShellCrumbs is speedway's own hand-built breadcrumb trail (speedway/app/components/ShellCrumbs.tsx), not the kit's Breadcrumbs primitive.
Layer 2: composition#
docs/app-patterns/12-primitives-and-rules.md sorts canon rules by whether a kit primitive already retires them; the page-level composition order itself is ruled by docs/design-language/09-page-composition.md. That doc names two archetypes (§A1) and its own use-case table calls the jobs list out directly: "layout:"list", table scrolls in card, PageTitle + NewJobMenu actions" (09-page-composition.md:53), a work surface, not a reading page.
Inside the page, JobsHeader (jobs.tsx:186-196) sits outside the Suspense boundary that streams the job list in (jobs.tsx:198-229), so the title and the "New job" action never blank while data loads; only the table area falls back to WorkspaceSkeleton (jobs.tsx:201-205). Once resolved, JobsView (jobs.tsx:231-288) renders the header again inside a LIST_PANEL flex column (jobs.tsx:258), followed by either the empty state or the table, matching doc 9 §A4's fixed order: breadcrumbs, then PageTitle pinned shrink-0, then the table.
Layer 3: primitives#
| Primitive | Props as called | file:line | Contract doc |
|---|---|---|---|
PageTitle | Icon={PAGE_META.jobs.Icon}, title="Jobs", subtitle="Each time you run a module...", actions={<NewJobMenu wid={wid} />} | jobs.tsx:188-194 | packages/ui/docs/page-title.md |
Dropdown + Button | NewJobMenu's trigger: variant="text" shade color="success" Icon="Plus", opening a Dropdown panel of workflow and module links | speedway/app/components/NewJobMenu.tsx:61-93 | packages/ui/docs/dropdown.md, packages/ui/docs/button.md |
EmptyState | className="mt-10", Icon="Table", title="No jobs yet", description="Create a job from your files...", action is a raw <Link> | jobs.tsx:260-275 | packages/ui/docs/empty-state.md |
DataTable<JobRowVM> (inside JobsTable) | minWidth={extraAction ? "1536px" : "1436px"}, renderRowDetail conditional on the row's own expandability | speedway/app/components/JobsTable.tsx:842-889 | packages/ui/docs/table.md |
StatusPill (inside JobsTable) | kind per stage (err/warn/ok/neutral), Icon={STATUS_ICON.*} | JobsTable.tsx:147-210 | packages/ui/docs/status.md |
JobsTable is the app's one shared table component, reused across the jobs page and every module tab (docs/design-language/06-tables.md:36, "one implementation, reused"). This doc did not re-derive JobsTable's internal row-click behavior from its 948 lines; the characterization above about which prop renders which stage pill is read directly from the file, but the broader "navigate-or-expand duality" claim in Layer 5 below is the canon's own claim, not this doc's independent finding.
Layer 4: patterns#
- Sidebar anatomy (
/patterns/sidebar-anatomy): the grouped rail, known-only counts, and identity footer thatWorkspaceNavrenders are the shape this pattern documents, though the pattern page itself is built from the kit'sAppShell/Sidebar, not from speedway's own component. - Shape-matched skeleton (
/patterns/shape-matched-skeleton): theWorkspaceSkeletonfallback rendered insidejobs.tsx'sSuspense(jobs.tsx:201-205,~/components/WorkspaceSkeleton) is speedway's own instance of the "page and placeholder share one container map" idea this pattern names.
Layer 5: canon rules in force#
docs/design-language/08-navigation-and-shell.md§A1 (:15): "chrome is infrastructure a route declares into, never markup a page repeats."jobs.tsxrenders no chrome of its own; everything isShellHandleand page content.- Same doc §A3 (
:21): active state must be explicit, never inferred by prefix.WorkspaceNavreadshandle.nav.active(jobs.tsx:32), not a path match. - Same doc §A7 (
:33): breadcrumbs earn their row only two or more levels deep. docs/design-language/09-page-composition.md§A3 (:23): viewport bounding is opt-in and named throughlayout: "list", never hand-built per page. §A4 (:26) fixes the composition order this page follows.docs/design-language/06-tables.md§A2 (:20): "row click means exactly one thing per table." The doc's own §C (:69) characterizesJobsTableas satisfying this through a navigate-or-expand split, where the name link carries navigation and the row carries expansion; this doc cites that claim rather than re-deriving it.
Lapses#
A third instance of raw daisyUI button classes. jobs.tsx:267-273 fills EmptyState's action slot with a raw <Link className="btn btn-primary btn-sm"> rather than the kit's Button wrapped with linkAs. packages/ui/docs/button.md's Banned section (button.md:67) names raw daisyUI btn classes as "cautioned" and lists exactly two bounded, documented deviations (dense editable grids, the kit's own ModalFooter); this call site is not one of the two and is not flagged there. It is, however, the exact block empty-state.md cites as its own reference example for a true-empty state (empty-state.md:45), so the two contract docs disagree about whether this specific line is sanctioned or cautioned. Fix, if this page is ever touched again: swap the raw anchor for <Button href={...} linkAs={Link} content="New job" />, or update button.md's caution list to name this as a third documented deviation.
See also#
docs/breakdowns/02-speedway-review.md, the same shell under a filtered work surface. This page has no filter toolbar of its own; the review queue is where speedway's toolbar predates the kit'sFacetBar.docs/app-patterns/10-recipe-browsable-list.md, what a request for a list brings along.packages/ui/docs/dropdown.md,NewJobMenu's composition primitive.