Repository navigation
My Mesh: status evidence, SNR attribution and traffic metrics (#304 remainder) - #586
Conversation
Relates to #304 (points 3, 4 and 5; 1, 2 and 6 landed in #351/#352). 3. Status evidence. A new shared helper (public/my-mesh-activity.js) reads the node-health payload and reports which evidence carries the badge: stats.lastAdvert (the node's own advert) and the relay activity behind stats.lastHeard are shown as separate rows, the relay row only where the evidence supports it. A badge that rests only on an attributed wire-hash prefix is rendered as attributed — hollow dot, provisional accent, "· relay-attributed" — never as a confident green. Thresholds stay in roles.js (#539); the helper defines none. 4. SNR. The Excellent/Good/Marginal/Poor verdict and its colour are gone. The mean is labelled with the sample scope it covers (n=…) and is only shown when a direct reception proves it belongs to this node (#76). The shared API field keeps its meaning. 5. Traffic. "Packets today" (a UTC calendar-day counter) is replaced by the rolling 24-hour count with its population and window stated, a lower bound (≥) when the history does not cover the window, and an em dash when the window is unknown. activity24h gains `previous`, the equally long window before it, so a comparison is like-for-like; it is shown only when both windows are complete and never as a percentage of a zero baseline. Avg hops moves into the evidence disclosure as "Avg observed path length", with its route/sample meaning. The card keeps its #351 compactness: the metrics are one row of three at every width and the explanation sits in a native <details> that is reachable by keyboard and touch. No new API request — the existing per-card health call carries everything. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The frontend no-undef lint (Kpa-clawbot#1342) reads .eslintrc.json's globals, so a new window-scoped helper has to be declared there like MyMeshObservers (#262). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Rapport — CS-Macmini PR#586 #304 — head a6b8c45Status: All three remaining #304 points implemented; every point has a test that is red before and green after plus at least one killed mutant; all 17 CI jobs pass. Requirement coverage
Server-side change
Compactness (#351's bound preserved)[K] Measured on the 51-observer repeater card: 1280 px 337.8 → 343.8 px, 390/375 px 349.1 → 329.1 px. Both stay inside the CI (run 40172746937, head a6b8c45)
The first push failed Local before pushing: Checks[K] Adjacent tests updated (behaviour change, not weakened assertions)
Remainder
|
…tests - Panel banner adds "relay-attributed" only for relay-sourced evidence, never for "no confirmed activity". - Pin the panel's no-confident-green rule (banner class and icon). - Direct SNR label counts "receptions" (all direct receptions); the tip says the mean covers only those that carry an SNR. - Traffic tip: "resolved path", no "confidently"/"below". - Pin a payload without a boolean complete flag as a lower bound. - Benchmark with 30K rows spread over 7 days (exercises the 24-48 h band). Relates to #304 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Relates to #304
Points 1, 2 and 6 of the issue landed in #351 and #352. This PR implements the
remaining points 3 (status evidence), 4 (SNR attribution) and
5 (traffic metrics) on the My Mesh cards and the home health panel, and
builds on the observer identity #572 (#262) just added.
Plan
(AGENTS.md rule 4):
stats.lastAdvertis strictly the node's own ADVERT,stats.lastHeardis that advert or identity-safe relay evidence(whichever is newer),
stats.avgSnris already the mean over directreceptions since Node "Heard By" card: decide whether to show direct-RF observers (defer) #76, and
activity24his the rolling window from My Mesh: accurate 24h node activity and compact repeater observer cards #351.(
public/my-mesh-activity.js, the shape My Mesh: show observer identity, status and traffic separately from mesh role #262 established) so the cards andthe health panel cannot drift apart, and so every rule is unit-testable.
the card already fetches, never as a new request.
<details>, and the metric grid loses a tile.What changed
3. Status evidence
Last own advertandLast attributed relay activityare separaterows in a new
Status evidencedisclosure on each card. The relay row isonly filled where the evidence supports it: the payload exposes one combined
lastHeard, so relay activity is reported exactly when it is newer thanthe node's own advert, and otherwise says
none newer than the last own advertrather than repeating the advert's time.the badge the dot stays filled and green. When the only evidence recent
enough is traffic attributed to the node by a short wire-hash prefix, the
badge becomes
Active · relay-attributed, the dot is hollow and the card'saccent drops to the provisional colour — an ambiguous match never produces
a confident green. The disclosure explains that a prefix unique among
known relays can still belong to an unknown one, and that a recent advert
is not proof of continuous uptime.
getHealthStatus/getHealthLastHeard(roles.js,fix(health): apply role thresholds without weakening activity evidence #539) remain the single source of truth and are injectable; the helper
defines none and the test asserts it.
4. SNR
Excellent / Good / Marginal / Poorverdict and its colour are removedfrom both the card and the health panel. Until Node "Heard By" card: decide whether to show direct-RF observers (defer) #76's attribution decision
is made, the card explains the metric instead of grading the repeater.
Direct SNR · n=42), andthe value is only shown when at least one direct reception proves it belongs
to this node — a mean with no direct attribution behind it would describe
whichever node the observer actually heard.
stats.avgSnritself is untouched: no shared health API metric isredefined.
5. Traffic
Packets today(a UTC calendar-day counter) becomesTransmissions · 24 h, the rolling window's own count, with thepopulation and window in the tooltip and as a
Counting windowrow in thedisclosure (a
titleis not reachable by touch).≥ 35) and an unknown windowas an em dash — never as a measured zero.
activity24hgainsprevious(windowStart,windowEnd,count,complete): the equally long window right before the rolling one. Thecomparison is shown only when both windows are proven complete, a
missing history is reported as
no comparable 24 h history, and a zerobaseline yields an absolute delta, never a division.
Avg hopsleaves the headline grid for the disclosure asAvg observed path length, explained as the mean hop count of theobserved paths of the node's transmissions — not a distance and not health.
Nothing labels node-associated traffic as forwarded traffic.
Cost and compactness
already makes (
/api/nodes/{key}/health?include=advertIntervals); the fiveapi(call sites inhome.jsare unchanged and pinned by a test.previousis filled by the same single pass over thenode's index that already fills the hourly buckets. The only added cost is
the RFC3339 parse for rows 24–48 h old that the string pre-filter used to
reject — the same order as the per-transmission work
GetNodeHealthalreadydoes for every row (it calls
heard.addon each). No new scan, no new lock,no new allocation per row.
every width (it was four over two rows below 600 px) and the explanation
is a closed
<details>. Measured on the 51-observer repeater card:1280 px 337.8 → 343.8, 390/375 px 349.1 → 329.1 — still inside the
< 350bound My Mesh: accurate 24h node activity and compact repeater observer cards #351 pinned, and lower than before on mobile.
Testing
Red before, green after for every point, each with at least one mutant; see
the report comment for the full table. Locally:
go test ./...incmd/serverandcmd/ingestor,sh test-all.sh(271 files),node test-frontend-helpers.js, and the affected Playwright suites against alocal Go server on the committed
e2e-fixture.dbprepared as in CI.