Style .version-badge anchor elements to use --nav-text-muted color
instead of browser-default blue. Adds hover state using --nav-text.
Works with both light and dark themes via existing CSS variables.
fixes#139
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Commit hash is now an <a> linking to GitHub commit (full hash in URL, 7-char display)
- Version tag only shown on prod (port 80/443 or no port), linked to GitHub release
- Staging (non-standard port) shows commit + engine only, no version noise
- Detect prod vs staging via location.port
- Updated tests: 16 cases covering prod/staging/links/edge cases
- Bumped cache busters
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add formatVersionBadge() that renders version, short commit hash, and
engine as a single badge in the nav stats area. Format: v2.6.0 · abc1234 [go].
Skips commit when 'unknown' or missing. Truncates commit to 7 chars.
Replaces the standalone engine badge call in updateNavStats().
8 unit tests cover all edge cases (missing fields, v-prefix dedup,
unknown commit, truncation).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Show [go] or [node] badge in the nav stats bar when /api/stats
returns an engine field. Gracefully hidden when field is absent.
- Add formatEngineBadge() to app.js (top-level, testable)
- Add .engine-badge CSS class using CSS variables
- Add 5 unit tests in test-frontend-helpers.js
- Bump cache busters
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add detail-collapsed class to split-layout initial HTML so the empty
right panel is hidden before any packet is selected. The class is
already removed when a packet row is clicked and re-added when the
close button is pressed.
Add 3 tests verifying the detail pane starts collapsed and that
open/close toggling is wired correctly.
Bump cache busters.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add #/compare page that lets users select two observers and compare
which packets each sees. Fetches last 24h of packets per observer,
computes set diff client-side using O(n) Set lookups. Shows summary
cards (both/only-A/only-B), stacked bar, type breakdown, and tabbed
detail tables. URL is shareable via ?a=ID1&b=ID2 query params.
- New file: public/compare.js (comparePacketSets + page module)
- Added compare button to observers page header
- 11 new tests for comparePacketSets (87 total frontend tests)
- Cache busters bumped
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes#131
The Nodes tab required a full page reload to see newly advertised nodes
because loadNodes() cached the node list in _allNodes and never
re-fetched it on WebSocket updates.
Changes:
- WS handler now filters for ADVERT packets only (payload_type 4 or
payloadTypeName ADVERT), instead of triggering on every packet type
- Uses 5-second debounce to avoid excessive API calls during bursts
- Resets _allNodes cache and invalidates API cache before re-fetching
- loadNodes(refreshOnly) parameter: when true, updates table rows and
counts without rebuilding the entire panel (preserves scroll position,
selected node, tabs, filters, and event listeners)
- Extracted isAdvertMessage() as testable helper with window._nodesIsAdvertMessage hook
- 13 new tests (76 total frontend helpers)
- Cache busters bumped
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes#130 — Nodes loaded from the database (API) are now dimmed with
reduced opacity when stale, matching the static map behavior, instead of
being completely removed by pruneStaleNodes(). WS-only (dynamically
added) nodes are still pruned to prevent memory leaks.
Changes:
- loadNodes() marks API-loaded nodes with _fromAPI flag
- pruneStaleNodes() dims _fromAPI nodes (fillOpacity 0.25) vs removing
- Active nodes restore full opacity when refreshed
- 3 new tests for dim/restore/WS-only behavior (63 total passing)
- Cache busters bumped
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
nodeMarkers map in live.js grew unbounded because ADVERT-injected
nodes were never removed. Added time-based pruning using health
thresholds from roles.js (24h for companions/sensors, 72h for
repeaters/rooms). Prune interval runs every 60 seconds.
- Track _liveSeen timestamp on each nodeData entry
- Update timestamp on every ADVERT (new or existing node)
- pruneStaleNodes() removes nodes exceeding silentMs threshold
- 5 new tests verifying pruning logic and threshold behavior
- Cache busters bumped
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Issues fixed:
- #127: Firefox copy URL - shared copyToClipboard() with execCommand fallback
- #125: Dismiss packet detail pane - close button with keyboard support
- #124: Customize window scrollbar - flex layout fix for overflow
- #122: Last Activity stale times - use last_heard || last_seen
Test improvements:
- E2E perf: replace 19 networkidle waits, cut navigations 14->7, remove 11 sleeps
- 8 new unit tests for copyToClipboard helper (47->55 in test-frontend-helpers)
- 1 new E2E test for packet pane dismiss
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
6 tests covering string, number, null, negative, and integer
values through the Number(x).toFixed() pattern used across
observer-detail, home, traces, and live pages.