Skip to content

fix eight dashboard defects found by driving the live dashboard - #110

Merged
sshaaf merged 9 commits into
sshaaf:mainfrom
samiujan:upstream/dashboard-defect-fixes
Oct 13, 2026
Merged

sshaaf merged 9 commits into
sshaaf:mainfrom
samiujan:upstream/dashboard-defect-fixes

Conversation

@samiujan

@samiujan samiujan commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Description

Eight independent fixes found by auditing the shipping dashboard against a live rgctl serve indexing a
TypeScript repo (5,345 nodes / 11,801 edges / 1,479 functions), driving each tab over the Chrome DevTools
Protocol and capturing DOM evidence. Each fix is its own commit so any of them can be dropped or
cherry-picked independently.

Fixes #112

dashboard/dist is rebuilt in this branch, not left stale. These fixes change dashboard/src, but
the built bundle is a separate committed artifact that the binary embeds at compile time — several
upstream dashboard commits update both together. Rebuilding needed a real wasm-pack build (the build
script substitutes a throwing stub engine when it is missing), so the last commit here carries the
rebuilt assets. Details in Additional Notes.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Performance improvement
  • Documentation update
  • Language support (new language plugin)
  • Refactoring (no functional changes)

Changes Made

One commit each, in the order they appear on the branch:

  1. Taint empty state named a flag that cannot enable taint. It advised
    discover . --languages java --cfg; --cfg does not produce a taint index (--with-taint does, and
    it already implies the CFG pass — src/cli/discover_impl.rs:116), and it hardcoded a language on a
    TypeScript repo. dashboard/src/TaintView.tsx:96-104
  2. Mutation filter defaulted to a CoolStore demo value. useState("ShoppingCart") meant the Field
    mutations panel rendered "0 shown" on every other repo — a working feature that looked broken. An
    empty filter already matches everything (MutationsPanel.tsx:19), so it now starts empty and derives
    its placeholder from index.types. dashboard/src/MutationsPanel.tsx:48,115
  3. Leaked developer stub text. ViewLegend renders its hint prop as visible body copy
    (ViewLegend.tsx:24), and the blast view passed it "Sidebar meta: impact score · direct callers ·
    impact zone". Of nine hint= call sites, only this one was a note-to-self; the rest are legitimate
    legend headings. The view has no colour or shape encoding, so the legend was removed rather than
    given a fake one. dashboard/src/BlastView.tsx:182-185
  4. 0 and undefined rendered as the same glyph. — for BC/Harm/Blast on nearly every row, with
    nothing explaining why. The plumbing is fine — three separate data causes: betweenness is genuinely
    sparse (12 of 5,345 rows nonzero), harmonic is opt-in and hard-zeroed
    (crates/rgctl-analysis/src/centrality.rs:788), and blast is never bulk-filled on a flat call graph
    (src/cli/discover_impl.rs:884; 0 of 5,345). — now means unavailable, a computed zero renders as
    0, and the column tooltips state the gating, which was previously undocumented in the UI.
    dashboard/src/functionListUtils.ts:31-70
  5. The stat row showed the node count twice. "Filtered Nodes" read the same expression as "Total
    Nodes" (App.tsx:98 and :104), and there is no filter state in the shell for it to reflect.
    dashboard/src/App.tsx:104
  6. Footer leaked internal dev metadata — a repo-internal doc path and the payload-format codename, in
    Bootstrap's pink <code> styling, on every page. Replaced with the graph receipt: index time,
    truncated graph digest (full value on hover), dashboard version. dashboard/src/App.tsx:168-171
  7. Two distinct functions rendered as identical sidebar rows. The CFG sidebar listed App twice with
    byte-identical meta for two different function_ids. shortPath kept only the last two path
    segments, so any two files sharing a final directory collapsed (src/routes/orders.ts and
    src/coolstore/routes/orders.ts both became …/routes/orders.ts). The index is not at fault —
    cfg_index.json is keyed by stable_key (storage.rs:196-207); an instrumented run produced 155
    entries / 155 unique ids / 0 duplicate pairs. Now keeps three segments, returns short paths verbatim
    instead of prefixing … needlessly, and exposes the full path via title.
    dashboard/src/functionListUtils.ts:160-169, FunctionListSidebar.tsx:193
  8. Communities were labelled "Louvain". Neither Louvain nor Leiden is implemented — it is label
    propagation with Newman modularity, as docs/design/graph-metrics-design.md:87 already states
    outright. Fixed two user-visible strings; the serialized field name louvain_community_id is
    deliberately left alone, since renaming it would break the migration JSON contract.
    dashboard/src/GraphView.tsx:366, MigrationView.tsx:550

New tests: functionListUtils.metrics.test.ts (6) and functionListUtils.paths.test.ts (4), split so
each commit's tests pass independently.

Testing

  • Unit tests added/updated

  • Integration tests added/updated — cargo test --test dashboard_bundle (project gate)

  • Tested with cargo test — the dashboard bundle gate; full suite not run, no Rust changes

  • Manual testing performed

  • npx vitest run — 21 passed / 21 across 5 files (10 of them new).

  • npx tsc -b — passes, after the wasm build below produced dashboard/wasm/rgctl_wasm.js. On a clean
    checkout this fails with src/worker.ts(3,53): Cannot find module '../wasm/rgctl_wasm.js', which is a
    genuine build prerequisite rather than test noise.

  • cargo test --test dashboard_bundle — 2 passed, confirming the rebuilt dist is structurally valid
    for the include_dir! embed and that discover writes the bundle correctly on the tiny fixture.

  • wasm-pack build crates/rgctl-wasm --target web --release --no-opt — the engine is real, not the build
    script's stub fallback: new module 197,295 bytes, old 190,188-byte module deleted.

  • cargo test was not run in full — no Rust code changed. Note that two lib tests fail on a clean tree
    (cli::agent_pack::tests::embedded_pack_lists_cursor_skills,
    languages::registry::tests::test_registry_creation); verified identical before and after these
    changes.

Test environment:

  • OS: macOS (darwin 25.x)
  • Rust version: n/a — no Rust changes

Checklist

  • My code follows the style guidelines (ran cargo fmt) — n/a, no Rust changes
  • I have performed a self-review of my code
  • I have commented my code where necessary
  • I have added tests that prove my fix/feature works
  • All new and existing tests pass (cargo test) — not run; no Rust code changed
  • No clippy warnings (cargo clippy --all-targets -- -D warnings) — not run; no Rust code changed
  • Documentation updated (if applicable) — column tooltips now document the metric gating; no
    docs/ changes needed
  • CONTRIBUTING.md guidelines followed
  • All commits include DCO sign-off (Signed-off-by: Sami Jan <sami.jan@gmail.com>)

Performance Impact

  • No measurable performance regression

All changes are presentational. The only extra work is index.types[0] for a placeholder and
digest.slice(0, 12) for the footer.

Breaking Changes

None. No API, schema, or serialized field changed.

Screenshots (if applicable)

Not attached — these were diagnosed from CDP screenshots and DOM dumps; happy to attach
before/after captures if useful.

Additional Notes

dashboard/dist is rebuilt in the final commit of this branch. dashboard/dist is tracked
(.gitignore:34 only ignores the root /dist) and compile-time embedded via
include_dir!("…/dashboard/dist") (crates/rgctl-dashboard/src/bundle.rs:7), so source changes under
dashboard/src/ have no effect on the shipped dashboard until dist is rebuilt and committed. Upstream
usually does both together — three of the last four dashboard commits updated src and dist
(9038146, e331284, d9b0f15).

Built with the real toolchain:

rustup target add wasm32-unknown-unknown
brew install wasm-pack
wasm-pack build crates/rgctl-wasm --target web --out-dir dashboard/wasm --release --no-opt
cd dashboard && npm run build

Verified rather than assumed: the new rgctl_wasm_bg-*.wasm is a genuine 197,295-byte WebAssembly
module and the old 190,188-byte one is gone; the fixes appear in the built chunks (the
--with-harmonic tooltip, the footer graph-digest receipt, three-segment path shortening) and the
leaked "Sidebar meta" string does not; tsc -b passes; 21/21 dashboard tests pass; and
cargo test --test dashboard_bundle passes. Rebuilt dist assets change hashed filenames, so the
commit is mostly renames plus index.html — normal for a bundle rebuild in this repo.

Separately worth fixing, not folded in here: scripts/build-dashboard.sh:13-19 silently writes a
throwing stub rgctl_wasm.js when wasm-pack is absent, and npm run build then bundles it happily —
so a contributor without wasm-pack can replace the real ~190 KB engine with a stub that throws at
runtime, and commit the result. It should fail loudly, or refuse to overwrite an existing real engine.
Happy to open that separately.

Two defects found in the same audit are not fixed here, deliberately:

  • 11 top-level tabs wrap to two rows at 1440px, orphaning "Migration Rules". Not a CSS bug — the tab
    bar treats lenses (CFG, Dataflow, Slice, Blast) as destinations, which is an information-architecture
    problem. Patching the wrap would entrench it.
  • Missing favicon → a 404 per page load (no <link rel="icon"> and no icon in dist/).

The empty state told users to run `rgctl discover . --languages java --cfg`,
which neither enables taint (that needs --with-taint) nor applies to a
non-Java repo. `--with-taint` implies the CFG/PDG pass, so it is the only
flag the message needs to name.

dashboard/src/TaintView.tsx:96-104

(cherry picked from commit d1d28e2)
Signed-off-by: Sami Jan <sami.jan@gmail.com>
The Field mutations (CPG) panel seeded its type filter with "ShoppingCart" —
a value that only exists in the CoolStore sample project. On any other repo
the filter matched nothing, so a working feature rendered "0 shown" on first
paint and read as broken.

An empty filter already matches every write (matchesType returns true when
`want` is empty), so start empty and derive the placeholder from the types
that were actually indexed.

dashboard/src/MutationsPanel.tsx:48,115

(cherry picked from commit d8cb093)
Signed-off-by: Sami Jan <sami.jan@gmail.com>
ViewLegend renders its `hint` prop as visible muted body copy. The blast
view passed it "Sidebar meta: impact score · direct callers · impact zone",
a note-to-self about the sidebar, so users read it as a caption.

The view has no colour or shape encoding, so the legend is dropped rather
than given a legend it cannot justify. The import goes with it.

dashboard/src/BlastView.tsx:182-185

(cherry picked from commit 0ef13f1)
Signed-off-by: Sami Jan <sami.jan@gmail.com>
rgctl does not run Louvain, and does not run Leiden either: what ships is
label propagation (Raghavan et al. 2007) with Newman modularity scoring, as
docs/design/graph-metrics-design.md:87 already states outright. Two
user-visible strings still claimed Louvain.

The serialized field name `louvain_community_id` is deliberately untouched —
renaming it would break the migration JSON contract for existing consumers.

dashboard/src/GraphView.tsx:366, dashboard/src/MigrationView.tsx:550

(cherry picked from commit 08c148b)
Signed-off-by: Sami Jan <sami.jan@gmail.com>
"Filtered Nodes" read `manifest.graph.node_count` — the same expression as
the "Total Nodes" card beside it — and there is no filter state anywhere in
the shell for it to reflect. On the audited repo both cards rendered 5345.

The card is removed rather than wired up: with no filter active the honest
value is the total, which is already on screen.

dashboard/src/App.tsx:104

(cherry picked from commit 21a3fe6)
Signed-off-by: Sami Jan <sami.jan@gmail.com>
The footer printed a repo-internal doc path and the payload format codename
in Bootstrap's pink <code> styling, so users saw
"docs/dashboard-design.md · manifest v1 · columnar_v2" on every page.

It now shows what a user can act on: when the graph was indexed, the graph
digest (truncated, full value on hover), and the dashboard version for bug
reports. The digest is the same identity the temporal/diff surfaces key on.

dashboard/src/App.tsx:168-171

(cherry picked from commit 28f6761)
Signed-off-by: Sami Jan <sami.jan@gmail.com>
The Functions table showed "—" for BC, Harm and Blast on every row of the
audited repo, which read as a broken column. The plumbing is actually fine:
`function_metrics.json` reaches the browser and joins on node index
(dashboard/src/worker.ts:106-114). The columns are empty for three separate
reasons, and the formatter hid all of them by collapsing `0` and
`undefined` into the same glyph:

  - betweenness is computed but genuinely sparse — 12 of 5345 rows nonzero
    on the audited repo (sampled Brandes above 500 nodes)
  - harmonic is opt-in and hard-zeroed unless --with-harmonic is passed
    (crates/rgctl-analysis/src/centrality.rs:788)
  - blast is never bulk-filled on a flat call graph, so it is 0 of 5345
    (src/cli/discover_impl.rs:884, blast_radius_scc.rs:202)

`—` now means "not available for this node" and a computed zero renders as
`0`, so the table tells the truth instead of looking dead. The column
tooltips also state the gating, which was previously undocumented in the UI.

dashboard/src/functionListUtils.ts:31-70

(cherry picked from commit fd7fc5d)
Signed-off-by: Sami Jan <sami.jan@gmail.com>
The CFG sidebar rendered two distinct functions as byte-identical rows:
same name, same "…/src/App.tsx · 2 blocks" meta, different function ids.
`shortPath` kept only the last two path segments, so any two files sharing
a final directory collapsed together (e.g. src/routes/orders.ts and
src/coolstore/routes/orders.ts both became "…/routes/orders.ts").

The index is not at fault: cfg_index.json is keyed by stable_key
(crates/rgctl-analysis/src/storage.rs:196-207) and contained no duplicate
ids in the audit — only the display collided.

  - keep three trailing segments, which separates the observed collisions
  - return paths that already fit verbatim, instead of prefixing "…" to
    paths that never needed eliding (src/App.tsx read as "…/src/App.tsx")
  - expose the full path via title, so a row can always be identified

dashboard/src/functionListUtils.ts:160-169, FunctionListSidebar.tsx:193

(cherry picked from commit 82a2aab)
Signed-off-by: Sami Jan <sami.jan@gmail.com>
The eight fixes in this branch changed dashboard/src, but the built bundle
is a separate committed artifact and the binary embeds it at compile time
(crates/rgctl-dashboard/src/bundle.rs:7). Without this commit the fixes were
inert: users would see the old dashboard until someone rebuilt.

That mismatch was the exception, not the norm — three of upstream's last
four dashboard commits updated src and dist together:

  9038146 kantra dashboard support    src 9   dist 24
  e331284 default under user home     src 12  dist 23
  d9b0f15 move to rgctl               src 8   dist 5
  f706150 semantic index vocab        src 2   dist 0

Rebuilt with the real toolchain rather than the build script's fallback:

  rustup target add wasm32-unknown-unknown
  brew install wasm-pack
  wasm-pack build crates/rgctl-wasm --target web --out-dir dashboard/wasm \
    --release --no-opt
  cd dashboard && npm run build

scripts/build-dashboard.sh:13-19 writes a *throwing stub* rgctl_wasm.js when
wasm-pack is absent, which the bundler then bakes in — so rebuilding without
wasm-pack would have replaced the real engine with a stub. Verified instead
that the new module is genuine (197,295 bytes, WebAssembly MVP magic) and
that the old 190,188-byte module is gone.

Also verified the fixes are present in the built output, not just the source:
the --with-harmonic tooltip, the footer graph-digest receipt, and the
three-segment path shortening all appear in the new chunks, and the leaked
"Sidebar meta" stub string does not. The two remaining "ShoppingCart" hits
are documentation examples (the Guide's CLI samples and the Dataflow tab
description), not the removed default — MutationsPanel.tsx now contains zero.

Incidental: dashboard/wasm/package.json was stale at 0.4.17 and is now
0.4.19, which is what wasm-pack regenerates.

tsc -b passes (it previously failed on the missing ../wasm/rgctl_wasm.js
module, which is a real prerequisite, not test noise). 21/21 dashboard tests
pass after the rebuild.

Signed-off-by: Sami Jan <sami.jan@gmail.com>
@samiujan
samiujan force-pushed the upstream/dashboard-defect-fixes branch from e91188d to 33e6e0c Compare October 13, 2026 19:01
@samiujan samiujan changed the title Upstream/dashboard defect fixes fix eight dashboard defects found by driving the live dashboard Oct 13, 2026
@samiujan

samiujan commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor Author

Two force-pushes on this branch after opening it — apologies for the noise, both were fixes to my own
mistakes and the net diff is what the description now says.

  1. DCO. The final commit (the dist rebuild) went in without a Signed-off-by trailer, so the DCO
    check flagged it. Amended and re-pushed; DCO is green now. All nine commits are signed off.
  2. dashboard/dist. The branch originally changed only dashboard/src, which left the fixes inert —
    the built bundle is a tracked artifact that the binary embeds at compile time, so users would have kept
    seeing the old dashboard. It is now rebuilt with a real wasm-pack build, which is why the commit
    count went from 8 to 9 and why the diff is mostly renamed hashed assets.

I also replaced the original description, which still claimed dist was not regenerated. That was true
when I opened the PR and wrong a few minutes later; the current description matches the branch.

No review has happened yet, so nothing was invalidated — but I would rather flag it than leave you
guessing at two push events.

@sshaaf
sshaaf merged commit 8f09655 into sshaaf:main Oct 13, 2026
1 check passed
samiujan added a commit to samiujan/rgctl that referenced this pull request Oct 14, 2026
…monstrate

Two rows were out of date after the rebase onto main:

  - `dashboard/dist` regen was marked **blocked**, "source fixes are not in the
    embedded bundle until dist is rebuilt". That landed in 8f09655 as
    sshaaf#110. The row now says done, and says why it mattered: `dist` is
    compile-time embedded, so until it is rebuilt the source fixes are invisible in
    a built binary while every test passes.
  - Change 7 was blocked on two `rgctl slice` defects. Both are now fixed and under
    review (issues sshaaf#113/sshaaf#114, PRs sshaaf#115/sshaaf#116), so the row records what actually
    gates it.

Also adds open question 4, because the more interesting problem is a gap rather than
a stale value: the maintainer's parking comment on sshaaf#61 names the code browser as the
missing piece, and no change in this sequence is the code browser. Change 1 is the
shell, changes 2-6 are rooms, and none renders source. The prototype does not cover
for it either — the Code lens shows a count of source lines, not source, and Spine
renders an empty state. The one requirement singled out is the one this design
cannot demonstrate.

Recorded as an open question, not invented as a change number: folding source
rendering into change 1 versus giving it its own change ahead of the rooms is a
scoping decision, not a correction.

Signed-off-by: Sami Jan <sami.jan@gmail.com>
samiujan added a commit to samiujan/rgctl that referenced this pull request Oct 14, 2026
…monstrate

Two rows were out of date after the rebase onto main:

  - `dashboard/dist` regen was marked **blocked**, "source fixes are not in the
    embedded bundle until dist is rebuilt". That landed in 8f09655 as
    sshaaf#110. The row now says done, and says why it mattered: `dist` is
    compile-time embedded, so until it is rebuilt the source fixes are invisible in
    a built binary while every test passes.
  - Change 7 was blocked on two `rgctl slice` defects. Both are now fixed and under
    review (issues sshaaf#113/sshaaf#114, PRs sshaaf#115/sshaaf#116), so the row records what actually
    gates it.

Also adds open question 4, because the more interesting problem is a gap rather than
a stale value: the maintainer's parking comment on sshaaf#61 names the code browser as the
missing piece, and no change in this sequence is the code browser. Change 1 is the
shell, changes 2-6 are rooms, and none renders source. The prototype does not cover
for it either — the Code lens shows a count of source lines, not source, and Spine
renders an empty state. The one requirement singled out is the one this design
cannot demonstrate.

Recorded as an open question, not invented as a change number: folding source
rendering into change 1 versus giving it its own change ahead of the rooms is a
scoping decision, not a correction.

Signed-off-by: Sami Jan <sami.jan@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] assorted dashboard defects found driving the live dashboard (8 fixes)

2 participants