Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ jobs:
BASE_URL=http://localhost:13581 node test-slideover-1056-e2e.js 2>&1 | tee -a e2e-output.txt
BASE_URL=http://localhost:13581 node test-issue-1692-packets-init-parallel-e2e.js 2>&1 | tee -a e2e-output.txt
BASE_URL=http://localhost:13581 node test-issue-147-packets-url-obs-e2e.js 2>&1 | tee -a e2e-output.txt
BASE_URL=http://localhost:13581 node test-issue-96-hide-control-e2e.js 2>&1 | tee -a e2e-output.txt
BASE_URL=http://localhost:13581 node test-issue-180-packets-url-modal-e2e.js 2>&1 | tee -a e2e-output.txt
BASE_URL=http://localhost:13581 node test-slideover-1168-munger-e2e.js 2>&1 | tee -a e2e-output.txt
BASE_URL=http://localhost:13581 node test-logo-pulse-1173-e2e.js 2>&1 | tee -a e2e-output.txt
Expand Down
81 changes: 78 additions & 3 deletions public/packets.js
Original file line number Diff line number Diff line change
Expand Up @@ -715,8 +715,58 @@
function _hashStripeStyle(hash) { return _isColorByHash() && hash && window.HashColor ? 'border-left:4px solid ' + HashColor.hashToHsl(hash, _currentTheme()) + ';' : ''; }
let groupByHash = true;
let filters = {};
{ const o = localStorage.getItem('meshcore-observer-filter'); if (o) filters.observer = o;
const t = localStorage.getItem('meshcore-type-filter'); if (t) filters.type = t; }
// Storage can throw (blocked, private mode); the filters then start empty.
try {
const o = localStorage.getItem('meshcore-observer-filter'); if (o) filters.observer = o;
const t = localStorage.getItem('meshcore-type-filter'); if (t) filters.type = t;
} catch (e) { /* storage unavailable */ }
// #96 — opt-in "Hide CONTROL packets" display filter, unchecked by default.
// Display only: CONTROL packets are still fetched and kept (also live ones),
// so unchecking shows them again without a reload, and a pinned hash (a
// direct link to one packet) bypasses it like every other client filter.
// Saved as '1' or '0' so an explicit unchecked choice survives a reload.
const PAYLOAD_TYPE_CONTROL = 11;
const HIDE_CONTROL_KEY = 'meshcore-hide-control';
// The URL value ('1' or '0') wins over the saved choice; default off.
function readHideControlPref(urlValue, stored) {
if (urlValue === '1') return true;
if (urlValue === '0') return false;
return stored === '1';
}
// The saved choice, or null when storage throws (blocked, private mode).
function readStoredHideControl() {
try { return localStorage.getItem(HIDE_CONTROL_KEY); } catch (e) { return null; }
}
// Saves the choice; false when storage throws (quota, blocked). Best effort:
// the checkbox still works for this view.
function saveHideControlPref(hide) {
try { localStorage.setItem(HIDE_CONTROL_KEY, hide ? '1' : '0'); return true; } catch (e) { return false; }
}
// One pass, order kept; the same array when off (no copy on the default path).
function filterHiddenControl(list, hide) {
return hide ? list.filter(p => p.payload_type !== PAYLOAD_TYPE_CONTROL) : list;
}
// Whether hiding CONTROL is what emptied the list: some CONTROL packet from
// before the CONTROL pass gets through the filters after it (laterFilters).
// Called only for an empty list, and runs the CONTROL packets only.
function controlHidingEmptiedList(beforeHide, laterFilters) {
const hidden = beforeHide.filter(p => p.payload_type === PAYLOAD_TYPE_CONTROL);
return hidden.length > 0 && laterFilters(hidden).length > 0;
}
// The checkbox's change: re-filters the loaded packets, no new request. The
// save comes first but cannot stop the filter or the URL update (#211).
function setHideControl(hide) {
hideControl = hide;
if (saveHideControlPref(hide)) savedHideControl = hide;
updatePacketsUrl();
renderTableRows();
}
// hideControl is what this view shows; savedHideControl is the saved choice
// as far as this page knows (it changes only when a save succeeds).
// buildPacketsQuery reads both, so a test sandbox that extracts it must
// declare both (test-issue-121/147-…, test-issue-96-hide-control.js).
let savedHideControl = readHideControlPref(null, readStoredHideControl());
let hideControl = savedHideControl;
let wsHandler = null;
let packetsPaused = false;
let pauseBuffer = [];
Expand Down Expand Up @@ -779,6 +829,11 @@
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));
// #96: hideControl=1 while on. Off is the default and is omitted, except
// while the saved choice is on: an explicit hideControl=0 then stays, so a
// reload of this URL still shows CONTROL (#211).
if (hideControl) parts.push('hideControl=1');
else if (savedHideControl) parts.push('hideControl=0');
// Sort state (#749) — encode as 'col[:asc]'; default 'time:desc' is omitted.
if (_packetSortColumn) {
var sortDefault = _packetSortColumn === 'time' && _packetSortDirection === 'desc';
Expand Down Expand Up @@ -1199,6 +1254,11 @@
if (_urlChannel) filters.channel = _urlChannel;
var _urlFilterExpr = _initUrlParams.get('filter');
if (_urlFilterExpr) filters._filterExpr = _urlFilterExpr;
// #96 — ?hideControl=1|0 wins over the saved choice for this view; only
// the checkbox changes the saved choice.
var _storedHideControl = readStoredHideControl();
savedHideControl = readHideControlPref(null, _storedHideControl);
hideControl = readHideControlPref(_initUrlParams.get('hideControl'), _storedHideControl);
// #749 — restore sort state from URL (overrides localStorage).
var _urlSort = _initUrlParams.get('sort');
if (_urlSort && window.URLState) {
Expand Down Expand Up @@ -1671,6 +1731,7 @@
<div class="filter-group filter-group-toggles">
<button class="btn ${groupByHash ? 'active' : ''}" id="fGroup" title="Collapse duplicate observations of the same packet into expandable groups">Group by Hash</button>
<button class="btn" id="fMyNodes" title="Show only packets from your favorited/claimed nodes"><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor-sprite.svg#ph-star-fill"/></svg> My Nodes</button>
<label class="filter-checkbox" title="Hide CONTROL packets (node discovery) from the list and from live updates. Nothing is discarded; direct links to a packet still open it."><input type="checkbox" id="fHideControl"${hideControl ? ' checked' : ''}> Hide CONTROL packets</label>
<select id="fTimeWindow" class="filter-select" aria-label="Time window filter">
<option value="15">Last 15 min</option>
<option value="30">Last 30 min</option>
Expand Down Expand Up @@ -2065,6 +2126,8 @@
this.classList.toggle('active', filters.myNodes);
loadPackets();
});
// #96 — re-filters the loaded packets; no new request.
document.getElementById('fHideControl').addEventListener('change', function () { setHideControl(this.checked); });

// Observation sort dropdown
const obsSortSel = document.getElementById('fObsSort');
Expand Down Expand Up @@ -2980,6 +3043,10 @@
const types = filters.type.split(',').map(Number);
displayPackets = displayPackets.filter(p => types.includes(p.payload_type));
}
// #96 — Hide CONTROL packets (display only; a pinned hash bypasses it).
const beforeHideControl = displayPackets;
if (!hashOnly) displayPackets = filterHiddenControl(displayPackets, hideControl);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

const controlHidden = displayPackets.length < beforeHideControl.length;
displayPackets = applyObserverFilter(displayPackets, filters, groupByHash, hashOnly);

// Packet Filter Language
Expand Down Expand Up @@ -3007,7 +3074,13 @@
_lastVisibleEnd = -1;
detachVScrollListener();
const colCount = _getColCount();
tbody.innerHTML = '<tr><td colspan="' + colCount + '" class="text-center text-muted" style="padding:24px">' + (filters.myNodes ? 'No packets from your claimed/favorited nodes' : 'No packets found') + '</td></tr>';
// #211: name CONTROL only when hiding it emptied the list, i.e. a CONTROL
// packet would get through the filters below the CONTROL pass.
const emptiedByControl = controlHidden && controlHidingEmptiedList(beforeHideControl, (list) => {
list = applyObserverFilter(list, filters, groupByHash, hashOnly);
return filters._packetFilter ? list.filter(filters._packetFilter) : list;
});
tbody.innerHTML = '<tr><td colspan="' + colCount + '" class="text-center text-muted" style="padding:24px">' + (filters.myNodes ? 'No packets from your claimed/favorited nodes' : emptiedByControl ? 'No packets found (CONTROL packets are hidden)' : 'No packets found') + '</td></tr>';
// Restore scroll position after DOM rebuild (#431)
if (scrollContainer) scrollContainer.scrollTop = savedScrollTop;
return;
Expand Down Expand Up @@ -4219,6 +4292,8 @@
_calcVisibleRange,
buildPacketsParams,
applyObserverFilter,
readHideControlPref,
filterHiddenControl,
buildReplayPackets,
renderTableRows,
_setPackets: function(p) { packets = p; },
Expand Down
6 changes: 6 additions & 0 deletions public/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -1058,6 +1058,12 @@ img.brand-logo {
}
.sort-help:hover .sort-help-tip { display: block; }
.filter-bar .btn:hover { background: var(--row-hover); }
/* #96 — "Hide CONTROL packets" checkbox in the packets filter bar. */
.filter-bar .filter-checkbox { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: var(--text); white-space: nowrap; cursor: pointer; }
.filter-bar .filter-checkbox input[type="checkbox"] { /* not the text-input box above */
width: 16px; height: 16px; min-width: 0; min-height: 0; padding: 0; margin: 0; cursor: pointer; accent-color: var(--accent);
}
@media (pointer: coarse) { .filter-bar .filter-checkbox { min-height: 44px; } } /* the label is the touch target */
.filter-bar .btn.active { background: var(--accent-strong); color: var(--text-on-accent); border-color: var(--accent-strong); }
.filter-bar .col-toggle-btn { height: 34px; min-height: 34px; }

Expand Down
1 change: 1 addition & 0 deletions test-all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ run test-packet-filter-ux.js
run test-issue-121-clear-filters-selection.js
run test-clear-filters.js
run test-issue-147-packets-url-detail-params.js
run test-issue-96-hide-control.js
run test-issue-180-packets-detail-close.js
run test-aging.js
run test-issue-1065-gesture-hints-gates.js
Expand Down
2 changes: 2 additions & 0 deletions test-issue-121-clear-filters-selection.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ function mount(state, filters) {
_rebuildObserverMenu: null,
_packetSortColumn: null,
_packetSortDirection: 'desc',
hideControl: false, // #96 closure state read by buildPacketsQuery
savedHideControl: false, // #211 likewise
};
sandbox.observerMap = new Map(sandbox.observers.map((o) => [o.id, o]));
vm.createContext(sandbox);
Expand Down
1 change: 1 addition & 0 deletions test-issue-147-packets-url-detail-params.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ function makeSandbox(startHash, opts) {
vm.runInContext(
'var filters = {}; var savedTimeWindowMin = 15; var DEFAULT_TIME_WINDOW = 15;\n' +
"var _packetSortColumn = null; var _packetSortDirection = 'desc';\n" +
"var hideControl = false; var savedHideControl = false;\n" + // #96/#211 closure state read by buildPacketsQuery
'var _observerFilterSet = null; var selectedObservers = new Set(); var selectedTypes = new Set();\n' +
'function buildObserverMenu() {} function updateObsTrigger() {} function buildTypeMenu() {}\n' +
'function updateTypeTrigger() {} function loadPackets() {}\n' +
Expand Down
Loading
Loading