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 ascorpus_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 inui/src/lib/components/dashboard/{table,job-detail}/,ui/src/lib/kanban/,lib/domain/src/{scoring,views,columns}.ts— NOT Yjs-coupled (onlyDashboard.svelteimports@cv/mutations/@cv/workspace-client, which PRODUCT-ENGINE.md forbids importing into JobCache). So net-new footprint is small.
Tab structure
- 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.
- Search — existing free-text + facets over
/api/search; a deep-link into a pre-filtered Matrix (already built). - Stats — existing
/api/statscards (total, sources, 7d-fresh, top companies); the honest empty-corpus landing (already built). - Board (post-MVP, session-only) — reused
ui/src/lib/kanbanover shortlisted rows by pipeline status; needs a session overlay. - Trends (post-corpus) — the genuine aggregate differentiator: salary percentiles +
postings-over-time via NEW percentile/
date_truncendpoints; 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/searchserver-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 reuseddashboard/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
JobDetaildrawer, 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_sessionsconfig 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