Sections: inconsistentHashSection, hashMatrixSection, collisionRiskSection
Use ?tab=collisions§ion=inconsistentHashSection to jump directly.
Scrolls after tab render completes (400ms delay for async content).
Added ids: node-stats, node-observers, fullPathsSection, node-packets.
Use ?section=<id> to scroll to any section on load.
e.g. #/nodes/<pubkey>?section=node-packets
Variable hash size badge and analytics links updated to use ?section=.
A node going 1B→2B and staying 2B is a firmware upgrade, not a bug.
Only flag as inconsistent when hash sizes flip-flop (2+ transitions in
the chronological advert sequence). Single transition = clean upgrade.
- Removed yellow text and redundant Status column
- Sizes Seen now uses colored badges (orange 1B, pale green 2B, bright green 3B)
- Row striping, card border/radius, accent-colored node links
- Current hash in mono with muted byte count
- Renamed 'Hash Collisions' tab to 'Hash Issues'
- New section at top: 'Inconsistent Hash Sizes' table listing all nodes
that have sent adverts with varying hash sizes
- Each node links to its detail page with ?highlight=hashsize for
per-advert hash size breakdown
- Shows current hash prefix, all sizes seen, and affected count
- Green checkmark when no inconsistencies detected
- Existing collision grid and risk table unchanged below
- Badge is now a link to the detail page with ?highlight=hashsize
- Detail page auto-scrolls to Recent Packets section
- Each advert shows its hash size badge (yellow if different from current)
- Detail page shows always-visible explanation banner (not hidden)
- Side pane badge links to detail page too
Badge shows cursor:help and clicking toggles a yellow-bordered info box
explaining the issue and suggesting firmware update. Stats row just shows
'⚠️ varies' with tooltip. Much less jarring than a dead yellow badge.
White semi-transparent square behind QR so black modules pop.
White rects in SVG already set to transparent by JS.
Same white backing in dark mode too (QR needs light bg to scan).
Tracks all hash_size values seen per node. If a node has sent adverts
with different hash sizes, flags it as hash_size_inconsistent with a
yellow ⚠️ badge on both side pane and detail page. Tooltip mentions
likely firmware bug (pre-1.14.1). Stats row shows all sizes seen.
Some nodes emit adverts with varying path byte values (e.g. 0x00 and 0x40).
Taking the first/newest was unreliable. Now takes the maximum hash_size
seen across all adverts for each node.
CSS fill-opacity selectors weren't matching the QR library's output.
Now JS directly sets white rects to transparent after SVG generation.
Overlay at 70% opacity so it doesn't fight the map for attention.
Removed 'Scan with MeshCore app' label from overlay version.
- QR globally reduced (140px → 100px, overlay 64px)
- Side pane: name/badges first, then map with QR overlaid in bottom-right corner
- Removed standalone QR section from side pane — saves vertical space
- Public key shown inline below map instead of separate section
- No-location nodes still get standalone centered QR
- Full detail page QR wrap narrower (max 160px)
- Map and QR code now sit side-by-side (flex: 3/1) instead of stacked
- QR section shows truncated public key below the code
- Stats section uses a compact 2-column table with alternating row stripes
- Name/badges/actions section tightened up with less vertical spacing
- Mobile (<768px): stacks map and QR vertically
- No-location nodes: QR centered at max 240px width
- Side pane: replace '📋 URL' button with '🔍 Details' link to full detail page
- Side pane: add hash size badge next to role badge
- Full detail page: add hash size badge next to role badge
- Full detail page: add Hash Size row in stats section
- Handle null hash_size gracefully
Customizer now syncs state.home and state.branding to window.SITE_CONFIG
on every change, then dispatches hashchange to trigger page re-render.
Previously only saved to localStorage — home.js reads SITE_CONFIG.
Two bugs:
1. fetch was cached by browser — added cache: 'no-store'
2. navigate() ran before config fetch completed — moved routing
into .finally() so SITE_CONFIG is populated before any page
renders. Home page was reading SITE_CONFIG before fetch resolved,
getting undefined, falling back to hardcoded defaults.
Adds a Theme Presets section at the top of the Theme Colors tab with 8
WCAG AA-verified preset themes:
- Default: Original MeshCore blue (#4a9eff)
- Ocean: Deep blues and teals, professional
- Forest: Greens and earth tones, natural
- Sunset: Warm oranges, ambers, and reds
- Monochrome: Pure grays, no color accent, minimal
- High Contrast: WCAG AAA (7:1), bold colors, accessibility-first
- Midnight: Deep purples and indigos, elegant
- Ember: Dark warm red/orange accents, cyberpunk feel
Each theme has both light and dark variants with all 20 color keys.
High Contrast theme includes custom nodeColors and typeColors for
maximum distinguishability.
Active preset is auto-detected and highlighted. Users can select a
preset then tweak individual colors (becomes Custom).
- Search order: app dir first (next to config.json), then data/ dir
- Startup log: '[theme] Loaded from ...' or 'No theme.json found. Place it next to config.json'
- README updated: 'put it next to config.json' instead of confusing data/ path
- Dark mode: now merges theme + themeDark and applies correctly
- Added missing CSS var mappings: navText, navTextMuted, background, sectionBg, font, mono
- Fixed 'background' key mapping (was 'surface0', never matched)
- Derived vars (content-bg, card-bg) set from server config
- Type colors from server config now applied to TYPE_COLORS global
- syncBadgeColors called after type color override
- Both loadConfigFile() and loadThemeFile() check data/ dir first, then app dir
- Theme endpoint re-reads both files on every request — edit the file, refresh the page
- No container restart, no symlinks, no extra mounts needed
- Just edit /app/data/theme.json (or config.json) and it's live
- entrypoint copies example config to /app/data/config.json on first run
- symlinks /app/config.json → /app/data/config.json so app code unchanged
- theme.json also symlinked from /app/data/ if present
- config persists across container rebuilds without extra bind mounts
- updated README with new config/theme instructions
- Import File button opens file picker for .json theme files
- Merges imported theme into current state, applies live preview
- Also syncs ROLE_COLORS/TYPE_COLORS globals on import
- Moved Copy/Download buttons out of collapsed details
- Raw JSON textarea now editable (was readonly)
- Server reads from theme.json (separate from config.json), hot-loads on every request
- POST /api/config/theme writes theme.json directly — no manual file editing
- GET /api/config/theme now merges: defaults → config.json → theme.json
- Also returns themeDark and typeColors (were missing from API)
- Customizer: replaced 'merge into config.json' instructions with Save/Load Server buttons
- JSON export moved to collapsible details section
- theme.json added to .gitignore (instance-specific)
CSS changes:
- style.css: .live-dot.connected, .hop-global-fallback, .perf-slow, .perf-warn
now use var(--status-green/red/yellow) instead of hardcoded hex
- live.css: live recording dot uses var(--status-red), LCD text uses var(--status-green)
JS changes (analytics.js):
- Added cssVar/statusGreen/statusYellow/statusRed/accentColor/snrColor helpers
that read from CSS custom properties with hardcoded fallbacks
- Replaced ~20 hardcoded status colors in: SNR histograms, quality zones,
zone borders/patterns, SNR timeline, daily SNR bars, collision badges
(Local/Regional/Distant), distance classification, subpath map markers,
hop distance distribution, network status cards, self-loop bars
JS changes (live.js):
- Added statusGreen helper for LCD clock color
- Legend dots now read from TYPE_COLORS global instead of hardcoded hex
All colors now respond to theme customization via the customize panel.
app.js was fetching /api/config/theme and overwriting ROLE_COLORS,
ROLE_STYLE, branding AFTER customize.js had already restored them
from localStorage. Now skips server overrides for any section
where user has local preferences.
Also added branding restore from localStorage on DOMContentLoaded.
Added --nav-text and --nav-text-muted CSS variables. All nav
selectors (.top-nav, .nav-brand, .nav-link, .nav-btn, .nav-stats)
use these instead of --text/--text-muted. Nav Text is in Basic
settings. Nav Muted Text in Advanced.
This is separate from page text because nav sits on a dark
background — page text color would be unreadable on the nav.
customize.js was loading last — saved colors restored AFTER the
map already created markers with default colors. Now loads right
after roles.js, before app.js. ROLE_STYLE colors are updated
before any page renders.
- Move --status-green/yellow/red from home.css to style.css :root (light+dark)
- Replace hardcoded status colors in style.css (.tl-snr, .health-dot, .byop-err,
.badge-hash-*, .fav-star.on, .spark-fill) with CSS variable references
- Replace hardcoded colors in live.css (VCR mode, stat pills, fdc-link, playhead)
- Replace --primary/--bg-secondary/--text-primary/--text-secondary dead vars with
canonical --accent/--input-bg/--text/--text-muted in style.css, map.js, live.js,
traces.js, packets.js
- Fix nodes.js legend colors to use ROLE_COLORS globals instead of hardcoded hex
- Replace hardcoded hex in home.js (SNR), perf.js (indicators), map.js (accuracy
circles) with CSS variable references via getComputedStyle or var()
- Add --detail-bg to customizer (THEME_CSS_MAP, DEFAULTS, ADVANCED_KEYS, labels)
- Move font/mono out of ADVANCED_KEYS into separate Fonts section in customizer
- Remove debug console.log lines from customize.js
- Bump cache busters in index.html
customize.js loads after roles.js but before app.js triggers
navigate(). Restoring colors in the IIFE body (not DOMContentLoaded)
ensures ROLE_STYLE/ROLE_COLORS/TYPE_COLORS are updated BEFORE
the map or any page creates markers.
Changed nav brand, links, buttons from hardcoded #fff/#cbd5e1 to
var(--text) and var(--text-muted). Setting primary text color
now changes nav text too. Removed unnecessary --nav-text variable.