@@ -1065,6 +1072,57 @@
this.textContent = bar.classList.contains('filters-expanded') ? 'Filters ▴' : 'Filters ▾';
});
+ // --- Clear filters button ---
+ const clearBtn = document.getElementById('clearFiltersBtn');
+ if (clearBtn) clearBtn.addEventListener('click', function() {
+ // Reset filters object
+ filters.hash = undefined;
+ filters.node = undefined;
+ filters.nodeName = undefined;
+ filters.observer = undefined;
+ filters.channel = undefined;
+ filters.type = undefined;
+ filters._filterExpr = undefined;
+ filters._packetFilter = null;
+ filters.myNodes = false;
+ _observerFilterSet = null;
+
+ // Clear localStorage filter entries
+ localStorage.removeItem('meshcore-observer-filter');
+ localStorage.removeItem('meshcore-type-filter');
+
+ // Reset DOM inputs
+ document.getElementById('fHash').value = '';
+ document.getElementById('fNode').value = '';
+ var pfInput = document.getElementById('packetFilterInput');
+ if (pfInput) { pfInput.value = ''; pfInput.classList.remove('filter-active', 'filter-error'); }
+ var pfError = document.getElementById('packetFilterError');
+ if (pfError) pfError.style.display = 'none';
+ var pfCount = document.getElementById('packetFilterCount');
+ if (pfCount) pfCount.style.display = 'none';
+ document.getElementById('fChannel').value = '';
+ document.getElementById('fMyNodes').classList.remove('active');
+
+ // Reset observer multi-select
+ var obMenu = document.getElementById('observerMenu');
+ if (obMenu) obMenu.querySelectorAll('input[type=checkbox]').forEach(function(cb) { cb.checked = false; });
+ document.getElementById('observerTrigger').textContent = 'All Observers ▾';
+
+ // Reset type multi-select
+ var typeMenu = document.getElementById('typeMenu');
+ if (typeMenu) typeMenu.querySelectorAll('input[type=checkbox]').forEach(function(cb) { cb.checked = false; });
+ document.getElementById('typeTrigger').textContent = 'All Types ▾';
+
+ // Reset region filter
+ RegionFilter.setSelected([]);
+
+ // Update URL and reload
+ updatePacketsUrl();
+ loadPackets();
+ });
+ // Show clear button if page loaded with active filters (e.g. from URL params)
+ updatePacketsUrl();
+
// Filter event listeners
document.getElementById('fHash').value = filters.hash || '';
document.getElementById('fHash').addEventListener('input', debounce((e) => { filters.hash = e.target.value || undefined; updatePacketsUrl(); loadPackets(); }, 300));
diff --git a/test-clear-filters.js b/test-clear-filters.js
new file mode 100644
index 00000000..906f3562
--- /dev/null
+++ b/test-clear-filters.js
@@ -0,0 +1,42 @@
+/* test-clear-filters.js — unit test for clear-filters button (#964) */
+'use strict';
+
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+
+console.log('--- test-clear-filters.js ---');
+
+const src = fs.readFileSync(path.join(__dirname, 'public', 'packets.js'), 'utf-8');
+
+// Test 1: button HTML present
+assert(src.includes('clearFiltersBtn'), 'clearFiltersBtn ID should exist');
+assert(src.includes('✕ Clear'), 'Clear button label should exist');
+
+// Test 2: clear handler resets all filter keys
+for (const k of ['hash', 'node', 'observer', 'channel', 'type', '_filterExpr']) {
+ assert(src.includes(`filters.${k} = undefined`), `should reset filters.${k}`);
+}
+assert(src.includes('filters._packetFilter = null'), 'should reset _packetFilter');
+assert(src.includes('filters.myNodes = false'), 'should reset myNodes');
+
+// Test 3: clears localStorage
+assert(src.includes("localStorage.removeItem('meshcore-observer-filter')"), 'clear observer localStorage');
+assert(src.includes("localStorage.removeItem('meshcore-type-filter')"), 'clear type localStorage');
+
+// Test 4: resets RegionFilter
+assert(src.includes('RegionFilter.setSelected([])'), 'reset RegionFilter');
+
+// Test 5: visibility toggle in updatePacketsUrl
+assert(src.includes("cb.style.display = active ?"), 'toggle clear btn visibility');
+
+// Test 6: resets DOM inputs
+assert(src.includes("getElementById('fHash').value = ''"), 'clear hash input');
+assert(src.includes("getElementById('fNode').value = ''"), 'clear node input');
+assert(src.includes("getElementById('fChannel').value = ''"), 'clear channel select');
+
+// Test 7: resets multi-select checkboxes
+assert(src.includes('observerMenu'), 'clear observer checkboxes');
+assert(src.includes('typeMenu'), 'clear type checkboxes');
+
+console.log('All 7 tests passed ✅');