feat(analytics): repeater metric scatter tab - #1760
Conversation
Polish review —
|
498b1b6 to
40dc2c8
Compare
Polish re-review (round 2) —
|
| # | Severity | Issue | Status | Evidence |
|---|---|---|---|---|
| 1 | MAJOR | Favorites silently dropped from sample | Addressed | New favs.concat(strided) path; test "ALL favorites survive sampling" exercises 2500 pts incl. favs at strided indices |
| 2 | MAJOR | Sampling cliff at n≈801 | Addressed | POINT_CAP bumped to 2000; stride only kicks in beyond that |
| 3 | MINOR | SVG <title> newlines collapse |
Addressed | · separators; regression test "tooltip uses ' · '" |
| 4 | MINOR | Tick/title overlap with 100.0% labels |
Addressed | pad 50 → 60 (per the review's own recommendation) |
| 5 | MINOR | _axisFmt integer check brittle |
Addressed | Now keyed on axis.score, uses Math.round for counts |
| 6 | MINOR | Hardcoded gray #6b7280 |
Addressed | Both occurrences → var(--text-muted); regression test asserts no hex fallback |
| 7 | NIT | 800 anchors in keyboard tab order | Addressed | tabindex="-1" on each <a>; comment explains Nodes table remains the kb-navigable surface |
| 8 | NIT | Empty-axes draws blank axes silently | Addressed | Explicit in-plot "No repeaters have values…" + regression test |
New findings
No new MAJOR or MINOR. The diff is focused, the test suite grew the right shape (behavioural assertions against the actual SVG, not source-grep), and the only out-of-scope item I'd surface is:
- NIT — favorite-flood degenerate case. If a user somehow favorites >2000 nodes,
budget = max(0, 2000 - favs.length)goes to 0 and all favorites still render unconditionally (so the legend stays truthful), but the perf cap is bypassed. Not realistic on today's network (≤55 repeaters per the prior CDP run) and probably not worth code. Mentioning so reviewers don't re-discover it later.
Tufte / Carmack / Kent-Beck — three-axis sign-off
- Tufte: axis padding now clears the
100.0%label, sampling disclosure remains in-plot, favorite ring still routes throughvar(--text). Graphical integrity preserved through the stride-fix. - Carmack: 2000 SVG circles + anchors is well within budget for the dashboard's other tabs (topology renders comparable counts). No layout thrash, no per-frame work.
- Kent-Beck: round-2 fixes each landed with a behavioural test (
favs survive sampling,"showing N of 2500",no sampling for small set,empty-axes message). Tests assert on actual rendered SVG, not source text. Net-new UI surface exemption still applies; tests-in-same-PR satisfied.
Verification done in this round
- Diff diffed against round-1 HEAD (
1cfa45f→40dc2c83): onlypublic/analytics.jsandtest-repeater-metric-scatter.jstouched, no surface-area expansion. - Test suite executed end-to-end on the PR head (20/20 ✓), including the three new round-2 regression assertions.
- PII preflight clean on this comment.
- No CDP visual re-verification this round — render geometry didn't change beyond
padwidening, and round-1 CDP screenshots at 1280px/360px already validated the layout family.
Thanks for the quick, thorough turnaround — this is merge-ready from the polish side.
40dc2c8 to
e7d0cb8
Compare
|
👋 Hourly polish bot here. Branch is currently Please rebase onto (v2 marker placed so this PR is tracked under the new review pipeline.) |
e7d0cb8 to
b3b8bec
Compare
Kpa-clawbot
left a comment
There was a problem hiding this comment.
Polish re-review (round 3, post-rebase) — feat(analytics): repeater metric scatter tab
Head: 3abde4f (single squashed commit on top of current origin/master, no merge noise). Branch is MERGEABLE again; the CONFLICTING state that gated the prior cron has been resolved.
Verdict: 0 BLOCKER / 0 MAJOR / 0 MINOR / 1 NIT — merge-ready from the polish side.
Verification on 3abde4f:
node test-repeater-metric-scatter.js→ 21/21 ✓ (all round-1 + round-2 regression guards intact).gh pr diff 1760=public/analytics.js(+208/-1),test-all.sh(+1),test-repeater-metric-scatter.js(+123, new). No surface-area expansion vs the round-2 head.- Tab wiring,
AREA_FILTER_TABSmembership, dispatcher case, sampling-with-favorite-preservation,tabindex="-1",var(--text-muted)fallback,·tooltip separators, in-plot "No repeaters have values…" message — all present in the diff. - PII preflight clean on this comment.
All prior round-1/round-2 findings — still addressed
| # | Sev | Issue | Status on 3abde4f |
|---|---|---|---|
| 1 | MAJOR | Favorites silently dropped from sample | ✅ favs.concat(strided) in renderMetricScatter (public/analytics.js); pinned by ALL favorites survive sampling test |
| 2 | MAJOR | Sampling cliff at n≈801 | ✅ POINT_CAP = 2000; stride engages only beyond cap |
| 3 | MINOR | SVG <title> newlines collapse |
✅ · separators; tooltip uses ' · ' test |
| 4 | MINOR | Tick/title overlap with 100.0% labels |
✅ pad = 60 |
| 5 | MINOR | _axisFmt integer check brittle |
✅ Now keyed on axis.score, uses Math.round for counts |
| 6 | MINOR | Hardcoded gray #6b7280 |
✅ Both occurrences → var(--text-muted); pinned by unknown-role test |
| 7 | NIT | Anchors pollute keyboard tab order | ✅ tabindex="-1" per anchor; comment explains Nodes table is the kb surface |
| 8 | NIT | Empty-axes silent blank plot | ✅ Explicit "No repeaters have values for both selected metrics." + test |
Adversarial + Tufte + Carmack + Dijkstra + Kent-Beck (single pass)
- Tufte (encoding): scatter encoding is honest — sampling disclosure stays in-plot when stride engages; favorite ring uses
var(--text)(semantic-neutral, notstatusYellowwhich means "degraded" elsewhere); axes auto-scale via_niceCeilso tiny scores fill the panel; gridlines & axis titles all route throughvar(--border)/var(--text-muted). Caption explicitly states units. Clean. - Carmack (perf at large N): 2000 SVG
<circle>+<a>nodes is well within budget for this dashboard (renderNodesTab/topology already render comparable counts). Single render pass per axis change, no per-frame work, sort-favorites-last is O(n log n) and only runs on the sampled set. No allocation-in-loop hot path. Fine. - Dijkstra (state & URL persistence): axis selection persists via
localStorage(meshcore-repeater-scatter-x/-y) with key validation againstKEYSon read — invalid/stale values fall back to defaultstraffic/bridgewithout throwing.try { setItem … } catch (e) { /* ignore */ }correctly handles quota/private-mode failures. State is local to the tab; no global mutation. No state-machine smells. Minor observation below. - Kent-Beck (TDD + test quality): net-new UI surface, single commit carrying both impl + the test file — repo's net-new-UI exemption applies and is satisfied (test-in-same-PR). Test does NOT string-grep the source for the meat: it
new Functions theREPEATER_METRIC_AXES … renderMetricScatterblock out and executes it against stub globals, asserting on actual SVG output (point count, anchor count, fill colour, escaping, sampling disclosure,tabindex, ring colour, empty-axes message, 2500-point cap, all-favorites stride). 21/21 ✓ locally. Test name describes behaviour, not implementation. Wired intotest-all.sh. Right shape.
New finding
1. NIT — axis selection is localStorage-scoped, not URL-scoped. Other analytics tabs persist via localStorage too (consistent with existing pattern), so this isn't a regression — but URL-state would let users share a "look at traffic vs adverts" link, which is the natural use case for a metric explorer. Defer; not in scope for this PR. (Cross-tab sharing of analytics deep-links is a separate enhancement worth its own issue.)
Out of scope
- Canvas vs SVG for the scatter — SVG fine at ≤2000 nodes (network has ~55 repeaters today per prior CDP run).
- Per-role legend ordering / opacity legend for sampled regions.
- URL-persisted axis selection (see NIT 1).
- Favorite-flood degenerate case (>2000 favorites bypasses perf cap path — round-2 surfaced this; not realistic today).
Thanks for the rebase. Nothing blocks merge from this side.
Polish review (round 3 / post-rebase) —
|
3abde4f to
53b33a7
Compare
Re-review: commit
|
|
The round-4 "Assumed LoRa preset caption / dumbbell tooltip" finding is not present on the current head |
53b33a7 to
46aeefe
Compare
pr-polish v2 re-review — PR #1760 @
|
Status check — merge-from-master onlyHead moved Auto-merge blocked: Go test job FAILING on Operator: review side is clean — gate is CI + community-PR maintainer policy. — mc cron v2 |
Status check — new CI failure after master mergeHead moved CI now RED on Inverse of #1761's failure: your branch declares Master added the #1706 reciprocity gate during this PR's review cycle (see Single fix (community branch — your call to push)Find the { path: '/analytics?tab=repeater-metrics', /* …same shape as siblings */ },Match the format of an adjacent analytics ROUTE entry (e.g. the Action: community branch, comment-only. Once you push that, CI should go green; round-2 review verdict re-applies. — mc cron v2 |
Delta review —
|
Add a "Repeater Metrics" analytics tab that plots each repeater/room as a point across two selectable metrics (traffic_share_score, bridge_score, relay_count_1h/24h, advert_count) already on /api/nodes. Frontend-only. - X/Y axis dropdowns, default Traffic share x Bridge score, persisted - points colored by node role (var(--text-muted) fallback), favorites ringed with the neutral var(--text), click-through to per-node analytics - axis sub-caption documents units; tick labels integer-formatted by axis kind; tooltips use ' · ' separators (SVG <title> collapses newlines); points carry tabindex="-1" to stay out of the keyboard tab order - sampling: cap ~2000 plotted points but keep ALL favorites and stride only non-favorites, with an in-plot "showing N of M points" disclosure, so the legend's favorite count never lies - empty-axis selection shows an explicit "no values" message test-repeater-metric-scatter.js executes the real render pipeline against stub globals (behavioral), with regression guards for ring colour, favorite-safe sampling, tooltip separators, tabindex and the empty-axis message. Wired into test-all.sh.
…t the fallbacks
- node->point mapping (repeater/room filter, traffic_share_score ->
usefulness_score -> null chain, name -> pubkey-prefix -> '?') moves into
a pure _toScatterPoints(nodes, favs) inside the test-extractable block;
renderRepeaterMetricsTab just calls it.
- xSel/ySel change listeners share one wireAxis(sel, key) body.
- tests: 7 new assertions - mapping fallbacks, role filter, fav flag and
_resolveAxis('bogus') falling back to the traffic axis (30 total).
advert_count -> adverts (plus bridge/relay counts) were mapped but never asserted; a typo in a source field name would have passed silently.
8145234 to
9dec4fa
Compare
Delta Review — review(maya) commits (0811a4b, 9dec4fa)Verdict: LGTM — no blockers, no majors. Carmack (frontend logic)
Kent Beck (TDD gate)9dec4fa adds assertions against concrete fixture data ( CI FailureFlake. Minor (0 action needed)No issues in the delta. ✅ Approving the delta. CI failure is a known flake. |
Automated review (parallel personas, consolidated)Verdict: merge-ready carmack: Zero server-side work — this plots existing tufte: Skipped CDP — feature isn't on staging (PR not merged; staging auto-deploys from master). Static code review only: axis units documented in a sub-caption, kent-beck: Test coverage strong ( Playwright failure (run 28999620989): Unrelated flake — MAJOR: none |
Repeater metric scatter tab. Closes #1763.