mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-05-11 19:54:42 +00:00
64745f89b1
## Summary Implements the customizer v2 per the [approved spec](docs/specs/customizer-rework.md), replacing the v1 customizer's scattered state management with a clean event-driven architecture. Resolves #502. ## What Changed ### New: `public/customize-v2.js` Complete rewrite of the customizer as a self-contained IIFE with: - **Single localStorage key** (`cs-theme-overrides`) replacing 7 scattered keys - **Three state layers:** server defaults (immutable) → user overrides (delta) → effective config (computed) - **Full data flow pipeline:** `write → read-back → merge → atomic SITE_CONFIG assign → apply CSS → dispatch theme-changed` - **Color picker optimistic CSS** (Decision #12): `input` events update CSS directly for responsiveness; `change` events trigger the full pipeline - **Override indicator dots** (●) on each field — click to reset individual values - **Section-level override count badges** on tabs - **Browser-local banner** in panel header: "These settings are saved in your browser only" - **Auto-save status indicator** in footer: "All changes saved" / "Saving..." / "⚠️ Storage full" - **Export/Import** with full shape validation (`validateShape()`) - **Presets** flow through the standard pipeline (`writeOverrides(presetData) → pipeline`) - **One-time migration** from 7 legacy localStorage keys (exact field mapping per spec) - **Validation** on all writes: color format, opacity range, timestamp enum values - **QuotaExceededError handling** with visible user warning ### Modified: `public/app.js` Replaced ~80 lines of inline theme application code with a 15-line `_customizerV2.init(cfg)` call. The customizer v2 handles all merging, CSS application, and global state updates. ### Modified: `public/index.html` Swapped `customize.js` → `customize-v2.js` script tag. ### Added: `docs/specs/customizer-rework.md` The full approved spec, included in the repo for reference. ## Migration On first page load: 1. Checks if `cs-theme-overrides` already exists → skip if yes 2. Reads all 7 legacy keys (`meshcore-user-theme`, `meshcore-timestamp-*`, `meshcore-heatmap-opacity`, `meshcore-live-heatmap-opacity`) 3. Maps them to the new delta format per the spec's field-by-field mapping 4. Writes to `cs-theme-overrides`, removes all legacy keys 5. Continues with normal init Users with existing customizations will see them preserved automatically. ## Dark/Light Mode - `theme` section stores light mode overrides, `themeDark` stores dark mode overrides - `meshcore-theme` localStorage key remains **separate** (view preference, not customization) - Switching modes re-runs the full pipeline with the correct section ## Testing - All existing tests pass (`test-packet-filter.js`, `test-aging.js`, `test-frontend-helpers.js`) - Old `customize.js` is NOT modified — left in place for reference but no longer loaded ## Not in Scope (per spec) - Undo/redo stack - Cross-tab synchronization - Server-side admin import endpoint - Map config / geo-filter overrides --------- Co-authored-by: you <you@example.com>
116 lines
6.9 KiB
HTML
116 lines
6.9 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>CoreScope</title>
|
|
|
|
<!-- Open Graph / Discord embed -->
|
|
<meta property="og:title" content="CoreScope">
|
|
<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/corescope/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="CoreScope">
|
|
<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/corescope/master/public/og-image.png">
|
|
<link rel="stylesheet" href="style.css?v=__BUST__">
|
|
<link rel="stylesheet" href="home.css?v=__BUST__">
|
|
<link rel="stylesheet" href="live.css?v=__BUST__">
|
|
<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">CoreScope</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" data-priority="high">Home</a>
|
|
<a href="#/packets" class="nav-link" data-route="packets" data-priority="high">Packets</a>
|
|
<a href="#/map" class="nav-link" data-route="map" data-priority="high">Map</a>
|
|
<a href="#/live" class="nav-link" data-route="live" data-priority="high">🔴 Live</a>
|
|
<a href="#/channels" class="nav-link" data-route="channels">Channels</a>
|
|
<a href="#/nodes" class="nav-link" data-route="nodes" data-priority="high">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 class="nav-more-wrap">
|
|
<button class="nav-btn nav-more-btn" id="navMoreBtn" aria-haspopup="true" aria-expanded="false" aria-controls="navMoreMenu" title="More pages">More ▾</button>
|
|
<div class="nav-more-menu" id="navMoreMenu" role="menu"></div>
|
|
</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" role="listbox"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<main id="app" role="main"></main>
|
|
|
|
<script src="vendor/qrcode.js"></script>
|
|
<script src="roles.js?v=__BUST__"></script>
|
|
<script src="customize-v2.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="region-filter.js?v=__BUST__"></script>
|
|
<script src="hop-resolver.js?v=__BUST__"></script>
|
|
<script src="hop-display.js?v=__BUST__"></script>
|
|
<script src="app.js?v=__BUST__"></script>
|
|
<script src="home.js?v=__BUST__"></script>
|
|
<script src="packet-filter.js?v=__BUST__"></script>
|
|
<script src="packet-helpers.js?v=__BUST__"></script>
|
|
<script src="packets.js?v=__BUST__"></script>
|
|
<script src="geo-filter-overlay.js?v=__BUST__"></script>
|
|
<script src="map.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="channels.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="nodes.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="traces.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="analytics.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="audio.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="audio-v1-constellation.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="audio-v2-constellation.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="audio-lab.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="live.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="observers.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="observer-detail.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="compare.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="node-analytics.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
<script src="perf.js?v=__BUST__" onerror="console.error('Failed to load:', this.src)"></script>
|
|
</body>
|
|
</html>
|