Symbols Page v2 — Symbol-First Intelligence Browser
Symbols Page v2 — Symbol-First Intelligence Browser
Status: Planning Assignee: gabriel Priority: P0 — primary navigation surface for symbol intelligence
Revised Vision
The Symbols page is the symbol browser — not a repo dashboard.
The Intel page (/intel) already owns repo-level health: health gauge,
alert strip, hotspot/dead/blast-risk cards. Building a second bento dashboard
on /symbols duplicates that entirely and splits the user's mental model.
The right question /symbols answers:
"What symbols exist in this repo and which ones should I care about right now?"
Every row in the symbol table IS the dashboard — for that symbol. The page becomes a data-dense, keyboard-navigable, filterable table where each row surfaces the complete signal picture for one symbol without a click.
Intel = repo-first (metric → symbols). Symbols = symbol-first (browse → detail). These must not overlap.
What Changes
REMOVE from scope (already covered by /intel):
✗ 6-panel bento dashboard (health score, churn chart, gravity map,
dead-code panel, kind donut, author leaderboard)
✗ musehub_repo_dashboard pre-compute table
✗ Repo-level vitals hero duplication
KEEP — what already exists and is good:
✓ Hero banner (compact stat strip: total, hotspots, dead, blast risk)
✓ Live-filter search input
✓ Op/kind filter pills
✓ Cursor pagination
ADD — the actual v2 work:
✓ Per-row sparkline (change frequency mini-chart, last 30 commits)
✓ Per-row coupling score (# symbols coupled to, from pre-computed table)
✓ Per-row blast indicator (downstream symbol count)
✓ Per-row age label (first_introduced relative, from symbol_vitals)
✓ Keyboard navigation — j/k to move, Enter to open detail, / to focus search
✓ Spectral row highlight on hover (border-glow effect)
✓ Signal chips inline: HOTSPOT · DEAD · HIGH-BLAST (vs separate column)
✓ Kind filter remembers state in URL param
✓ Empty-state narrative for new repos
Row Design (the core of v2)
┌─────────────────────────────────────────────────────────────────────────────┐
│ fn musehub/services/musehub_wire.py::_to_wire_commit HOTSPOT │
│ │
│ ▁▃▅▇▅▃▁▂▄▆▅▂ 14× coupled: 8 blast: 23 born 4mo ago ↗ 2.1×/wk │
└─────────────────────────────────────────────────────────────────────────────┘
kind address (full, ellipsized) signal chips
sparkline change_count coupling_count blast_count age velocity
All values from pre-computed tables (musehub_symbol_vitals,
musehub_symbol_coupling) — zero aggregation at request time.
Data Already Available (from Phase 0 / fast-reads work)
musehub_symbol_vitals — first_introduced, change_count, version_count,
op breakdown, avg_velocity (can compute from count + age)
musehub_symbol_coupling — shared_commits per co_address → COUNT = coupling_score
musehub_symbol_history_entries — message, commit_branch (denormalized)
The coupling count per symbol needs one extra pre-computed column:
coupling_count INT on musehub_symbol_vitals — incremented at push time.
Blast radius count (# symbols this one co-changes with) = same as coupling_count.
Implementation Phases (load-bearing order)
Phase 1 — Data: coupling_count on vitals (½ day)
┌─────────────────────────────────────────────┐
│ musehub_symbol_vitals │
│ + coupling_count INT DEFAULT 0 │
│ │
│ Indexer: after _upsert_symbol_coupling() │
│ → UPDATE vitals SET coupling_count = │
│ (SELECT COUNT(*) FROM coupling │
│ WHERE repo_id=? AND address=?) │
└─────────────────────────────────────────────┘
Migration: 0032_symbol_vitals_coupling_count.py
Tests: V101–V105 (column exists, populated, accurate, idempotent, cascade)
Phase 2 — Route: enrich symbol list rows (½ day)
symbol_list_page() currently returns:
address, kind, op, committed_at, heat (change_count from entries)
Add from vitals JOIN:
first_introduced, change_count (authoritative), coupling_count,
version_count, op breakdown
SQL: LEFT JOIN musehub_symbol_vitals ON (repo_id, address)
No extra queries — one join, same pagination.
Tests: R201–R205 (row includes vitals fields, null-safe, paginated correctly)
Phase 3 — Template: data-dense rows (1 day)
┌──────────────────────────────────────────────────────────┐
│ SYMBOL TABLE v2 │
│ │
│ [/] search... [all ▾] [fn] [cls] [file] [hotspot only] │
│ │
│ ▸ fn path/to/file.py::symbol_name HOTSPOT │
│ ▁▂▄▇▄▂▁ 14× coupled:8 4mo 2.1×/wk │
│ │
│ ▸ cls path/to/other.py::ClassName │
│ ▁▁▁▂▁▁▁ 3× coupled:2 1yr 0.1×/wk │
│ │
│ < prev 1–50 of 847 next > │
└──────────────────────────────────────────────────────────┘
Spectral hover: row gets --border-glow + spectral-text on address
Keyboard: j/k = move focus Enter = open detail / = search
CSS: .sym2-row--v2, .sym2-sparkline, .sym2-coupling-badge
No JS framework — vanilla <script> block, keyboard listener on table.
Tests: T301–T308 (template renders vitals, sparkline data attr, keyboard attrs present, signal chips, spectral class applied, pagination)
Phase 4 — Spectral Polish (½ day)
• Focused row: spectral-glow border left edge (4px, gradient)
• Signal chips: HOTSPOT=amber-glow, DEAD=frost-glow, HIGH-BLAST=crimson-glow
• Search input: spectral focus ring on :focus
• Stat strip: subtle spectral gradient underline (not a full bento panel)
• Zero layout changes to /intel — it stays as-is
Tests: T401–T403 (CSS classes present in rendered HTML)
Phase 5 — Empty State + Onboarding (¼ day)
┌─────────────────────────────────────────────┐
│ │
│ ◈ No symbols indexed yet │
│ │
│ Push your first commit to build the │
│ symbol index. Every function, class, │
│ and file will appear here with full │
│ provenance — automatically. │
│ │
│ [ View docs → ] │
│ │
└─────────────────────────────────────────────┘
Tests: T501–T502 (empty state shown when index_status=not_built, hidden otherwise)
Seven-Tier Test Coverage
Tier 1 — DB schema V101–V105 (vitals coupling_count column)
Tier 2 — Indexer unit V201–V205 (coupling_count populated correctly)
Tier 3 — Route unit R201–R210 (list endpoint returns vitals join data)
Tier 4 — Template T301–T315 (row renders all data-dense fields)
Tier 5 — Integration I401–I410 (push → index → list shows coupling_count)
Tier 6 — Accessibility A501–A505 (keyboard nav attrs, aria-labels)
Tier 7 — Visual/E2E E601–E605 (spectral classes, empty state)
Total: ~45 test IDs across 3 test files.
Muse Intel Pre-Read (before starting each phase)
muse -C ~/ecosystem/musehub code grep "symbol_list_page" --json
muse -C ~/ecosystem/musehub code impact "musehub/api/routes/musehub/ui_symbols.py::symbol_list_page" --json
muse -C ~/ecosystem/musehub code deps "musehub/api/routes/musehub/ui_symbols.py" --json
muse -C ~/ecosystem/musehub code hotspots --top 5 --json
Branch Strategy
muse checkout -b feat/symbols-v2-p1-coupling-count --intent "add coupling_count to vitals" --resumable
muse checkout -b feat/symbols-v2-p2-route-join --intent "enrich list rows with vitals join" --resumable
muse checkout -b feat/symbols-v2-p3-template --intent "data-dense symbol rows" --resumable
muse checkout -b feat/symbols-v2-p4-spectral --intent "spectral polish on symbol table" --resumable
muse checkout -b feat/symbols-v2-p5-empty-state --intent "empty state onboarding" --resumable
Acceptance Criteria
- Each symbol row shows: sparkline, change_count, coupling_count, age, velocity
- j/k keyboard navigation works, Enter opens detail, / focuses search
- Spectral glow on focused row (no layout shift)
/intelpage is untouched — zero overlap- All ~45 tests pass
- Symbol list page loads in <200ms (single JOIN, no aggregation)
- Empty state renders correctly for un-indexed repos