Files
meshcore-analyzer/public/index.html
you 14ff1821d6 fix: hash-based packet deduplication in Live feed
Root cause: addFeedItem had no dedup logic — each WS message created
a new feed entry regardless of hash. Dedup only worked when the
'Realistic propagation' toggle was ON (which buffers by hash before
calling animateRealisticPropagation). Default mode called animatePacket
directly for every observation, producing duplicate feed entries.

Fix: Added feedHashMap (hash -> {element, count, pkt, addedAt}) that
tracks recent feed items by packet hash. When a packet with a known
hash arrives within 30s, the existing feed item is updated in-place:
- Observation count badge incremented
- Item flashed and moved to top of feed
- No duplicate DOM element created

Also adds data-hash attribute to feed items for testability.

Tests: 5 new Playwright tests in test-live-dedup.js covering:
- Same hash different observers → single entry
- Different hashes → separate entries
- 5 rapid sequential duplicates → single entry with count 5
- Same hash same observer → still deduplicates
- Packets without hash → not deduplicated
2026-03-24 19:35:28 +00:00

108 lines
6.3 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="icon" href="favicon.ico" type="image/x-icon">
<link rel="icon" href="favicon.svg" type="image/svg+xml">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>MeshCore Analyzer</title>
<!-- Open Graph / Discord embed -->
<meta property="og:title" content="MeshCore Analyzer">
<meta property="og:description" content="Real-time MeshCore LoRa mesh network analyzer — live packet visualization, node tracking, channel decryption, route analysis, and deep mesh analytics.">
<meta property="og:image" content="https://raw.githubusercontent.com/Kpa-clawbot/meshcore-analyzer/master/public/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:url" content="https://analyzer.00id.net">
<meta property="og:type" content="website">
<meta name="theme-color" content="#0a0a0a">
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="MeshCore Analyzer">
<meta name="twitter:description" content="Real-time MeshCore LoRa mesh network analyzer — live packet visualization, node tracking, channel decryption, and route analysis.">
<meta name="twitter:image" content="https://raw.githubusercontent.com/Kpa-clawbot/meshcore-analyzer/master/public/og-image.png">
<link rel="stylesheet" href="style.css?v=1774380547">
<link rel="stylesheet" href="home.css?v=1774380547">
<link rel="stylesheet" href="live.css?v=1774380547">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin="anonymous">
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin="anonymous"></script>
<script src="https://unpkg.com/leaflet.heat@0.2.0/dist/leaflet-heat.js"></script>
<script src="https://unpkg.com/chart.js@4/dist/chart.umd.min.js"></script>
</head>
<body>
<a class="skip-link" href="#app">Skip to content</a>
<nav class="top-nav" role="navigation" aria-label="Main navigation">
<div class="nav-left">
<a href="#/" class="nav-brand">
<span class="brand-icon">🍄</span>
<span class="brand-text">MeshCore Analyzer</span>
<span class="live-dot" id="liveDot" title="WebSocket connected" aria-label="WebSocket connected"></span>
</a>
<div class="nav-links">
<a href="#/home" class="nav-link" data-route="home">Home</a>
<a href="#/packets" class="nav-link" data-route="packets">Packets</a>
<a href="#/map" class="nav-link" data-route="map">Map</a>
<a href="#/live" class="nav-link" data-route="live">🔴 Live</a>
<a href="#/channels" class="nav-link" data-route="channels">Channels</a>
<a href="#/nodes" class="nav-link" data-route="nodes">Nodes</a>
<a href="#/traces" class="nav-link" data-route="traces">Traces</a>
<a href="#/observers" class="nav-link" data-route="observers">Observers</a>
<a href="#/analytics" class="nav-link" data-route="analytics">Analytics</a>
<a href="#/perf" class="nav-link" data-route="perf">⚡ Perf</a>
<a href="#/audio-lab" class="nav-link" data-route="audio-lab">🎵 Lab</a>
</div>
</div>
<div class="nav-right">
<div class="nav-stats" id="navStats" title="Live stats"></div>
<div class="nav-fav-wrap">
<button class="nav-btn" id="favToggle" title="Favorites"></button>
<div class="nav-fav-dropdown" id="favDropdown"></div>
</div>
<button class="nav-btn" id="searchToggle" title="Search (Ctrl+K)">🔍</button>
<button class="nav-btn" id="customizeToggle" title="Customize theme & branding">🎨</button>
<button class="nav-btn" id="darkModeToggle" title="Toggle dark mode">☀️</button>
<button class="nav-btn hamburger" id="hamburger" title="Menu" aria-label="Toggle navigation menu"></button>
</div>
</nav>
<!-- Search overlay -->
<div id="searchOverlay" class="search-overlay hidden" aria-label="Search packets, nodes, channels">
<div class="search-box">
<input type="text" id="searchInput" placeholder="Search packets, nodes, channels…" autofocus>
<div id="searchResults" class="search-results"></div>
</div>
</div>
<main id="app" role="main"></main>
<script src="vendor/qrcode.js"></script>
<script src="roles.js?v=1774380547"></script>
<script src="customize.js?v=1774380547" onerror="console.error('Failed to load:', this.src)"></script>
<script src="region-filter.js?v=1774380547"></script>
<script src="hop-resolver.js?v=1774380547"></script>
<script src="hop-display.js?v=1774380547"></script>
<script src="app.js?v=1774380547"></script>
<script src="home.js?v=1774380547"></script>
<script src="packet-filter.js?v=1774380547"></script>
<script src="packets.js?v=1774380547"></script>
<script src="map.js?v=1774380547" onerror="console.error('Failed to load:', this.src)"></script>
<script src="channels.js?v=1774380547" onerror="console.error('Failed to load:', this.src)"></script>
<script src="nodes.js?v=1774380547" onerror="console.error('Failed to load:', this.src)"></script>
<script src="traces.js?v=1774380547" onerror="console.error('Failed to load:', this.src)"></script>
<script src="analytics.js?v=1774380547" onerror="console.error('Failed to load:', this.src)"></script>
<script src="audio.js?v=1774380547" onerror="console.error('Failed to load:', this.src)"></script>
<script src="audio-v1-constellation.js?v=1774380547" onerror="console.error('Failed to load:', this.src)"></script>
<script src="audio-lab.js?v=1774380547" onerror="console.error('Failed to load:', this.src)"></script>
<script src="live.js?v=1774380547" onerror="console.error('Failed to load:', this.src)"></script>
<script src="observers.js?v=1774380547" onerror="console.error('Failed to load:', this.src)"></script>
<script src="observer-detail.js?v=1774380547" onerror="console.error('Failed to load:', this.src)"></script>
<script src="node-analytics.js?v=1774380547" onerror="console.error('Failed to load:', this.src)"></script>
<script src="perf.js?v=1774380547" onerror="console.error('Failed to load:', this.src)"></script>
</body>
</html>