Repository navigation
feat(packets): optional Hide CONTROL packets filter (#96) - #211
Conversation
Red tests for the opt-in "Hide CONTROL packets" checkbox on the packets page: - test-issue-96-hide-control.js (unit, in test-all.sh): the saved/URL preference (off by default, explicit "0" kept, URL wins), the CONTROL-only filter (order kept, no copy when off) and hideControl=1 in the URL hash. - test-issue-96-hide-control-e2e.js (Playwright step in deploy.yml, against the e2e fixture's 4 CONTROL packets): default shows them, checking hides exactly them without a new /api/packets request, the choice survives a reload both ways, the URL wins, a direct link still opens a CONTROL packet, and the checkbox is reachable at phone width. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mVL1VB4jtY7caZQnMWJpW
An opt-in "Hide CONTROL packets" checkbox in the packets filter bar (toggles group, next to My Nodes). Unchecked by default, so nothing changes for users without a saved choice. - Display only: the filter runs in renderTableRows' existing chain, right after the type filter, as one pass over the loaded packets (filterHiddenControl). CONTROL packets are still fetched and kept, live ones too, so unchecking shows them again without a reload or a new API request. Ingest, retention, statistics and anomaly detection are untouched. - A pinned hash (a direct link to one packet) bypasses it, like every other client filter. - Saved as meshcore-hide-control = '1' or '0', so an explicit unchecked choice survives a reload. hideControl=1 is in the URL hash while it is on (buildPacketsQuery); a URL value wins over the saved choice for that view (readHideControlPref). - An empty list caused by the filter says so. - Styled with CSS variables only; the checkbox keeps a 16 px box and the label is the 44 px touch target on coarse pointers. Test updates: the #121 and #147 sandboxes declare the new closure state that buildPacketsQuery reads; the E2E also pins the checkbox size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mVL1VB4jtY7caZQnMWJpW
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mVL1VB4jtY7caZQnMWJpW
In CI the E2E step runs after other suites that add packets to the same server, so the list held 509 packets against the 500 the test's API snapshot saw. The list may hold more than that snapshot, never fewer; the later steps compare against the list's own count and passed unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018mVL1VB4jtY7caZQnMWJpW
Rapport — CS-cloud PR#211 #96 — head 23b89f5Status: Draft PR with all acceptance criteria met: the tests were red on master and are green on the branch, and CI is green on the head. Ready for review; it was not set to ready, merged or closed. Evidence tags: [T] test or CI run, [A] analysis, [K] known, not run again. Branch
Acceptance criteria
Tests
[T] The two existing unit tests that extract
MutantsEach mutant was applied to a copy of the branch, then the unit test and the E2E were run against a server serving that copy:
All five are red with the mutant and green on the code. [T] A sixth mutant, dropping a CI (head
|
| Job | Result |
|---|---|
| Go Build & Test | success |
| Playwright E2E Tests | success; the log shows the #96 E2E "7 passed, 0 failed" |
| Build & Publish Docker Image | success |
| Release Artifacts / Deploy Staging / Publish Badges & Summary | skipped (PR) |
[T]
The first CI run, on 24380db3, failed in this PR's E2E step 1 only. The list showed 509 packets against the 500 in the test's API snapshot, because earlier E2E suites in the same job add packets to the server. Commit 23b89f5b makes that assertion "at least the snapshot". The steps that hide exactly the 4 CONTROL packets compare against the list's own count, and they passed in both runs. [T]
Remaining
- [A] Row limit: with the mobile row limit of 1,000, if most of the newest 1,000 packets are CONTROL, hiding them leaves fewer rows. Older non-CONTROL packets outside that load are not fetched to fill the gap. The existing type filter behaves the same way, and avoiding it would need a server-side filter, which is out of scope (no extra API calls). The empty-list message says why the list is empty.
- [A] Clear Filters does not reset the checkbox: it is a display preference, like Group by Hash and Hex Paths.
- [A] Live updates: no test injects a live WebSocket packet. That they follow the checkbox rests on code analysis: they merge into the loaded list and render through the same
renderTableRowspath. - Not done here: staging verification, and the customizer (no new configurable value).
Generated by Claude Code
Review — CS-MacBook PR#211 hide-control — head 23b89f5Dom: APPROVE med nits The filter does what the issue asks: opt-in, type 11 only, one pass in the existing chain, live packets follow the checkbox with no reload, and the URL/saved/default order is right. The nits are an unguarded Evidence: [T] = ran it, [A] = read in source or diff, [K] = taken from the author's report, not re-verified here. This review was read-only. Findings
No blocking defects found. 1. Behaviour
2. Live updates [T]I inserted real rows into the fixture DB (CONTROL and GRP_TXT, as an ingestor would) so the server's poller broadcast them over the WebSocket; the page was on
14 of 14 [T]. [A] why: the WebSocket handler only filters by window, type, observer, region and node, and always merges into 3. Deep link and persistence [T]
4. Performance [T]In-page on the fixture's packets page, 30,000 and 50,000 synthetic packets (1 in 97 CONTROL), 5. UI [T, A]
6. Tests [T]
All seven killed. [T]
7. Rules
Not verified
|
dborup
left a comment
There was a problem hiding this comment.
Review: changes recommended before merge
Reviewed head 23b89f5bf4bb5686cc317391cce0a6acb0f76ebd against master 8bafcdf2a6bb0014127bce4822c8ad0399bd2f12. The head was rechecked before posting and is unchanged.
Two P2 findings are attached inline, both reproduced in a local Chromium browser:
- Filtering only after the capped fetch can leave an empty list with no way to load older matching packets. This misses the historical-loading acceptance criterion in #96.
- URL serialization drops an explicit
hideControl=0, so a saved enabled preference takes over after reloading the same page.
The basic behavior otherwise worked in the checks performed: default visibility, checkbox persistence, direct CONTROL links, and incoming live packets being hidden and restored without being discarded. No backend changes are included in this PR.
Verification
- Local
test-all.sh: 215/215 files passed. - The PR's
test-issue-96-hide-control-e2e.js: 7/7 passed against a disposable local fixture. - Independent browser reproduction of the URL override problem used the real fixture: 499 rows before reload, 495 after reload, with the saved preference unchanged at
1. - Independent historical-loading reproduction used controlled API responses: 1,001 transmissions, newest 1,000 CONTROL, then one ADVERT, all within the selected time window. The mobile page requested
limit=1000, displayed zero rows and issued no continuation request. - Controlled live WebSocket messages: the visible count increased from 1 to 2 after one CONTROL and one ADVERT arrived while hiding was enabled; unchecking restored the CONTROL and the count became 3.
- Current CI Go, Playwright and Docker checks are green. Full Go suites were not rerun locally for this frontend-only change.
The existing green tests do not cover the two failing end-to-end scenarios. No staging or production validation was performed, and no implementation changes were made.
Posted as a comment review because the signed-in account is the PR author; the recommendation remains to address both findings before merging.
| } | ||
| // #96 — Hide CONTROL packets (display only; a pinned hash bypasses it). | ||
| const beforeHideControl = displayPackets.length; | ||
| if (!hashOnly) displayPackets = filterHiddenControl(displayPackets, hideControl); |
There was a problem hiding this comment.
[P2] Do not let hidden CONTROL packets exhaust the fetched history window
This removes CONTROL only after loadPackets() has fetched the capped list (PACKET_LIMIT is 1,000 on mobile and 50,000 on desktop). If the newest 1,000 transmissions are CONTROL and the next older transmission is an ADVERT within the selected time window, enabling this filter leaves the mobile list empty. There is no continuation request or load-more path to reach the matching ADVERT. I reproduced this with a browser and a controlled 1,001-transmission response: the only list request had limit=1000, the visible count was zero, and the remaining matching transmission was never requested. This violates #96's explicit requirement that hiding CONTROL must not strand historical browsing on an empty page when matching packets exist. Apply the exclusion before the retrieval limit or provide a bounded continuation mechanism while keeping direct links and unchecking behavior intact. Add a browser regression with more than one fetch window and the only non-CONTROL packet beyond the first window.
| if (filters.observer) parts.push('observer=' + encodeURIComponent(filters.observer)); | ||
| if (filters.channel) parts.push('channel=' + encodeURIComponent(filters.channel)); | ||
| if (filters._filterExpr) parts.push('filter=' + encodeURIComponent(filters._filterExpr)); | ||
| if (hideControl) parts.push('hideControl=1'); // #96; off (default) is omitted |
There was a problem hiding this comment.
[P2] Preserve an explicit hideControl=0 URL override
Omitting the parameter whenever hideControl is false loses the distinction between the default and an explicit URL override. With meshcore-hide-control=1 saved, opening #/packets?timeWindow=525600&hideControl=0 initially shows CONTROL correctly, but renderLeft() calls updatePacketsUrl() and this serializer removes hideControl=0. Reloading that same page then restores the saved enabled preference and hides CONTROL. In Chromium on the local fixture the count changed from 499 to 495 after reload, while localStorage remained 1. Preserve the explicit off override in the canonical/shareable URL without changing the saved preference. Add a browser regression covering saved 1 plus URL 0, the initialization URL rewrite and a reload; the pure preference-reader test alone does not exercise this failure.
Red on the current branch: - unit: storage helpers that survive a throwing localStorage, the checkbox change still filtering and updating the URL when the save fails, hideControl=0 kept while the saved choice is on, and the empty-list note blaming CONTROL only when it is the cause (12 failing); - E2E: hideControl=0 over a saved "1" through the load-time URL rewrite and a reload, the empty-list note with an expression that matches nothing, and a throwing setItem on the checkbox (3 failing). Relates to #96 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A throwing localStorage no longer stops the checkbox: the change goes through setHideControl, which saves best effort (saveHideControlPref) and then always updates the URL and re-renders. The saved choice is read through readStoredHideControl, and the observer/type filter reads at module load are guarded too. - An explicit hideControl=0 survives URL rewrites: while the saved choice is on, off is written as hideControl=0, so a reload of the URL shows what was on screen. savedHideControl tracks the saved choice and only changes when a save succeeds. - The empty-list note names CONTROL only when hiding it emptied the list: controlHidingEmptiedList runs the hidden CONTROL packets through the observer and expression filters, on the empty-list path only. - buildPacketsQuery now reads hideControl and savedHideControl; the code says so for test sandboxes, and the #121/#147 sandboxes declare both. Relates to #96 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rapport — CS-Minimax PR#211 runde 2 — head 39d1651Status: Round 2 pushed to the same branch, still draft. Nits 1–3 and info 5 are fixed with tests that were red first; 8 of 8 new mutants are killed; local unit and E2E tests are green. CI is red only on the Kpa-clawbot#1122 Details-clamp E2E, which fails the same way on master Review feedback addressed (commit Evidence: [T] = I ran it, [A] = read in source or diff, [K] = taken from an earlier report, not re-verified here. Branch and commits:
Nit 1 — a throwing
|
| Test | Result |
|---|---|
sh test-all.sh |
218 passed, 0 failed (218 files), exit 0 |
node test-frontend-helpers.js |
707 passed, 0 failed |
node test-issue-96-hide-control.js |
28 passed (14 before this round); 12 of 28 red before the fix |
node test-issue-121-clear-filters-selection.js |
10 passed |
node test-issue-147-packets-url-detail-params.js |
all passed |
node test-clear-filters.js |
all passed |
E2E setup:
- A local Go server built from the branch, on a scratch copy of
e2e-fixture.db. - The fixture was prepared as in
deploy.yml:freshen-fixture.sh, the Packets page collapse button in the left column of the table opens the dialog. Kpa-clawbot/CoreScope#1486/"Group Data" message type missing from packet view window's "message type" filter Kpa-clawbot/CoreScope#1791 seed,corescope-migrate, then the [feat] node detail, separate flood-adv from zero-hop adv Kpa-clawbot/CoreScope#2073 and Active observers vanish as nodes after retention.nodeDays without an advert; observer link dead-ends on 'Node not found' #199 seeds. - The server served the branch's
public/. It was started with nohup and stopped by the pid of the port listener, not$!.
| E2E | Result |
|---|---|
test-issue-96-hide-control-e2e.js |
10 passed; the 3 new steps were red before the fix, the 7 old ones green |
test-issue-147-packets-url-obs-e2e.js |
12 passed |
test-issue-180-packets-url-modal-e2e.js |
12 passed |
test-filter-ux-e2e.js |
12 passed |
Mutants: 8 of 8 killed. Each was applied alone to public/packets.js and the file was restored afterwards, byte-identical to the fix (cmp).
Rules:
scripts/check-xss-sinks.sh --diff origin/master: exit 0, nothing flagged.- Fork guards:
== 'Kpa-clawbot/CoreScope'occurs 9 times indeploy.ymland once inrelease-fast-path.yml, unchanged. This round changes no workflow. - No closing keywords in the commits or the PR body, which still starts with
Relates to #96.
Browser [T]: in the built-in browser against the same local server:
- Saved
1plus…&hideControl=0, then a reload: unchecked, all packets listed, the URL keepshideControl=0, and the saved choice stays1. - The two empty-list notes are as in the E2E.
CI
Run 37276455116 on 39d16513 [T]:
| Job | Result |
|---|---|
| Go Build & Test | success |
| Playwright E2E Tests | failure, in test-issue-1122-details-row-clamp-e2e.js only |
| Release Artifacts | skipped |
| Build & Publish Docker Image, Deploy Staging, Publish Badges & Summary | skipped (after the failure) |
Not caused by this PR:
- The failure: 2 of 18 steps failed,
[desktop-1200]and[tablet-900]"advert links in Details stay visible and clickable":advert link in Details is not hit-testable … "R5-D4 300D Rak". These are the only two failed steps in the whole job log. - Master fails the same way: master's own push run
37274272380onaff158c7fails with the same two steps in the same file [T]. - Locally the same 2 steps fail against this branch's
public/, masteraff158c7'spublic/and341a1961'spublic/.341a1961's CI run was green earlier today, so the failure depends on data or time, not on this code [T]. - Mechanism, probed [T]:
- At 1200 px the packets table's expand column gets an inline
width: 43.45%(512 px), which leaves Details about 65 px. - The advert link wraps inside the one-line clip, so its bounding box is 70 px tall.
- The test's hit point (the box's vertical centre) then lands on a lock icon in the next row.
- Neither the expand column's width nor the test is touched by this PR [A]. This deserves its own issue.
- At 1200 px the packets table's expand column gets an inline
This PR's E2E in CI [T]:
test-issue-96-hide-control-e2e.jsran before the bug(packets): filter UX disaster — help panel overlaps table, toolbar chaotic, path chips spill rows Kpa-clawbot/CoreScope#1122 step and passed 10 of 10, including the three new round 2 steps.test-filter-ux-e2e.js,test-issue-147-packets-url-obs-e2e.jsandtest-issue-180-packets-url-modal-e2e.jsalso ran before it and passed.- Because the step is fail-fast, about 40 later E2E files did not run in CI. These include the touch-target, live, logo and channels groups; none of them is a packets-filter test.
I did not rerun the job, because the same failure reproduces on master.
Remaining
- Codex P2, the fetch cap: documented, with the follow-up issue proposed above (not filed).
- Blocked storage from the start: still breaks the page through the other unguarded reads, as before this PR. This round only guards the Feature request: Add an optional “Hide CONTROL packets” checkbox to the packet monitor #96 reads and the filter pair next to them.
- CI: red on the bug(packets): filter UX disaster — help panel overlaps table, toolbar chaotic, path chips spill rows Kpa-clawbot/CoreScope#1122 Details-clamp E2E, which master
aff158c7shares. This PR's checks will only go green once master's is fixed, or with a rerun if it turns out to be time-dependent. - Narrow-width empty-list message: invisible. This is pre-existing (TableResponsive) and suggested as a separate task.
- Not verified: Firefox and Safari, a real quota error instead of a simulated one, Back/Forward across a
hideControlchange, and the fulltest-e2e-playwright.jssuite locally (CI runs it).
Review — CS-MacBook PR#211 runde 2 — head 39d1651Dom: APPROVE med nits Evidence tags: [T] run or test output, [A] assessment or inference, [K] checked in code, diff, git or CI. Setup: Round-1 findings and Codex P2
New findings
No blocking finding. 1. Behaviour is unchanged otherwise
2. Rules
3. TestsOn the merged tree against master
All three are killed. [T] 4. The red CI check: the Kpa-clawbot#1122 Details-clamp E2E
5. Codex P2 (fetch cap): is the documentation correct?The "Known limitation: the fetch cap" section of the PR body says the following; each claim holds. [K]
Not verified
|
dborup
left a comment
There was a problem hiding this comment.
Follow-up review
Reviewed head 39d16513fae473b18bf56a943a9b68e5c34b46b8 against base aff158c7ee9f7cdea1e6c00abd63d2f73557d4c7. The head is unchanged at posting. The PR has already been merged, so this review records the verified fix and outstanding follow-up rather than a pre-merge decision.
Fixed: explicit URL override
The previous hideControl=0 finding is resolved. With the saved preference set to 1, the explicit URL override now survives both the page's URL rewrite and a reload. My independent browser check showed 499 packets before and after reload, the checkbox remained unchecked, the URL retained hideControl=0, and the saved preference remained 1.
Remaining P2: filtering after the fetch cap can still strand the list
At public/packets.js:3048, CONTROL packets are removed only after the capped fetch (1,000 on mobile; 50,000 on desktop). I reproduced this again with controlled browser API responses containing 1,001 transmissions: the newest 1,000 were CONTROL, followed by one ADVERT, all within the selected time window. The mobile page made one request with limit=1000, displayed zero packets, and issued no continuation request for the available ADVERT.
This is now explicitly documented and tracked in #242, but it is deferred, not fixed. The historical-loading acceptance criterion in #96 therefore remains unmet. The follow-up should exclude hidden types before applying the cap, or provide bounded continuation that reaches matching packets, with a regression test for this scenario. See the original finding.
Verification
- Local
test-all.sh: 218/218 frontend test files passed, including 28/28 Hide CONTROL unit checks. - PR feature browser suite: 10/10 passed against an isolated local fixture.
- Independent Chromium checks confirmed the URL fix and reproduced the remaining historical-loading limitation.
- ESLint for
public/packets.js: passed. - Current GitHub Go, Playwright, and Docker checks: successful. Full Go suites were not rerun locally for this frontend-only change.
No additional defects were identified in this re-review. No implementation changes or staging/production validation were performed. The merged change fixes the URL regression, but #242 is still needed to satisfy the original historical-loading requirement.
…242) With CONTROL excluded server-side and dropped from the live feed, the "(CONTROL packets are hidden)" note can only appear between the checkbox change and the refetch. Say so at the call site, and pin that window in the #242 browser test (held refetch over an all-CONTROL page) so the machinery stays covered rather than silently dead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Relates to #96, #242
Adds an opt-in Hide CONTROL packets checkbox to the packets page. It is unchecked by default, so nothing changes for users without a saved choice.
Plan
public/packets.js:readHideControlPref(urlValue, stored): the URL wins, then the saved choice, default off;filterHiddenControl(list, hide): drops payload type 11 only.renderTableRows' existing filter chain, right after the type filter. That one place covers the historical list and live updates: the WebSocket handler already merges live packets into the loaded list and re-renders throughrenderTableRows.hashOnly), so a direct link to a CONTROL packet still opens it.meshcore-hide-control='1'or'0'. PuthideControl=1in the URL hash viabuildPacketsQuery()/updatePacketsUrl().test-all.sh) and a Playwright E2E (in thedeploy.ymlE2E step), both red on master. Then the fix.Behaviour
/api/packetsrequest.localStoragethrows (quota, blocked), the checkbox still filters and updates the URL for this view; only the save is lost. The saved choice is read and written throughreadStoredHideControl()/saveHideControlPref(), both in try/catch.hideControl=1is in the URL while the box is checked.hideControl=0stays in the URL, so the page's own URL rewrites (load, time window, detail open/close) do not drop it, and a reload of that URL shows what was on screen.hideControl=1orhideControl=0in the URL wins over the saved choice for that view. Only the checkbox changes the saved choice.#/packets/<hash>(and?hash=) bypass the filter, like every other client-side filter, so a CONTROL packet stays reachable. The standalone#/packet/<id>page is not filtered at all.cmd/serverandcmd/ingestorare unchanged.Known limitation: the fetch cap
The filter runs on the packets already fetched. The page fetches at most
PACKET_LIMITpackets for the time window: 1,000 on mobile, 50,000 on desktop./api/packetscan only include onetype, not exclude types. A real fix needs a server-side exclusion and a new request when the box changes, which is a separate change covering both filters. The known limit is tracked in feat(packets): server-side exclusion of packet types in /api/packets for the type filter and Hide CONTROL #242.Placement
The checkbox sits in the filter bar's toggles group, next to My Nodes.
var(--text)andaccent-color: var(--accent)only.Performance (rule 0)
renderTableRows, the packets table render.filterHiddenControlreturns the same array, so there is no copy and no extra work.controlHidingEmptiedListruns only when the list is already empty. It is one O(n) pass to pick out the CONTROL packets, then the observer and expression filters on those only. Nothing changes on the non-empty path.filterHiddenControlalone, Node, linux/amd64 container, 15 runs, median [min–max]):Tests
test-issue-96-hide-control.js(unit,test-all.sh), 28 tests:'1'/'0'and unknown values;hideControl=1inbuildPacketsQuery();getItem/setItem, the checkbox change still filtering and updating the URL when the save fails,hideControl=0kept while the saved choice is on, and the empty-list note's condition.test-issue-96-hide-control-e2e.js(Playwright,deploy.yml), 10 steps against the e2e fixture's 4 CONTROL packets:/api/packetsrequest, and putshideControl=1in the URL;hideControl=0over a saved1through the load-time URL rewrite and a reload;setItemon the checkbox;#121and#147unit-test sandboxes extractbuildPacketsQuery. They declare thehideControlandsavedHideControlclosure state it reads, and the code says so.Mutants
filterHiddenControldoes nothingrenderTableRowsskips the filterhashOnlybypass removed)stored !== '0')hideControl=1missing from the URLhideControl=0not kept while saved is onsavedHideControlfollows a failed savecontrolHidden🤖 Generated with Claude Code