Skip to content

fix(dashboard): keep reads for a deleted file on the map (BEA-49) - #100

Merged
ssowonny merged 2 commits into
mainfrom
bea-49-ph-scan-bug-dashboard-reports-no-reads-while-the-panel
Aug 2, 2026
Merged

fix(dashboard): keep reads for a deleted file on the map (BEA-49)#100
ssowonny merged 2 commits into
mainfrom
bea-49-ph-scan-bug-dashboard-reports-no-reads-while-the-panel

Conversation

@ssowonny

@ssowonny ssowonny commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

TL;DR

  • Delete or rename a well-read doc and its whole read history vanished from the Dashboard — the one signal the Dashboard exists to give.
  • Same cause as the filed symptom: the file panels joined heat onto the current file tree and dropped whatever didn't match, while the agent-coverage panel beside them rendered those exact reads. One page, one ledger, two answers.
  • Hot path now ranks those rows too, labelled "no longer in the project", and clicking one opens its History (the file view would 404 on it).
  • The two plots stay tree-only — they position by freshness and an orphan has no mtime — but each now carries a count of what it can't show.
  • Known gap: this labels orphans, it doesn't track renames. A renamed file still reads as "gone" plus a new file, and pruning old rows is still out of scope.

What was wrong

Insights.tsx built every point by left-joining the heat map onto flatFiles. Map, Reads × freshness and Hot path are all built from that list, so all three inherited the drop; Agent coverage is built straight from the ?by=device response, which does no join at all. Hence one screen saying both "No reads in the window yet" and "seed-agent read (root) and notes".

What changed

  • lib/heat.ts gains orphanPaths(heatMap, known) — pure, unit-tested (Insights.tsx can't be; node's runner doesn't do JSX).
  • Insights.tsx derives orphans next to pts, respecting the scope filter and the current lens exactly as tree files do. An orphan is never plotted (days never reaches staleColor or the scatter's X(), danger stays false) — an invented position is the dishonesty fix(dashboard): make the knowledge treemap honest on a young project #93 was about.
  • Hot path renders orphans with an .in-hp-gone label and routes their click/Enter to onOpenHistory; Browser.tsx passes its existing openHistory to both <Insights> sites.
  • One footnote under the treemap and the scatter when orphans > 0, nothing at 0. The count follows the lens, so the footnote can never advertise a row Hot path won't list.

Screenshots

Same seeded fixture on both sides: scratch.md has 4 human reads and is deleted by the seed.

Before (origin/main) After
before after

Before: Hot path lists three files totalling 58 reads; the ledger holds 62. The missing 4 are scratch.md's, dropped silently. After: it's ranked in place, labelled, and the plots say one file's reads aren't on them.

Acceptance

  • go build ./..., go vet ./..., go test ./... — pass.
  • npm test — 43 pass (adds the orphanPaths cases).
  • npm run e2e — 99 pass, including two new dashboard specs: the deleted-file row is listed, labelled, footnoted and routes to /<pid>/history/scratch.md; and the footnote follows the lens.
  • Reconciliation, checked against the running hub rather than assumed: under the agent lens the Hot-path totals come to 41, and ?by=device totals 41. (Restated from the spec per the plan — ?by=device is AgentHeat, agent-kind reads only, so it can only reconcile under that lens.)
  • UI evaluated by driving the real hub at 1440×1000 across all three lenses; screenshots above.

Deviations from the plan

One, small: the plan didn't say what an orphan with zero reads under the current lens should do. Left alone, the footnote counted a file Hot path then refused to list (Hot path filters reads > 0), which is the same class of disagreement this PR is fixing — so orphans are filtered by lens reads too, and there's an e2e spec pinning it.

The plan's line references were stale in one place: Insights is rendered from src/apps/Browser.tsx, not src/components/Browser.tsx. No behavioral difference.

BEA-38 was re-checked as the spec asked: resolved in 01f33c9, different cause (the bar's split arithmetic, now in lib/heat.ts), does not share this join. Nothing to co-ordinate.

Land this before BEA-51 — same file, same derivation. BEA-51's empty-state condition has to be scoped.length === 0 && orphans.length === 0, or it will hide the rows this PR adds.

Architecture changes

architecture/webapp-frontend.md — the lib class lists heat.ts's exports by name, and this adds one (orphanPaths). No new seams, no changed ownership.

✅ added · ❌ removed (strikethrough) · unmarked = unchanged

flowchart TB
    components["components"]
    lib["<div style='text-align:left'><b>lib</b><br/>+diff.ts splitLines lcsDiff diffText<br/>+runs.ts groupRuns runFileCount<br/>+heat.ts heatFor heatTotal heatText heatLevel hotPathSplit<br/>+heat.ts ageRange isFlatRange ageSpanLabel (treemap scale)<br/><span style='background:#22c55e55;padding:0 4px;border-radius:3px'>✅ +heat.ts orphanPaths (reads whose file left the tree)</span><br/>+sniff.ts sniffBytes BlobText MAX_BYTES<br/>+utils.ts</div>"]
    components -- "diffText groupRuns hotPathSplit" --> lib
Loading

Build session

cd $(git worktree list | grep bea-49-ph-scan-bug | awk '{print $1}') && claude --resume d84fe4f0-cdab-41b2-851a-063ce31a4713

(only works on the machine that ran the build)

The Dashboard's file panels built every point by joining the heat map onto
the current file tree, so a heat row whose path had left the project was
silently dropped — while the agent-coverage panel below, which does no such
join, rendered those same reads. One page, one ledger, two answers.

The production consequence is the real bug: delete or rename a well-read doc
and its whole read history vanishes from the map, which is exactly the
signal the Dashboard exists to give.

Hot path now ranks orphaned rows alongside tree files, labelled "no longer
in the project" and opening that path's History (the file view would land on
the not-found page). The two plots stay tree-only — both position by
freshness and an orphan has no mtime, so any position would be invented —
but each carries a count of what it can't show. "No reads in the window yet"
can now only render when the scope genuinely has none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ssowonny
ssowonny merged commit fe872dd into main Aug 2, 2026
2 checks passed
@ssowonny
ssowonny deleted the bea-49-ph-scan-bug-dashboard-reports-no-reads-while-the-panel branch August 2, 2026 23:11
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.

1 participant