jobcache.corbet.ch dashboard — design recommendation (#72) — 2026-06-08

Output of a 3-angle judge-panel (triage-at-volume · market-intelligence · cv-shell-mirror), synthesised + grounded against the actual code. For Julian to react to before the deeper increments. The read-only MVP (below) is the safe, architecture-forced foundation any design needs, so I'm building that increment now; the triage UX + analytics await your steer.

Recommendation

Graft triage-at-volume's PRODUCT onto cv-shell-mirror's ARCHITECTURE. Lead with a single Matrix tab tuned for triage ("swipe-right at volume" — the North Star's core verb); keep market-intelligence as supporting tabs, not the front door.

Why this and not the others (verified against code):

  • North Star names triage-at-volume as the core; RADAR/EXTRACT/.../scoring are supporting. The market-intelligence design (4 of 6 tabs analytics, triage demoted) inverts that → rejected as the primary frame, kept as supporting tabs once the corpus has data.
  • Feasibility forces cv-shell-mirror's data model. The shared corpus row (PostingSummary, jobcache/shared/src/fact-tree.ts) is lean: {fingerprint, source, url, title, company, location, posted_at, first_seen_at}no salary/commute/score, and per CLAUDE.md §1/§5 + PRODUCT-ENGINE.md it's read-only + global. Judgments/scores/decision-state CANNOT live on it — they live in a session config overlay, composed at render as corpus_row ⊕ session_overlay[fingerprint]. calculateScore (lib/domain/src/scoring.ts) returns null cleanly → honest "—" until a session supplies dimensions (no fake data).
  • Reuse boundary is real. @cv/public-ui (lib/public-ui/) is a THIN shell (tabs/rightSlot/ identitySlot/children + InstanceStore). The table/scoring/kanban are pure functions over arrays+config in ui/src/lib/components/dashboard/{table,job-detail}/, ui/src/lib/kanban/, lib/domain/src/{scoring,views,columns}.tsNOT Yjs-coupled (only Dashboard.svelte imports @cv/mutations/@cv/workspace-client, which PRODUCT-ENGINE.md forbids importing into JobCache). So net-new footprint is small.

Tab structure

  1. Matrix — the triage table: corpus rows, server-side faceted/paginated/sorted; groups by decision-state when a session is active, else a plain read-only list; Score blank until a session supplies dimensions. The product surface.
  2. Search — existing free-text + facets over /api/search; a deep-link into a pre-filtered Matrix (already built).
  3. Stats — existing /api/stats cards (total, sources, 7d-fresh, top companies); the honest empty-corpus landing (already built).
  4. Board (post-MVP, session-only) — reused ui/src/lib/kanban over shortlisted rows by pipeline status; needs a session overlay.
  5. Trends (post-corpus) — the genuine aggregate differentiator: salary percentiles + postings-over-time via NEW percentile/date_trunc endpoints; every figure shows its n, excludes (never imputes) missing salary. Needs a chart lib (net-new).

MVP (building now — increment 1)

Ship ONE thing: the Matrix tab as a read-only corpus browser wired into the existing [sessionId]/(browse) AppShell, + relabel the already-built Search/Stats into the tab set.

  • Extend /api/search server-side with facet params (source/company/location/remote/recency) + sort, keep limit/offset pagination (SEARCH_LIMIT_MAX=200).
  • CorpusMatrix.svelte (~200 lines): fetch page → toJobUI(posting) adapter → render via the reused dashboard/table/* sub-tree — NO Yjs, NO @cv/mutations, NO @cv/workspace-client.
  • Columns = honest corpus facts only (Source logo, Title, Company, Location, Posted, First-seen, Open↗) + a structurally-blank "—" Score column.
  • Row click → reused JobDetail drawer, Overview/Description tabs only (Commute/Intel/Scorecard need session/enrichment).
  • Corpus is EMPTY today → every surface ships its empty state first ("No postings yet"); the acceptance bar is that the table looks right at 0, 1, and 200 rows.

Increment 2 (after the overlay store): the decision-field + swipe/keyboard/bulk "SpecialSauce"

  • "Save to session" rightSlot. Increment 3: Board (needs pipeline status) + Trends (needs a non-empty corpus + new aggregate endpoints + a chart lib).

Reuse (verbatim, pure, no Yjs)

ui/src/lib/components/dashboard/table/* (ColumnHeader, DataCell, SourceLogo, ScoreBreakdown, SortBar, ResizeHandle, column-widths, Sortables) · dashboard/job-detail/{JobDetail,OverviewTab, DescriptionTab} · lib/domain/src/{scoring,views,columns}.ts · toolbar/modals/{Views,Columns, Scoring,Kanban} (point onSave at the session store, not client.applyBatch) · ui/src/lib/kanban/*.

Decisions for Julian

  • Approve the triage-product-on-cv-architecture direction + the Matrix-first tab order?
  • The session-overlay model for triage decisions/scores (vs corpus row) — confirmed by the read-only-shared-corpus constraint, but is the overlay a localStorage thing or a real jobcache_sessions config doc?
  • Build Trends analytics (new percentile/date_trunc endpoints + chart lib) after the corpus fills, or is market-intelligence a bigger priority than triage?

kind: journal nodes:

  • jobcache-dashboard-design-2026-06-08 summary: "Working design record for the JobCache dashboard perspective on 2026-06-08." links: event:
    • jobcache-dashboard-design-event-2026-06-08 idea:
    • jobcache-dashboard-product-vision

Source: wiki/content/working-memory/jobcache-dashboard-design-2026-06-08.md