mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-25 23:53:44 +00:00
## Show each channel message's region scope Each message in the Channels view now shows the region scope it was sent with, as a small chip in the meta line: the region name (for example `#be`), `unknown scope`, or nothing. This is the channel-message part of #1852 by @dborup, extracted as a focused change. #1852 was closed unmerged because it had grown to the whole fork diff. The implementation follows dborup's commitsc686ae3f,350bf7eeanda94d57edon dborup/CoreScope, adapted to current master. dborup is co-author on the commit. ### What changed - `cmd/server/db.go:2099,2195`: `GetChannelMessages` selects `t.scope_name` when the column exists and returns it as `scope_name`. - `cmd/server/store.go:5654`: the in-memory `GetChannelMessages` returns `scope_name`, so the field does not depend on which path serves the endpoint. - `cmd/server/store.go:2966,3244`: both WebSocket broadcast builders carry `scope_name`, so a live message shows its region immediately. - `public/channels.js:342,2278`: `messageScopeChipHtml` renders the chip with the existing `.sa-chip-declared` / `.sa-chip-unmatched` styles from `scope-audit.css`. The name goes through `escapeHtml`. No new CSS. - `public/channels.js:678,695,1435,1487`: the decrypt path and the WebSocket path keep `scope_name` on the message. ### Differences from #1852 - The field is `scope_name`, the name `/api/packets` already uses. - No `routeType` field. `transmissions.scope_name` already tells the states apart: NULL means no transport code, an empty string means a transport code that no configured region key matched. The frontend uses `??`, not `||`, so the empty string is kept. - A chip instead of `scope: <name>` text. The area label from later #1852 commits is not included. ### Perf One extra column per observation row in the page query (at most `limit` transmissions), and one extra map entry per broadcast observation. No new queries, loops or API calls. ### Tests - `cmd/server/channel_message_scope_name_test.go`: the three states through the DB query, the store, `/api/channels/{hash}/messages` over both paths, a schema without the column, and both broadcast builders. 5 of its 6 tests fail without the change; the sixth guards the missing-column case and passes either way. - `test-issue-1851-channel-message-scope.js`: the REST, WebSocket and client-side decrypt paths, escaping, and the name / unknown / none render. 4/4 fail without the change. Registered in `test-all.sh` and the unit step of `deploy.yml`. - Mutation checks: returning `nil` for `scope_name` in the DB path fails the DB and endpoint tests; `||` instead of `??` in the WebSocket path fails the WebSocket test. - `go test ./...` in `cmd/server`: ok. gofmt and go vet clean. ### Browser validation On a staging instance with live traffic (build `e84d2da6`), in Chrome: - `/api/channels/{hash}/messages` carries the `scope_name` key on every message in the 19 channels whose results I read. `#hamradio`, latest 50: 34 named, 1 empty string, 15 NULL. - Opening `#hamradio` renders 104 chips: `#nl` 53, `#be` 32, `#de` 11, `#eu` 6, `#bx` 1 and `unknown scope` 1, and no chip on unscoped messages. Chip text `rgb(26, 26, 46)` on `rgb(238, 242, 255)` in the light theme. ### Not verified - Dark theme not checked. - The real-decrypt branch of `decryptCandidates` has no test and was not exercised in the browser; the already-decrypted branch is tested. - Messages already in the client decrypt cache show no chip until they are decrypted again. - `go test -race` and the Playwright E2E suite were not run locally. Fixes #1851 ## Review follow-up (commit `50346589`) An independent review found no correctness or XSS problem and confirmed DB, store and WebSocket agree on the value. Changed: - **Real decrypt branch tested.** A new test runs the real AES+HMAC decrypt branch in `decryptCandidates` with one packet per scope state; deleting `scope_name` there now fails 2 of 7 tests. - **Tooltip wording.** The unknown-scope tooltip now says the scope "could not be matched to a single region on this instance" (`public/channels.js:338-347`). The ingestor stores an empty name both when no key matches and when several match without exactly one operator-configured key (`cmd/ingestor/region_keys.go:364-393`), so "matches none of the configured keys" was wrong for the second case. - **Old decrypt cache.** Decrypted messages cached before this change had no `scope_name` key and stayed chipless as long as the candidate count did not change. A cached message missing the key now forces one full decrypt; a cache that has it still takes the delta path. A test covers each case. - **Docs.** `docs/api-spec.md` documents `scope_name` on the channel messages response, with the null / empty string / name semantics. Corrections to the description: - **Test counts.** With the `db.go` and `store.go` changes reverted, 4 of the 5 top-level Go tests fail (6 of 7 counting subtests); only the missing-column test passes. - **Broadcast payload.** `scope_name` is added to `pkt`, which is copied into `broadcastMap` and also nested as `packet` (`store.go` ~2974-2980, ~3252-3257), so the key appears twice per observation: 36 bytes for `null`, 48 bytes for `"#belgium"`. - **Side effect on the Packets page.** The live table reads `m.data.packet` (`packets.js` ~1316-1318), so flat rows and expanded group children now show Scope for live packets. In grouped mode a new group copies a fixed field list without `scope_name` (~1384-1395) and shows the empty placeholder until reload. Before this PR every live row showed that placeholder, so this is not a regression. The three copies of the three-state scope rendering (`app.js`, `packets.js`, `channels.js`) are left as they are. --------- Co-authored-by: dborup <3627142+dborup@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>