Skip to content

Mark channel messages sent without scope - #482

Merged
dborup merged 3 commits into
masterfrom
feat/480-channels-no-scope-marker
Oct 15, 2026
Merged

dborup merged 3 commits into
masterfrom
feat/480-channels-no-scope-marker

Conversation

@dborup-agent

Copy link
Copy Markdown
Collaborator

Relates to #480

What

Channel messages sent without a region scope now show a compact amber ⚠ no scope marker where a scoped message shows scope: #dk, plus a Scope row with the same marker in packet detail for unscoped GRP_TXT/GRP_DATA. Scoped messages are unchanged. No API or Go change, no per-message requests.

Plan (small, single milestone)

  1. public/app.js: isUnscopedFlood(routeType) (the one decision) and renderNoScope(routeType) (the one markup), next to isPathHashSizeWarn.
  2. public/channels.js: meta line calls renderNoScope when there is no scope name and the route is not transport.
  3. public/packets.js: scopeDetailHtml(pkt) keeps the existing scoped display and adds the marker row for unscoped GRP_TXT/GRP_DATA.
  4. public/style.css: marker reuses --path-hash-warn (amber --warning, as Warn on 1-byte path hash: channel 'Sent with' badge, packet detail and node badge (recommend 2/3-byte) #353/feat(ui): warn on 1-byte path hash in channels, packet detail and node badge (#353) #356); no new colour.

Design decisions

  • Detection is by route type only. FLOOD (1) is unscoped. TRANSPORT_FLOOD (0) is scoped, with or without a known name, so scope_name = '' is never "no scope". DIRECT (2) and TRANSPORT_DIRECT (3) get no marker.
  • API shapes checked against the local server first: channel messages carry routeType (int) and scope (null when unscoped); packet detail carries route_type, payload_type and scope_name.
  • Tooltip and sr-only text state only the firmware behaviour and the recommendation; nothing attributes a low heard-count to the missing scope.
  • Perf: constant-time string building per message/row; no new loops, fetches or caches.
  • Customizer: reuses the existing pathHashWarn token. A switch to hide the marker is a later customizer milestone (AGENTS.md rule 8).

Tests

  • test-issue-480-no-scope.js (new, in test-all.sh): helper table, markup, channels meta-line render, theming.
  • test-packets.js: 7 new cases for the Scope row.
  • test-issue-480-no-scope-e2e.js (new, shard 2): real fixture message and GRP_DATA packet, scoped/unknown/direct/advert absence, light and dark, desktop and 360x800, axe color-contrast, no horizontal overflow.

🤖 Generated with Claude Code

dborup and others added 3 commits October 15, 2026 19:38
An unscoped flood (route type FLOOD) now shows a compact amber "no scope"
marker on the Channels meta line and in the packet-detail Scope row for
GRP_TXT/GRP_DATA. One shared helper pair in app.js (isUnscopedFlood /
renderNoScope) decides from the route type only, never the scope name, so a
scoped transmission with an unknown name is not marked. DIRECT routes get no
marker. Reuses --path-hash-warn and the #353 icon/recommendation style.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The stubbed packet is an unscoped GRP_TXT, so #480 adds a no-scope marker with
the shared path-hash-warn class to the same detail list. The #353 step asserts
that the Hash Size value stays neutral, not that nothing else warns.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@dborup-agent

Copy link
Copy Markdown
Collaborator Author

Rapport — CS-pve-agent2 PR#482 #480 — head d637cb3

Status: Implemented, all CI jobs green on head d637cb37. Draft, not merged. Needs review.

Evidence tags: [T] test run (local or CI), [A] analysis or code reading, [K] known or caveat.

Requirements

# Requirement Test (red before → green after) Mutant (killed by)
1 Channels meta line shows ⚠ no scope (amber --path-hash-warn, warning icon, tooltip and sr-only text) for unscoped messages; scoped messages unchanged test-issue-480-no-scope.js (16 new): aborts before (no helper), 16/16 after [T]. E2E test-issue-480-no-scope-e2e.js against master's frontend: 9 passed, 16 failed; on the branch 25/25 [T] marker for every FLOOD/name-based/always → unit red [T]; text dropped, tooltip changed → red [T]
2 Packet detail Scope row with the same marker for unscoped GRP_TXT/GRP_DATA; scoped display kept test-packets.js (7 new): 7 ❌ before → 194/194 after [T]. E2E covers a stubbed GRP_TXT and the real fixture GRP_DATA packet [T] row dropped → E2E 8 failed [T]; payload gate removed or GRP_DATA dropped → unit red [T]
3 Detection by route type only: FLOOD = unscoped; TRANSPORT_FLOOD (named, empty name, null name) scoped; DIRECT/TRANSPORT_DIRECT, missing and invalid values get nothing helper table in the unit file; E2E has scoped, unknown-name, null-name transport, direct and advert cases [T]. API shapes checked first: channel messages carry routeType (int) and scope (null); packet detail carries route_type, payload_type, scope_name [T] TRANSPORT_FLOOD marked → unit 3 red and E2E 4 red [T]; string form dropped → red [T]. The first E2E version did not kill this mutant, so I added a null-name TRANSPORT_FLOOD case [T]
4 One shared helper (DRY) isUnscopedFlood and renderNoScope in app.js, loaded as shipped by the tests; both call sites use them; a test asserts no inline markup in channels.js [A][T] n/a
5 Theming, WCAG AA in both themes no hardcoded colour in the .no-scope rule (unit) [T]; E2E checks computed colour = --path-hash-warn = --warning ≠ --danger, and axe color-contrast on every marker in light/dark, desktop/360x800 [T] hardcoded colour → unit red; muted colour override → E2E 16 red [T]
6 Browser check, both themes, desktop and 360x800 E2E 25/25; no horizontal overflow, marker on-screen. Screenshots checked by eye for channels (fixture) and packet detail [T] n/a

Invariants

  • cmd/server read-only; the diff touches no Go files [T]
  • No new map[string]interface{} [T]
  • No hardcoded colours; scripts/check-xss-sinks.sh --diff origin/master clean, exit 0 [T]. The tooltip is a constant passed through escapeHtml [A]
  • Fork guards unchanged: no workflow file is in the diff [T]
  • No per-message API calls; constant-time string building [A]

Local runs (fixture prepared like CI: freshen, inline seed, migrate, seeds 2073/199/245/122)

  • node test-frontend-helpers.js: 718 passed, 0 failed [T]
  • eslint@8 on public/*.js: 0 errors [T]
  • sh test-all.sh: 253 passed, 1 failed. The failure was test-test-all.js, because the new suites were not registered. Registered in test-all.sh and scripts/e2e-shards.json; test-test-all.js 8/8 and the shard tests pass after [T]. I did not rerun the whole test-all.sh afterwards [K]
  • cmd/server go test ./...: ok [T]. cmd/ingestor not run locally because the PR has no Go diff; CI Ingestor job passed [K]

CI per job

Job Result
Go Build & Test, Go Server Tests ×3, Go Ingestor, Go Libraries & CLI, Verify Go Test Assignments pass [T]
JavaScript Unit Tests & Lint, Script & Container Unit Checks, Validate Docker Image pass [T]
Playwright E2E 1/3, 2/3, 3/3, Verify E2E Shards & Coverage, Build & Publish Docker Image pass on d637cb37 [T]
Release Artifacts / Publish Badges & Summary / Deploy Staging skipping (fork-guarded) [T]

The first head, c64b52e0, failed E2E 3/3. My new marker carries the shared path-hash-warn class and sits in the same packet-detail list that the #353 E2E scanned for "2-byte must not warn", so 5 variants failed. This was a real interaction, not the known-flaky #271. Commit d637cb37 scopes that check to the Hash Size element (red 20/25 → green 25/25 locally). The Verify and Docker jobs had failed only as a consequence. No reruns.

Leftovers

  • The hide/show choice for the marker is not in the customizer; it reuses pathHashWarn. A later milestone per AGENTS.md rule 8 [K]
  • On staging data about 65% of channel messages are unscoped, so the marker will appear on most of them. It is compact, but the operator should see it live [K]
  • The fixture has no scoped TRANSPORT_FLOOD channel message; scoped cases use channels.js test hooks and stubbed packet responses [K]

@dborup-agent

Copy link
Copy Markdown
Collaborator Author

Review — CS-MacBook PR#482 — head d637cb3

Dom: APPROVE med nits

Evidence tags: [T] test I ran, [A] code reading/analysis, [K] known caveat or not verified. Head checked with git ls-remote before and after: unchanged. Tested on the merged tree (git merge-tree of the head onto origin/master 31dac2b), served from a scratch archive.

Findings

# Severity Finding
1 nit test-packets.js does not kill the "TRANSPORT_FLOOD marked" mutant (194/194 still pass). It is killed by the helper unit test (2 red) and by the E2E (4 red), so the criterion is covered, but the packet-detail unit table has no TRANSPORT_FLOOD row with scope_name: null [T]
2 nit test-issue-353-path-hash-warn-e2e.js failed once on the head (node-detail contrast NaN:1, "fg on bg (none)") while Go suites were running in parallel. Three clean reruns: 25/25 each. Node detail is not touched by this PR and the check passed on the master frontend, so I treat it as timing flake [T][K]
3 nit The marker has no customizer switch; reuses pathHashWarn. The author notes this as a later milestone (AGENTS.md rule 8) [A]
4 info scopeDetailHtml keeps the old inline style="color:var(--text-muted)" for "unknown scope". It is pre-existing, moved unchanged, and uses a CSS variable [A]

No blocking findings.

Answers to the review points

  • Detection by route type [T]: isUnscopedFlood is true only for 1 / '1'. TRANSPORT_FLOOD (0, with name, empty name, null name) is not marked; DIRECT (2) and TRANSPORT_DIRECT (3) are not marked; null/undefined/'x'/4/-1/true/{} are not marked. Channels: msg.scope → "scope: …"; transport routes → "scope: unknown"; only else the marker. Mutant "TRANSPORT_FLOOD marked" fails: unit 2 red, E2E 4 red (21 pass / 4 fail).
  • API shapes [T]: local server on the fixture. GET /api/channels/%23wardriving/messages returns routeType (int) and scope (null) per message. GET /api/packets/<hash> returns packet.route_type, payload_type, scope_name (null for the seeded FLOOD GRP_TXT/GRP_DATA). The code reads exactly these fields. Caveat [K]: the fixture has no scoped TRANSPORT_FLOOD channel message, so scoped cases come from the test hooks and stubbed responses.
  • DRY and style [A][T]: one isUnscopedFlood and one renderNoScope in app.js, next to the Warn on 1-byte path hash: channel 'Sent with' badge, packet detail and node badge (recommend 2/3-byte) #353 helpers. Both call sites (channels.js, packets.js) use them; a unit test asserts no inline "no scope" markup in channels.js. Same path-hash-warn class and warning icon (not error red). The only new CSS is .no-scope { white-space: nowrap }; no colour literal. E2E checks computed colour equals --path-hash-warn and axe color-contrast passes in light/dark at desktop and 360x800 (25/25).
  • Tooltip [A][T]: text states only the firmware behaviour (default flood scope; flood.max.unscoped / region denyf may stop it) plus the recommendation. A unit test rejects "heard/repeats/lower/fewer". Mutant adding "so fewer repeaters hear it" fails 2 unit tests.
  • Scoped messages unchanged / no per-message calls [A][T]: scoped and unknown-scope output is asserted byte-for-byte by name (scope: #dk, scope: unknown, "unknown scope"). The helpers are constant-time string builders; no new fetches.
  • Browser [T]: the author's E2E ran by me against a local server: 25/25 on the head; against the master frontend 9 pass / 16 fail (red before, green after). I did not look at screenshots by eye [K].

Acceptance criteria → tests

Criterion Test Red before?
Unscoped message shows marker + explanation test-issue-480-no-scope.js (16), E2E channels unit aborts on master (helper missing) [T]; E2E 16 fail on master frontend [T]
Packet detail Scope row (GRP_TXT/GRP_DATA) test-packets.js (7 new) + E2E 7 red on master public/ [T]
Route-type detection incl. empty name helper table, channels render tests, E2E mutants below
Scoped unchanged unit + test-packets.js + E2E mutant M3/M5
Contrast AA both themes E2E axe colour mutant below

Tests I ran (merged tree)

  • cmd/server go test ./...: ok (61.9s) [T]
  • cmd/ingestor go test ./...: ok (111s) [T]
  • sh test-all.sh: 256 passed, 0 failed [T]
  • node test-frontend-helpers.js: 719 passed, 0 failed [T]
  • test-issue-480-no-scope.js 16/16, test-packets.js 194/194 [T]
  • E2E on fixture (freshen, seed SQL, corescope-migrate, seeds 2073 and 199): test-issue-480-no-scope-e2e.js 25/25; test-issue-353-path-hash-warn-e2e.js 25/25 on reruns (see finding 2) [T]
  • scripts/check-xss-sinks.sh --diff origin/master: exit 0 [T]
  • CI on the head, per job: all Go, JS/lint, script, Docker, three Playwright shards and the shard/Go-assignment verifiers pass; Badges/Release/Deploy skipped [T]

My mutants

Mutant Result
M1 isUnscopedFlood also true for 0 (TRANSPORT_FLOOD) unit: 2 red; E2E: 4 red
M2 also true for 2 (DIRECT) unit 3 red, test-packets.js 1 red
M3 scopeDetailHtml uses truthy scope_name (empty name falls to marker path) test-packets.js 1 red
M4 tooltip appended with "so fewer repeaters hear it" unit 2 red
M5 channels transport guard removed unit 1 red
M6 E2E-served CSS overrides marker colour with muted E2E 20 of 25 red

Invariants

  • No Go files in the diff; cmd/server stays read-only; no new map[string]interface{} outside tests [T][A]
  • No workflow file in the diff, so the fork guards are untouched [T]; I did not independently recount the 9 and 1 [K]
  • No closing keywords (Relates to #480); commits are authored by dborup <kontakt@meshview.dk> [T]
  • No hardcoded colours added [T]

Not verified

  • Visual check by eye of screenshots (relied on E2E geometry, overflow and axe checks).
  • Behaviour on real staging data (the author notes ~65% of messages will show the marker).
  • Customizer exposure (out of scope for this PR).

@dborup
dborup marked this pull request as ready for review October 15, 2026 21:00
@dborup
dborup merged commit 412f0db into master Oct 15, 2026
18 checks passed
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.

2 participants