{
  "id": "corescope",
  "name": "CoreScope",
  "kind": "tool",
  "status": "active",
  "maturity": "stable",
  "description": "Self-hosted MeshCore packet analyzer with MQTT ingest, live packet feeds, interactive maps, channel chat, packet tracing and per-node analytics.",
  "image": "corescope.png",
  "maintainers": [
    {
      "name": "Kpa-clawbot",
      "url": "https://github.com/Kpa-clawbot"
    }
  ],
  "repository": "https://github.com/Kpa-clawbot/CoreScope",
  "documentation": "https://github.com/Kpa-clawbot/CoreScope/tree/master/docs",
  "license": "GPL-3.0-or-later",
  "languages": [
    "javascript",
    "go"
  ],
  "platforms": [
    "docker",
    "linux",
    "web"
  ],
  "interfaces": [
    "web",
    "api",
    "headless"
  ],
  "connections": [
    "mqtt",
    "http",
    "websocket"
  ],
  "capabilities": [
    "monitoring",
    "telemetry",
    "packet-analysis",
    "mapping"
  ],
  "install": [
    {
      "type": "docker",
      "package": "ghcr.io/kpa-clawbot/corescope",
      "url": "https://github.com/Kpa-clawbot/CoreScope/pkgs/container/corescope",
      "command": "docker run -d --name corescope --restart=unless-stopped -p 80:80 -p 1883:1883 -v /your/data:/app/data ghcr.io/kpa-clawbot/corescope:latest"
    },
    {
      "type": "github-release",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases"
    },
    {
      "type": "source",
      "url": "https://github.com/Kpa-clawbot/CoreScope"
    }
  ],
  "popularity": {
    "githubStars": 140,
    "githubForks": 34,
    "githubWatchers": 2,
    "githubOpenIssues": 115,
    "githubContributors": 18,
    "lastChecked": "2026-06-23"
  },
  "verification": {
    "sourceAvailable": true,
    "releasesAvailable": true,
    "signedReleases": false,
    "ciBuilds": true,
    "hasDocumentation": true,
    "lastChecked": "2026-06-23",
    "notes": [
      "Latest GitHub release has no binary assets; deployment is documented through the GHCR Docker image or source checkout."
    ]
  },
  "tags": [
    "monitoring",
    "analyzer",
    "map"
  ],
  "last_reviewed": "2026-06-23",
  "source": {
    "path": "data/software/corescope/software.yaml",
    "updatedAt": "2026-06-23T22:05:30+02:00"
  },
  "latest_version": "3.9.2",
  "released": "2026-06-13",
  "releases": [
    {
      "version": "v3.9.2",
      "name": "CoreScope v3.9.2",
      "datetime": "2026-06-13T04:48:03Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.9.2",
      "prerelease": false,
      "notes": "**Upgrade urgency: Recommended.** Big batch of operator-felt wins.\n\n## Highlights\n\n### Accessibility (#1668 — M2/M3/M4/M5 merged; M6 in flight)\n- Two-tier CSS palette with semantic tokens (`--palette-*` → `--color-*`/`--text-*`); operators can now hot-swap theme palettes\n- WCAG AA color-contrast hits across all 22 audited routes — **443 baseline violations → 0** verified by the new axe CI gate (#1696)\n- Typography pass: 14px body / 12px+500 chip floor across the entire chrome (#1679)\n- Hash-cell analytics palette, badge contrast, /live VCR controls (#1681)\n- Audio-lab BPM + Volume sliders now have aria-labels\n\n### MQTT observability\n- Per-source MQTT status endpoint + Observers panel (#1682) — connected/disconnected/last-packet/error counters per broker\n- Mobile-friendly: cards at ≤640px instead of squished table (#1698)\n\n### Cold-load + analytics correctness\n- **Packet-store cold-load actually loads the database now (#1691).** Pre-fix: after restart, only 0.3% of DB rows were loaded into RAM, dashboards looked empty for hours. Root cause: hot-load filtered by `first_seen` (immutable per hash) instead of effective recency. New `transmissions.last_seen` denormalized column + writer-side update fixes this end-to-end.\n- Analytics endpoint returns 503 Retry-After during warm-up (#1688) — operators no longer see post-restart partial slices\n- Warm-up banner surfaces backfill state in the UI (#1683)\n\n### Firmware 1.16 sync\n- Server-side decoder parity for extended ACK payloads (#1695)\n- Customizer toggle to hide 1-byte path hops (collide ~8-way at 2k nodes) — #1689\n\n### Performance\n- `/packets` init parallelizes loadObservers + loadPackets (#1693) — first-row time dropped from 25-40s to <5s on slow links\n- Confidence rating weighted by hash mode (#1687) — 1-byte sightings carry less weight than 6-byte\n\n### CI / operability\n- OpenAPI completeness gate (#1678 Phase 1)\n- Release fast-path: re-tag :edge → :vX.Y.Z when SHA matches (#1680) — most patch releases now skip the 30min full pipeline rerun\n- Staging disk monitor + cleanup cron (#1686)\n- Slideover E2E flake-gate reduced 20× → 3×, root cause fixed in #1693\n\n## Container image\n\n`ghcr.io/kpa-clawbot/corescope:v3.9.2` (multi-arch: amd64, arm64)\n\n## Verification\n\n- 22 routes × 2 themes × 2 viewports automated a11y gate (axe-core, #1696)\n- All 19 PRs ran through the full polish chain (parallel reviewers + Kent Beck TDD gate)\n- Cold-load fix verified by `transmissions.last_seen` test fixture (#1691)\n\n## Operator upgrade (MikroTik)\n\n```\n/container add remote-image=ghcr.io/kpa-clawbot/corescope:v3.9.2 envlist=cs-env mounts=cs-data,cs-caddyfile interface=veth-corescope start-on-boot=yes name=corescope-prod-v392\n```\n\n## Acknowledgements\n\nExternal contributors carry over from v3.9.1.",
      "notesHtml": "<p><strong>Upgrade urgency: Recommended.</strong> Big batch of operator-felt wins.</p>\n<h2>Highlights</h2>\n<h3>Accessibility (#1668 — M2/M3/M4/M5 merged; M6 in flight)</h3>\n<ul>\n<li>Two-tier CSS palette with semantic tokens (<code>--palette-*</code> → <code>--color-*</code>/<code>--text-*</code>); operators can now hot-swap theme palettes</li>\n<li>WCAG AA color-contrast hits across all 22 audited routes — <strong>443 baseline violations → 0</strong> verified by the new axe CI gate (#1696)</li>\n<li>Typography pass: 14px body / 12px+500 chip floor across the entire chrome (#1679)</li>\n<li>Hash-cell analytics palette, badge contrast, /live VCR controls (#1681)</li>\n<li>Audio-lab BPM + Volume sliders now have aria-labels</li>\n</ul>\n<h3>MQTT observability</h3>\n<ul>\n<li>Per-source MQTT status endpoint + Observers panel (#1682) — connected/disconnected/last-packet/error counters per broker</li>\n<li>Mobile-friendly: cards at ≤640px instead of squished table (#1698)</li>\n</ul>\n<h3>Cold-load + analytics correctness</h3>\n<ul>\n<li><strong>Packet-store cold-load actually loads the database now (#1691).</strong> Pre-fix: after restart, only 0.3% of DB rows were loaded into RAM, dashboards looked empty for hours. Root cause: hot-load filtered by <code>first_seen</code> (immutable per hash) instead of effective recency. New <code>transmissions.last_seen</code> denormalized column + writer-side update fixes this end-to-end.</li>\n<li>Analytics endpoint returns 503 Retry-After during warm-up (#1688) — operators no longer see post-restart partial slices</li>\n<li>Warm-up banner surfaces backfill state in the UI (#1683)</li>\n</ul>\n<h3>Firmware 1.16 sync</h3>\n<ul>\n<li>Server-side decoder parity for extended ACK payloads (#1695)</li>\n<li>Customizer toggle to hide 1-byte path hops (collide ~8-way at 2k nodes) — #1689</li>\n</ul>\n<h3>Performance</h3>\n<ul>\n<li><code>/packets</code> init parallelizes loadObservers + loadPackets (#1693) — first-row time dropped from 25-40s to &lt;5s on slow links</li>\n<li>Confidence rating weighted by hash mode (#1687) — 1-byte sightings carry less weight than 6-byte</li>\n</ul>\n<h3>CI / operability</h3>\n<ul>\n<li>OpenAPI completeness gate (#1678 Phase 1)</li>\n<li>Release fast-path: re-tag :edge → :vX.Y.Z when SHA matches (#1680) — most patch releases now skip the 30min full pipeline rerun</li>\n<li>Staging disk monitor + cleanup cron (#1686)</li>\n<li>Slideover E2E flake-gate reduced 20× → 3×, root cause fixed in #1693</li>\n</ul>\n<h2>Container image</h2>\n<p><code>ghcr.io/kpa-clawbot/corescope:v3.9.2</code> (multi-arch: amd64, arm64)</p>\n<h2>Verification</h2>\n<ul>\n<li>22 routes × 2 themes × 2 viewports automated a11y gate (axe-core, #1696)</li>\n<li>All 19 PRs ran through the full polish chain (parallel reviewers + Kent Beck TDD gate)</li>\n<li>Cold-load fix verified by <code>transmissions.last_seen</code> test fixture (#1691)</li>\n</ul>\n<h2>Operator upgrade (MikroTik)</h2>\n<pre><code>/container add remote-image=ghcr.io/kpa-clawbot/corescope:v3.9.2 envlist=cs-env mounts=cs-data,cs-caddyfile interface=veth-corescope start-on-boot=yes name=corescope-prod-v392\n</code></pre>\n<h2>Acknowledgements</h2>\n<p>External contributors carry over from v3.9.1.</p>\n"
    },
    {
      "version": "v3.9.1",
      "name": "CoreScope v3.9.1",
      "datetime": "2026-06-12T06:41:14Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.9.1",
      "prerelease": false,
      "notes": "**Upgrade urgency: Recommended** — v3.9.1 is what v3.9.0 should have been. v3.9.0's container image never published (Playwright flake gated Docker build). v3.9.1 includes everything in v3.9.0 plus:\n\n- **WCAG AA contrast pass** — new two-tier CSS palette (raw `--palette-*` → semantic `--color-*`/`--text-*`); muted-text family bumped to ≥4.5:1 in both themes (most well above — `--text-muted` on dark surface goes 3.5 → 11.58:1). Operator-reported unknown-repeater chip (\"dark-blue text on dark-blue background\") fixed (2.75:1 → 4.95:1). Closes #1671. Partial fix for #1668. (#1676, f0addfda)\n- **Slideover test stability** — `test-slideover-1056-e2e.js` was racing the packets virtual-scroll spacer; tightened selectors, bumped data-row wait to 20s. Cleared the day-of-release CI flakes. Fixes #1662. (#1663+followups, f06359d7)\n\n## Verification\n\nTest plan: `workspace-meshcore/test-plans/v3.9.0-cdp-test-plan.md` (93 tests, applies unchanged to v3.9.1).\nM1 a11y audit: `workspace-meshcore/a11y-audit/reports/violations-summary.md` (2,429 BLOCKER → estimated 85% cleared by M2).\n\n## Acknowledgements\n\nExternal contributors from v3.9.0 still apply: @efiten, @EldoonNemar. No new external PRs since v3.9.0.",
      "notesHtml": "<p><strong>Upgrade urgency: Recommended</strong> — v3.9.1 is what v3.9.0 should have been. v3.9.0's container image never published (Playwright flake gated Docker build). v3.9.1 includes everything in v3.9.0 plus:</p>\n<ul>\n<li><strong>WCAG AA contrast pass</strong> — new two-tier CSS palette (raw <code>--palette-*</code> → semantic <code>--color-*</code>/<code>--text-*</code>); muted-text family bumped to ≥4.5:1 in both themes (most well above — <code>--text-muted</code> on dark surface goes 3.5 → 11.58:1). Operator-reported unknown-repeater chip (\"dark-blue text on dark-blue background\") fixed (2.75:1 → 4.95:1). Closes #1671. Partial fix for #1668. (#1676, f0addfda)</li>\n<li><strong>Slideover test stability</strong> — <code>test-slideover-1056-e2e.js</code> was racing the packets virtual-scroll spacer; tightened selectors, bumped data-row wait to 20s. Cleared the day-of-release CI flakes. Fixes #1662. (#1663+followups, f06359d7)</li>\n</ul>\n<h2>Verification</h2>\n<p>Test plan: <code>workspace-meshcore/test-plans/v3.9.0-cdp-test-plan.md</code> (93 tests, applies unchanged to v3.9.1).\nM1 a11y audit: <code>workspace-meshcore/a11y-audit/reports/violations-summary.md</code> (2,429 BLOCKER → estimated 85% cleared by M2).</p>\n<h2>Acknowledgements</h2>\n<p>External contributors from v3.9.0 still apply: @efiten, @EldoonNemar. No new external PRs since v3.9.0.</p>\n"
    },
    {
      "version": "v3.9.0",
      "name": "CoreScope v3.9.0",
      "datetime": "2026-06-12T02:55:34Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.9.0",
      "prerelease": false,
      "notes": "**Upgrade urgency: Medium** — fixes the post-restart \"relay timelines empty\" regression, surfaces silent `/api/nodes` truncation, and ships operator-controlled per-name hiding.\n\n_257 commits since v3.8.3 (72 substantive + 185 auto-generated coverage bumps). Every bullet ends with a commit SHA — `git show <sha>` to verify._\n\n## Highlights\n\n- **Your relay timelines survive a restart.** Before v3.9.0, every container restart left repeater nodes with empty hop histories until live traffic replayed enough adverts to re-attribute. Now the relay-hop index is rebuilt from `path_json` during cold load — per-relay timelines, hop counts, and route stats are intact the moment the server says it's ready. (#1643, 938153dd)\n- **`/api/nodes` stops silently truncating at 500 rows.** The hard cap was hiding nodes from the map, analytics and packets pages on any mesh of meaningful size — without any warning. Now properly paginated across every consumer, with internal UI requests bypassing the per-page clamp. (#1607, 26105748; #1637, 9002b25b; #1589, 7421ead9)\n- **Hide your own node from a public dashboard with a prefix rename.** New `hiddenNamePrefixes` config (default `[\"🚫\"]`) drops matching nodes from `/api/nodes*` while keeping DB rows for analytics — same convention other MeshCore dashboards already follow, no DB surgery, no permanent loss of history. (#1655, 825b2648)\n- **Observer Compare is finally discoverable.** The compare page existed before but was a hidden URL trick; now there are three entry points (header CTA, sticky selector strip, observer-table multi-select) leading into a Tufte-grade compare view with state-preserving selection. (#1642, 531bc8ac; #1645, c93ae67e; #1647, 167af54e)\n- **Per-node Reach.** New `/api/nodes/{pubkey}/reach` + UI surfaces directional link quality per neighbor — answers \"is my link to X any good in both directions\" without staring at a topology graph. (#1627, e2212f50)\n\n\n## What's New\n\n### Observer Compare\n- Promote observer comparison to first-class: header CTA, sticky selector strip, observer-table multi-select. (#1642, 531bc8ac)\n- Tufte-grade compare page with themed button vocabulary + state-preserving multi-select across navigations. (#1645, c93ae67e)\n- Polish: tightened checkboxes, hierarchy, selector strip + mobile fixes. (#1647, 167af54e)\n- Wire `TableSort` on the observers table with numeric/time column types so the sort affordance actually sorts. (#1641, d72ab69f)\n\n### Reach & Nodes\n- Per-node Reach page + `GET /api/nodes/{pubkey}/reach` (directional link quality). (#1627, e2212f50)\n- Paginate `/api/nodes` across map/live/analytics/packets/area-map so the 500-row server cap stops silently truncating UIs. (#1607, 26105748; #1637, 9002b25b)\n- Sortable First Seen column on the Nodes table. (#1587, 7533b3b6)\n- Firmware `repeat:on|off` hint now excludes listener-only observers from the disambiguator. (#1624, a4776557)\n- Link RTC-reset warnings on node detail to the offending packet hashes. (#1590, 1a2b8c48)\n\n### Analytics\n- Relay Airtime Share endpoint + dumbbell chart. (#1601, 3898688d)\n- 5-minute rolling-baseline anomaly detection for Write Sources. (#1593, a26a412c)\n- TRACE packets overlay per-hop SNR on the path graph. (#1622, e9aed641)\n- Multi-byte prefix repeaters now show up in the 1-byte hash-usage matrix view. (#1591, 3df89241)\n\n### Live & Map\n- Fullscreen toggle on the live map + controls collapsed by default. (#1572, d7bd9d57)\n- Colorblind simulation overlay (Brettel/Vienot) with reset-to-Wong button. (#1600, 571c960c)\n- Path symbols legend disclosure on packets. (#1570, 5fd8900c)\n- OSM / Stamen tile providers with per-provider Leaflet layer control. (#1533, d7cd9203)\n- Operator-configurable `liveMap.maxNodes` (default 2000). (#1577, 1bdb92de)\n\n### Config & Operator Surfaces\n- `hiddenNamePrefixes` (default `[\"🚫\"]`) — drop matching nodes from `/api/nodes*` while preserving DB rows. (#1655, 825b2648)\n- Config-driven disabled-tabs list in the customizer modal. (#1579, 7292d60f)\n- `branding\n…",
      "notesHtml": "<p><strong>Upgrade urgency: Medium</strong> — fixes the post-restart \"relay timelines empty\" regression, surfaces silent <code>/api/nodes</code> truncation, and ships operator-controlled per-name hiding.</p>\n<p><em>257 commits since v3.8.3 (72 substantive + 185 auto-generated coverage bumps). Every bullet ends with a commit SHA — <code>git show &lt;sha&gt;</code> to verify.</em></p>\n<h2>Highlights</h2>\n<ul>\n<li><strong>Your relay timelines survive a restart.</strong> Before v3.9.0, every container restart left repeater nodes with empty hop histories until live traffic replayed enough adverts to re-attribute. Now the relay-hop index is rebuilt from <code>path_json</code> during cold load — per-relay timelines, hop counts, and route stats are intact the moment the server says it's ready. (#1643, 938153dd)</li>\n<li><strong><code>/api/nodes</code> stops silently truncating at 500 rows.</strong> The hard cap was hiding nodes from the map, analytics and packets pages on any mesh of meaningful size — without any warning. Now properly paginated across every consumer, with internal UI requests bypassing the per-page clamp. (#1607, 26105748; #1637, 9002b25b; #1589, 7421ead9)</li>\n<li><strong>Hide your own node from a public dashboard with a prefix rename.</strong> New <code>hiddenNamePrefixes</code> config (default <code>[\"🚫\"]</code>) drops matching nodes from <code>/api/nodes*</code> while keeping DB rows for analytics — same convention other MeshCore dashboards already follow, no DB surgery, no permanent loss of history. (#1655, 825b2648)</li>\n<li><strong>Observer Compare is finally discoverable.</strong> The compare page existed before but was a hidden URL trick; now there are three entry points (header CTA, sticky selector strip, observer-table multi-select) leading into a Tufte-grade compare view with state-preserving selection. (#1642, 531bc8ac; #1645, c93ae67e; #1647, 167af54e)</li>\n<li><strong>Per-node Reach.</strong> New <code>/api/nodes/{pubkey}/reach</code> + UI surfaces directional link quality per neighbor — answers \"is my link to X any good in both directions\" without staring at a topology graph. (#1627, e2212f50)</li>\n</ul>\n<h2>What's New</h2>\n<h3>Observer Compare</h3>\n<ul>\n<li>Promote observer comparison to first-class: header CTA, sticky selector strip, observer-table multi-select. (#1642, 531bc8ac)</li>\n<li>Tufte-grade compare page with themed button vocabulary + state-preserving multi-select across navigations. (#1645, c93ae67e)</li>\n<li>Polish: tightened checkboxes, hierarchy, selector strip + mobile fixes. (#1647, 167af54e)</li>\n<li>Wire <code>TableSort</code> on the observers table with numeric/time column types so the sort affordance actually sorts. (#1641, d72ab69f)</li>\n</ul>\n<h3>Reach &amp; Nodes</h3>\n<ul>\n<li>Per-node Reach page + <code>GET /api/nodes/{pubkey}/reach</code> (directional link quality). (#1627, e2212f50)</li>\n<li>Paginate <code>/api/nodes</code> across map/live/analytics/packets/area-map so the 500-row server cap stops silently truncating UIs. (#1607, 26105748; #1637, 9002b25b)</li>\n<li>Sortable First Seen column on the Nodes table. (#1587, 7533b3b6)</li>\n<li>Firmware <code>repeat:on|off</code> hint now excludes listener-only observers from the disambiguator. (#1624, a4776557)</li>\n<li>Link RTC-reset warnings on node detail to the offending packet hashes. (#1590, 1a2b8c48)</li>\n</ul>\n<h3>Analytics</h3>\n<ul>\n<li>Relay Airtime Share endpoint + dumbbell chart. (#1601, 3898688d)</li>\n<li>5-minute rolling-baseline anomaly detection for Write Sources. (#1593, a26a412c)</li>\n<li>TRACE packets overlay per-hop SNR on the path graph. (#1622, e9aed641)</li>\n<li>Multi-byte prefix repeaters now show up in the 1-byte hash-usage matrix view. (#1591, 3df89241)</li>\n</ul>\n<h3>Live &amp; Map</h3>\n<ul>\n<li>Fullscreen toggle on the live map + controls collapsed by default. (#1572, d7bd9d57)</li>\n<li>Colorblind simulation overlay (Brettel/Vienot) with reset-to-Wong button. (#1600, 571c960c)</li>\n<li>Path symbols legend disclosure on packets. (#1570, 5fd8900c)</li>\n<li>OSM / Stamen tile providers with per-provider Leaflet layer control. (#1533, d7cd9203)</li>\n<li>Operator-configurable <code>liveMap.maxNodes</code> (default 2000). (#1577, 1bdb92de)</li>\n</ul>\n<h3>Config &amp; Operator Surfaces</h3>\n<ul>\n<li><code>hiddenNamePrefixes</code> (default <code>[\"🚫\"]</code>) — drop matching nodes from <code>/api/nodes*</code> while preserving DB rows. (#1655, 825b2648)</li>\n<li>Config-driven disabled-tabs list in the customizer modal. (#1579, 7292d60f)</li>\n<li>`branding\n…</li>\n</ul>\n"
    },
    {
      "version": "v3.8.3",
      "name": "CoreScope v3.8.3",
      "datetime": "2026-06-04T01:49:15Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.8.3",
      "prerelease": false,
      "notes": "# CoreScope v3.8.3\n\n**Upgrade urgency: High** — contains a stored XSS fix. Public dashboards should upgrade immediately.\n\n_187 commits since v3.8.2 (47 substantive + 140 auto-generated coverage bumps). Every bullet ends with a commit SHA — `git show <sha>` to verify._\n\n## Highlights\n\n- **The live map is buttery now.** Packet animations and node pulses moved off SVG-per-frame onto a hardware-accelerated canvas overlay — 60 FPS even when the mesh is firing on all cylinders. (#1490, 914f8694; #1521, 75a38f02)\n- **Dashboards and APIs feel snappier under load.** `/api/observers` p95 dropped from ~10.8s to sub-second on busy meshes, `/api/stats` is no longer re-scanning the observations table every 15s for the navbar, and per-observer analytics stopped re-parsing every timestamp three times. (#1483, 13bdee57; #1516, 38506001)\n- **Stored XSS class closed — same class as CVE-2026-45323.** Mesh-advertised node names and observer names were rendered to the DOM unescaped on app/nodes/observers/packets/live/analytics/route-view/area-map; a payload like `<img src=x onerror=…>` executed. All sinks now escape; follow-up audits closed three more XSS sinks, an unbounded-`limit` DoS, and several log-injection paths. New CI gate hard-fails PRs that reintroduce either class. (#1537, f15b6779; #1539, 53339e08; #1540, 800d61c3; #1544, 7b430450; #1543, e4a21fc9)\n- **Cross-domain embeds.** `?embed=1` on `/#/map` and `/#/channels` strips the chrome, and a new `CORS_ALLOWED_ORIGINS` env unlocks Home Assistant / Grafana panel embeds. (#1500, 367265eb)\n- **The map's region filter now actually filters the map.** Selecting a region hides non-region nodes by default; a \"Show all nodes\" toggle preserves the old behavior. (#1501, 28713fab)\n- **Observers with broken clocks now name themselves.** Observers emitting zone-less timestamps get a ⚠️ chip on the observer list and a banner on detail with fix instructions — no more \"why is this observer showing 1970?\" support tickets. (#1480, 43b93c6b)\n- **Operators with big DBs no longer wait minutes for ingestor startup.** Heavy index builds run in the background via a new async-migration runner; the ingestor accepts packets immediately on boot. (#1541, e438451d)\n- **Fresh deploys can decrypt #Public out of the box.** Default Public channel key now ships in `channel-rainbow.json`. (#897, 451b5e88)\n\n<details>\n<summary><b>Stored-XSS sink list (for security verification)</b></summary>\n\nHTML-escaped at: `app.js` global search dropdown (node + channel name); `nodes.js` table row + Leaflet popups (×2); `observers.js` table name cell; `packets.js` observer-name cells (×4) + multi-select checkbox label; `live.js` node-filter `<option>` + map tooltip + hop popup; `analytics.js` topology tooltip + RF-health aria-label; `packets.js` CHAN hex decode fields; `route-view.js` hop + union tooltips (×2); `area-map.html` node popups (×2). Global `escapeHtml` now covers the full 5-char OWASP set (`& < > \" '`). `map.js` `safeEsc` was a no-op identity since #48 — now wired to the real escaper. Backend `sanitizeName()` deliberately keeps `< > \" &` for lossless storage / `meshcore://` deep-links; fix is at the sink per OWASP. Round-2 sweep added: `traces.js` URL-fragment in popups, `observer-detail.js` MQTT-meta tooltip, `analytics.js` RF-health `aria-label`. Pinned by `test-xss-escape-sinks.js` and `test-anl1-tooltip-render.js` with tag-injection and attribute-breakout payloads. (#1537, #1539)\n\n</details>\n\n## Features\n\n- Hide non-region nodes by default on the live map when a region is selected; \"Show all nodes\" toggle restores legacy view; state in `localStorage['mc-region-show-all-nodes']`. Fixes #1108. (#1501, 28713fab)\n- `?embed=1` on `/#/map` or `/#/channels` suppresses top-nav, bottom-nav, drawer; new `corsAllowedOrigins` config + `CORS_ALLOWED_ORIGINS` env; `Access-Control-Allow-Methods` tightened to `GET, HEAD, OPTIONS`. Fixes #1369. (#1500, 367265eb)\n- Customizer: marker stroke color/width/opacity tunable via Colors → Marker Stro\n…",
      "notesHtml": "<h1>CoreScope v3.8.3</h1>\n<p><strong>Upgrade urgency: High</strong> — contains a stored XSS fix. Public dashboards should upgrade immediately.</p>\n<p><em>187 commits since v3.8.2 (47 substantive + 140 auto-generated coverage bumps). Every bullet ends with a commit SHA — <code>git show &lt;sha&gt;</code> to verify.</em></p>\n<h2>Highlights</h2>\n<ul>\n<li><strong>The live map is buttery now.</strong> Packet animations and node pulses moved off SVG-per-frame onto a hardware-accelerated canvas overlay — 60 FPS even when the mesh is firing on all cylinders. (#1490, 914f8694; #1521, 75a38f02)</li>\n<li><strong>Dashboards and APIs feel snappier under load.</strong> <code>/api/observers</code> p95 dropped from ~10.8s to sub-second on busy meshes, <code>/api/stats</code> is no longer re-scanning the observations table every 15s for the navbar, and per-observer analytics stopped re-parsing every timestamp three times. (#1483, 13bdee57; #1516, 38506001)</li>\n<li><strong>Stored XSS class closed — same class as CVE-2026-45323.</strong> Mesh-advertised node names and observer names were rendered to the DOM unescaped on app/nodes/observers/packets/live/analytics/route-view/area-map; a payload like <code>&lt;img src=x onerror=…&gt;</code> executed. All sinks now escape; follow-up audits closed three more XSS sinks, an unbounded-<code>limit</code> DoS, and several log-injection paths. New CI gate hard-fails PRs that reintroduce either class. (#1537, f15b6779; #1539, 53339e08; #1540, 800d61c3; #1544, 7b430450; #1543, e4a21fc9)</li>\n<li><strong>Cross-domain embeds.</strong> <code>?embed=1</code> on <code>/#/map</code> and <code>/#/channels</code> strips the chrome, and a new <code>CORS_ALLOWED_ORIGINS</code> env unlocks Home Assistant / Grafana panel embeds. (#1500, 367265eb)</li>\n<li><strong>The map's region filter now actually filters the map.</strong> Selecting a region hides non-region nodes by default; a \"Show all nodes\" toggle preserves the old behavior. (#1501, 28713fab)</li>\n<li><strong>Observers with broken clocks now name themselves.</strong> Observers emitting zone-less timestamps get a ⚠️ chip on the observer list and a banner on detail with fix instructions — no more \"why is this observer showing 1970?\" support tickets. (#1480, 43b93c6b)</li>\n<li><strong>Operators with big DBs no longer wait minutes for ingestor startup.</strong> Heavy index builds run in the background via a new async-migration runner; the ingestor accepts packets immediately on boot. (#1541, e438451d)</li>\n<li><strong>Fresh deploys can decrypt #Public out of the box.</strong> Default Public channel key now ships in <code>channel-rainbow.json</code>. (#897, 451b5e88)</li>\n</ul>\n\n<b>Stored-XSS sink list (for security verification)</b><p>HTML-escaped at: <code>app.js</code> global search dropdown (node + channel name); <code>nodes.js</code> table row + Leaflet popups (×2); <code>observers.js</code> table name cell; <code>packets.js</code> observer-name cells (×4) + multi-select checkbox label; <code>live.js</code> node-filter <code>&lt;option&gt;</code> + map tooltip + hop popup; <code>analytics.js</code> topology tooltip + RF-health aria-label; <code>packets.js</code> CHAN hex decode fields; <code>route-view.js</code> hop + union tooltips (×2); <code>area-map.html</code> node popups (×2). Global <code>escapeHtml</code> now covers the full 5-char OWASP set (<code>&amp; &lt; &gt; \" '</code>). <code>map.js</code> <code>safeEsc</code> was a no-op identity since #48 — now wired to the real escaper. Backend <code>sanitizeName()</code> deliberately keeps <code>&lt; &gt; \" &amp;</code> for lossless storage / <code>meshcore://</code> deep-links; fix is at the sink per OWASP. Round-2 sweep added: <code>traces.js</code> URL-fragment in popups, <code>observer-detail.js</code> MQTT-meta tooltip, <code>analytics.js</code> RF-health <code>aria-label</code>. Pinned by <code>test-xss-escape-sinks.js</code> and <code>test-anl1-tooltip-render.js</code> with tag-injection and attribute-breakout payloads. (#1537, #1539)</p>\n<h2>Features</h2>\n<ul>\n<li>Hide non-region nodes by default on the live map when a region is selected; \"Show all nodes\" toggle restores legacy view; state in <code>localStorage['mc-region-show-all-nodes']</code>. Fixes #1108. (#1501, 28713fab)</li>\n<li><code>?embed=1</code> on <code>/#/map</code> or <code>/#/channels</code> suppresses top-nav, bottom-nav, drawer; new <code>corsAllowedOrigins</code> config + <code>CORS_ALLOWED_ORIGINS</code> env; <code>Access-Control-Allow-Methods</code> tightened to <code>GET, HEAD, OPTIONS</code>. Fixes #1369. (#1500, 367265eb)</li>\n<li>Customizer: marker stroke color/width/opacity tunable via Colors → Marker Stro\n…</li>\n</ul>\n"
    },
    {
      "version": "v3.8.2",
      "name": "v3.8.2 — Mobile UX, Customizer, Performance",
      "datetime": "2026-05-29T02:50:54Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.8.2",
      "prerelease": false,
      "notes": "# v3.8.2 — Mobile UX, Customizer, Performance\n\n_(Draft for operator review. ~3 weeks of work since v3.8.1, ~25 substantive commits.)_\n\n## Headliners\n\n- **Mobile UX overhaul** — packets surface redesigned mobile-first with single-row navbar, semantic-first detail panel, hidden chrome rail; nav controls (Favorites/Search/Customize) finally reachable on phones (#1471 — closes #1415/#1458/#1461/#1467/#1468/#1470).\n- **Customizer reframe** — CB preset is now an end-user OPT-IN, not a force-override. Operator's `config.json nodeColors` are honored by default; per-user customizer override beats the preset (#1446/#1447/#1448/#1449 — fixes longstanding \"config not honored\" reports).\n- **Dark-tile provider picker** — 4 dark map variants pickable in customizer: Carto Dark, Esri Dark Gray, Voyager Inverted, Positron Inverted (#1420/#1430). Honored across all map surfaces including node-detail inset (#1470).\n- **Observer timestamps fixed** — California (UTC-7) observers were perpetually 7h stale because naive timestamps were treated as UTC. Now: `observer.last_seen` always uses ingest time, per-packet rxTime keeps envelope time with symmetric clamp (#1463/#1464/#1465/#1466).\n- **Cold-path cache** — `/api/nodes` no longer blocks on stale-cache rebuild; serves stale-while-revalidate (#1272/#1436).\n\n## Operator-facing changes\n\n### UI / UX\n- New customizer toggle: **Show encrypted channels** (default OFF — finally a way to control the encrypted-channel flood without DevTools) (#1454/#1455)\n- **Traffic share** label replaces the misleading \"Usefulness\" score (renamed everywhere; new `traffic_share_score` JSON field added alongside the legacy `usefulness_score` for API compat). Tooltips explain both Traffic share and Bridge score with tap-to-toast on mobile (#1456/#1457)\n- **Custom navbar logos** no longer squished to 125×36 — proportional aspect preserved for square, tall, wide variants (#1450/#1451)\n- **Per-role color overrides** now actually propagate to marker SVGs (was silently dropped before — #1438/#1439/#1441/#1443)\n- \"unknown\" fake channel no longer accumulates in the channel list (live router was synthesizing it from undecryptable CHAN messages — #1468)\n- Hash matrix marks **0x00 and 0xFF as reserved prefixes** per MeshCore firmware keygen convention (@halo779 report — #1473/#1474)\n- Mobile gesture hints — touch-only gate added (no longer show on desktop), width:fit-content fix for off-screen pills (#1065 chain)\n\n### Server / Performance\n- `repeaterEnrichTTL` cache cold-path now stale-while-revalidate; first request after expiry no longer blocks (#1272/#1436)\n- Hourly SQLite WAL checkpoint prevents unbounded WAL growth (#1435)\n- Startup warning when `GOMEMLIMIT < 50% of container memory limit` — prevents future OOMs (#1264/#1429)\n- WebSocket MQTT broker support (`ws://` / `wss://`) documented + tested (#902)\n- Paths-through-node now sorted by recency (LastSeen DESC) with count as tiebreaker — was non-deterministic map iteration order (#1145/#1431)\n\n### Bug fixes\n- Naive (zone-less) ISO timestamps in MQTT envelopes now symmetrically clamped (was: only future-skew rejected, past-skew passed through verbatim — California observers perpetually stale) (#1463/#1464)\n- Observer.last_seen always uses ingest time, never envelope time (architectural cleanup, eliminates whole class of TZ bugs) (#1465/#1466)\n- Hop-name mis-resolution on prefix collision — regression test added for #1144 (the production fix shipped earlier; this guards against re-introduction) (#1433)\n- Channel-color-picker outside-click E2E flake fixed for real this time (race with `setTimeout(0)` listener install — proper fix using rAF×2 + retry) (#1462)\n- CI: master runs no longer cancelled by subsequent commits (was dropping Deploy Staging silently — #1395/#1426)\n- CSS parse-error: stray non-comment text after `#1062` block was eating the next CSS rule from CSSOM (gesture-hint .width:fit-content wasn't taking effect for weeks) (#1065/#1453)\n\n## Community contributions this cycl\n…",
      "notesHtml": "<h1>v3.8.2 — Mobile UX, Customizer, Performance</h1>\n<p><em>(Draft for operator review. ~3 weeks of work since v3.8.1, ~25 substantive commits.)</em></p>\n<h2>Headliners</h2>\n<ul>\n<li><strong>Mobile UX overhaul</strong> — packets surface redesigned mobile-first with single-row navbar, semantic-first detail panel, hidden chrome rail; nav controls (Favorites/Search/Customize) finally reachable on phones (#1471 — closes #1415/#1458/#1461/#1467/#1468/#1470).</li>\n<li><strong>Customizer reframe</strong> — CB preset is now an end-user OPT-IN, not a force-override. Operator's <code>config.json nodeColors</code> are honored by default; per-user customizer override beats the preset (#1446/#1447/#1448/#1449 — fixes longstanding \"config not honored\" reports).</li>\n<li><strong>Dark-tile provider picker</strong> — 4 dark map variants pickable in customizer: Carto Dark, Esri Dark Gray, Voyager Inverted, Positron Inverted (#1420/#1430). Honored across all map surfaces including node-detail inset (#1470).</li>\n<li><strong>Observer timestamps fixed</strong> — California (UTC-7) observers were perpetually 7h stale because naive timestamps were treated as UTC. Now: <code>observer.last_seen</code> always uses ingest time, per-packet rxTime keeps envelope time with symmetric clamp (#1463/#1464/#1465/#1466).</li>\n<li><strong>Cold-path cache</strong> — <code>/api/nodes</code> no longer blocks on stale-cache rebuild; serves stale-while-revalidate (#1272/#1436).</li>\n</ul>\n<h2>Operator-facing changes</h2>\n<h3>UI / UX</h3>\n<ul>\n<li>New customizer toggle: <strong>Show encrypted channels</strong> (default OFF — finally a way to control the encrypted-channel flood without DevTools) (#1454/#1455)</li>\n<li><strong>Traffic share</strong> label replaces the misleading \"Usefulness\" score (renamed everywhere; new <code>traffic_share_score</code> JSON field added alongside the legacy <code>usefulness_score</code> for API compat). Tooltips explain both Traffic share and Bridge score with tap-to-toast on mobile (#1456/#1457)</li>\n<li><strong>Custom navbar logos</strong> no longer squished to 125×36 — proportional aspect preserved for square, tall, wide variants (#1450/#1451)</li>\n<li><strong>Per-role color overrides</strong> now actually propagate to marker SVGs (was silently dropped before — #1438/#1439/#1441/#1443)</li>\n<li>\"unknown\" fake channel no longer accumulates in the channel list (live router was synthesizing it from undecryptable CHAN messages — #1468)</li>\n<li>Hash matrix marks <strong>0x00 and 0xFF as reserved prefixes</strong> per MeshCore firmware keygen convention (@halo779 report — #1473/#1474)</li>\n<li>Mobile gesture hints — touch-only gate added (no longer show on desktop), width:fit-content fix for off-screen pills (#1065 chain)</li>\n</ul>\n<h3>Server / Performance</h3>\n<ul>\n<li><code>repeaterEnrichTTL</code> cache cold-path now stale-while-revalidate; first request after expiry no longer blocks (#1272/#1436)</li>\n<li>Hourly SQLite WAL checkpoint prevents unbounded WAL growth (#1435)</li>\n<li>Startup warning when <code>GOMEMLIMIT &lt; 50% of container memory limit</code> — prevents future OOMs (#1264/#1429)</li>\n<li>WebSocket MQTT broker support (<code>ws://</code> / <code>wss://</code>) documented + tested (#902)</li>\n<li>Paths-through-node now sorted by recency (LastSeen DESC) with count as tiebreaker — was non-deterministic map iteration order (#1145/#1431)</li>\n</ul>\n<h3>Bug fixes</h3>\n<ul>\n<li>Naive (zone-less) ISO timestamps in MQTT envelopes now symmetrically clamped (was: only future-skew rejected, past-skew passed through verbatim — California observers perpetually stale) (#1463/#1464)</li>\n<li>Observer.last_seen always uses ingest time, never envelope time (architectural cleanup, eliminates whole class of TZ bugs) (#1465/#1466)</li>\n<li>Hop-name mis-resolution on prefix collision — regression test added for #1144 (the production fix shipped earlier; this guards against re-introduction) (#1433)</li>\n<li>Channel-color-picker outside-click E2E flake fixed for real this time (race with <code>setTimeout(0)</code> listener install — proper fix using rAF×2 + retry) (#1462)</li>\n<li>CI: master runs no longer cancelled by subsequent commits (was dropping Deploy Staging silently — #1395/#1426)</li>\n<li>CSS parse-error: stray non-comment text after <code>#1062</code> block was eating the next CSS rule from CSSOM (gesture-hint .width:fit-content wasn't taking effect for weeks) (#1065/#1453)</li>\n</ul>\n<h2>Community contributions this cycl</h2>\n<p>…</p>\n"
    },
    {
      "version": "v3.8.1",
      "name": "v3.8.1 — Route, Refined",
      "datetime": "2026-05-27T08:29:10Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.8.1",
      "prerelease": false,
      "notes": "# CoreScope v3.8.1 — Route, Refined\n\n_Released 2026-05-27. Previous: [v3.7.2](https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.7.2) (2026-05-06). Supersedes [v3.8.0](https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.8.0) (released earlier today, before this perf fix landed)._\n\n## ⚡ Patch in v3.8.1: `/api/nodes` TTL fix\n\nCommunity contribution from **@efiten** (#1425). The `repeaterEnrichTTL` was 15 seconds but the background recomputer runs every 5 minutes — so the cache was stale for 4m45s out of every 5min. On dense hop-graph deployments that meant 18s `/api/nodes` cold-miss responses. TTL is now derived as `2 * recomputer interval` so the cache stays warm between ticks. Measured 18.6s → ~0.5s on the contributor's prod mesh (analyzer.on8ar.eu).\n\nIf you already deployed v3.8.0, just upgrade to v3.8.1 — same image semantics, drop-in.\n\n---\n\nThree weeks, 135 merged PRs. The headline is the **Route Viewer redesign** — packets and the paths they travel, finally rendered as one coherent story. Underneath: startup-window improvements for large DBs, a near-complete mobile chrome overhaul.\n\n---\n\n## 🗺️ NEW: Route Viewer\n\nThe new route viewer (`route-view.js` + `route-view.css`) puts packet context, observer paths, and resolved hops in one sidebar — and reflows cleanly to a bottom sheet on mobile.\n\n- **Packet context block** in the sidebar header — per-type fact list (ADVERT name/role/sig/pubkey, DM src→dst with encryption state, GRP_TXT channel + decrypted preview, TRACE official-vs-observed hops, PATH src→dst with payload-source chips). Merges `pkt.decoded_json` + `obs.decoded_json` and falls back to byte-level `raw_hex` parsing for encrypted DMs.\n- **Multi-path picker** — chip per unique observer-path (`<count>/<total>` + hex hop string). Click to isolate; \"All\" renders an edge-deduplicated UNION view (each unique edge once, stroke weight = observer count).\n- **Deep-link URLs** — `#/map?packet=<hash>&obs=<id>`. Bookmarkable, shareable, single source of truth. sessionStorage flow removed.\n- **Hop resolution priority chain** — server `resolved_path` → shared `HopResolver` (observer-IATA-aware, same as packets page) → raw prefix. Kills a whole class of \"route view named hops differently than packet detail\" bugs.\n- **Markers** — uniform 22 px filled circle with seq number inside, hollow endpoint ring for SRC/DST, double concentric ring on SRC=DST loops, spider-fan for sub-14 px collisions debounced to `zoomend`.\n- **Colorblind-preset live colors** — `routeRamp` per preset (viridis / plasma / pure-luminance) written to `--mc-rt-ramp-0..4` CSS vars and hot-recoloured on `cb-preset-changed` / `theme-changed`.\n- **Desktop chrome** — drag-to-resize sidebar persisted to localStorage, Material/Drive-style collapse chevron centred on the right edge.\n- **Mobile bottom sheet** — anchored above bottom-nav + safe-area inset, thin summary line when collapsed (`TYPE · N hops · X km · M obs`), expands to ~75 vh. All three legacy mobile detail panels closed on route entry.\n\nCloses: #1418, #1419, #1422 · PR #1423\n\nRelated map work that landed this cycle: role-aware marker shapes + outline rings (#1334), WCAG 2.2 AA pass on cluster bubbles + role pills + multi-byte labels (#1357), thinner always-on marker outline (#1347).\n\n---\n\n## ⚡ Startup\n\n- **Hot startup window** — new `packetStore.hotStartupHours` config loads only N hours of packet history synchronously and fills the rest in background daily chunks. Large DBs (multi-GB) no longer block the HTTP listener on a full-retention load before serving traffic (#1187).\n- **Ingestor now owns the neighbor-graph and all schema migrations**; the server is read-only. Removes a class of startup races where two processes raced the same migration (#1286, #1289).\n\n---\n\n## 🔬 NEW: Protocol decoder coverage\n\nThe MeshCore decoder now handles every documented payload type, with the legend updated end-to-end so operators see real names instead of `Type 6`.\n\n- **GRP_DATA + MULTIPART** wire-format decoded (#12\n…",
      "notesHtml": "<h1>CoreScope v3.8.1 — Route, Refined</h1>\n<p><em>Released 2026-05-27. Previous: <a href=\"https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.7.2\" target=\"_blank\" rel=\"noopener noreferrer\">v3.7.2</a> (2026-05-06). Supersedes <a href=\"https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.8.0\" target=\"_blank\" rel=\"noopener noreferrer\">v3.8.0</a> (released earlier today, before this perf fix landed).</em></p>\n<h2>⚡ Patch in v3.8.1: <code>/api/nodes</code> TTL fix</h2>\n<p>Community contribution from <strong>@efiten</strong> (#1425). The <code>repeaterEnrichTTL</code> was 15 seconds but the background recomputer runs every 5 minutes — so the cache was stale for 4m45s out of every 5min. On dense hop-graph deployments that meant 18s <code>/api/nodes</code> cold-miss responses. TTL is now derived as <code>2 * recomputer interval</code> so the cache stays warm between ticks. Measured 18.6s → ~0.5s on the contributor's prod mesh (analyzer.on8ar.eu).</p>\n<p>If you already deployed v3.8.0, just upgrade to v3.8.1 — same image semantics, drop-in.</p>\n<hr />\n<p>Three weeks, 135 merged PRs. The headline is the <strong>Route Viewer redesign</strong> — packets and the paths they travel, finally rendered as one coherent story. Underneath: startup-window improvements for large DBs, a near-complete mobile chrome overhaul.</p>\n<hr />\n<h2>🗺️ NEW: Route Viewer</h2>\n<p>The new route viewer (<code>route-view.js</code> + <code>route-view.css</code>) puts packet context, observer paths, and resolved hops in one sidebar — and reflows cleanly to a bottom sheet on mobile.</p>\n<ul>\n<li><strong>Packet context block</strong> in the sidebar header — per-type fact list (ADVERT name/role/sig/pubkey, DM src→dst with encryption state, GRP_TXT channel + decrypted preview, TRACE official-vs-observed hops, PATH src→dst with payload-source chips). Merges <code>pkt.decoded_json</code> + <code>obs.decoded_json</code> and falls back to byte-level <code>raw_hex</code> parsing for encrypted DMs.</li>\n<li><strong>Multi-path picker</strong> — chip per unique observer-path (<code>&lt;count&gt;/&lt;total&gt;</code> + hex hop string). Click to isolate; \"All\" renders an edge-deduplicated UNION view (each unique edge once, stroke weight = observer count).</li>\n<li><strong>Deep-link URLs</strong> — <code>#/map?packet=&lt;hash&gt;&amp;obs=&lt;id&gt;</code>. Bookmarkable, shareable, single source of truth. sessionStorage flow removed.</li>\n<li><strong>Hop resolution priority chain</strong> — server <code>resolved_path</code> → shared <code>HopResolver</code> (observer-IATA-aware, same as packets page) → raw prefix. Kills a whole class of \"route view named hops differently than packet detail\" bugs.</li>\n<li><strong>Markers</strong> — uniform 22 px filled circle with seq number inside, hollow endpoint ring for SRC/DST, double concentric ring on SRC=DST loops, spider-fan for sub-14 px collisions debounced to <code>zoomend</code>.</li>\n<li><strong>Colorblind-preset live colors</strong> — <code>routeRamp</code> per preset (viridis / plasma / pure-luminance) written to <code>--mc-rt-ramp-0..4</code> CSS vars and hot-recoloured on <code>cb-preset-changed</code> / <code>theme-changed</code>.</li>\n<li><strong>Desktop chrome</strong> — drag-to-resize sidebar persisted to localStorage, Material/Drive-style collapse chevron centred on the right edge.</li>\n<li><strong>Mobile bottom sheet</strong> — anchored above bottom-nav + safe-area inset, thin summary line when collapsed (<code>TYPE · N hops · X km · M obs</code>), expands to ~75 vh. All three legacy mobile detail panels closed on route entry.</li>\n</ul>\n<p>Closes: #1418, #1419, #1422 · PR #1423</p>\n<p>Related map work that landed this cycle: role-aware marker shapes + outline rings (#1334), WCAG 2.2 AA pass on cluster bubbles + role pills + multi-byte labels (#1357), thinner always-on marker outline (#1347).</p>\n<hr />\n<h2>⚡ Startup</h2>\n<ul>\n<li><strong>Hot startup window</strong> — new <code>packetStore.hotStartupHours</code> config loads only N hours of packet history synchronously and fills the rest in background daily chunks. Large DBs (multi-GB) no longer block the HTTP listener on a full-retention load before serving traffic (#1187).</li>\n<li><strong>Ingestor now owns the neighbor-graph and all schema migrations</strong>; the server is read-only. Removes a class of startup races where two processes raced the same migration (#1286, #1289).</li>\n</ul>\n<hr />\n<h2>🔬 NEW: Protocol decoder coverage</h2>\n<p>The MeshCore decoder now handles every documented payload type, with the legend updated end-to-end so operators see real names instead of <code>Type 6</code>.</p>\n<ul>\n<li><strong>GRP_DATA + MULTIPART</strong> wire-format decoded (#12\n…</li>\n</ul>\n"
    },
    {
      "version": "v3.8.0",
      "name": "v3.8.0 — Route, Refined (superseded by v3.8.1)",
      "datetime": "2026-05-27T08:11:17Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.8.0",
      "prerelease": true,
      "notes": "> ⚠️ **Superseded by [v3.8.1](https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.8.1)** — released the same day with a community-contributed `/api/nodes` TTL fix (#1425, @efiten). Use v3.8.1 instead.\n\n---\n\n# CoreScope v3.8.0 — Route, Refined\n\n_Released 2026-05-27. Previous: [v3.7.2](https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.7.2) (2026-05-06)._\n\nThree weeks, 135 merged PRs. The headline is the **Route Viewer redesign** — packets and the paths they travel, finally rendered as one coherent story. Underneath: startup-window improvements for large DBs, a near-complete mobile chrome overhaul.\n\n---\n\n## 🗺️ NEW: Route Viewer\n\nThe new route viewer (`route-view.js` + `route-view.css`) puts packet context, observer paths, and resolved hops in one sidebar — and reflows cleanly to a bottom sheet on mobile.\n\n- **Packet context block** in the sidebar header — per-type fact list (ADVERT name/role/sig/pubkey, DM src→dst with encryption state, GRP_TXT channel + decrypted preview, TRACE official-vs-observed hops, PATH src→dst with payload-source chips). Merges `pkt.decoded_json` + `obs.decoded_json` and falls back to byte-level `raw_hex` parsing for encrypted DMs.\n- **Multi-path picker** — chip per unique observer-path (`<count>/<total>` + hex hop string). Click to isolate; \"All\" renders an edge-deduplicated UNION view (each unique edge once, stroke weight = observer count).\n- **Deep-link URLs** — `#/map?packet=<hash>&obs=<id>`. Bookmarkable, shareable, single source of truth. sessionStorage flow removed.\n- **Hop resolution priority chain** — server `resolved_path` → shared `HopResolver` (observer-IATA-aware, same as packets page) → raw prefix. Kills a whole class of \"route view named hops differently than packet detail\" bugs.\n- **Markers** — uniform 22 px filled circle with seq number inside, hollow endpoint ring for SRC/DST, double concentric ring on SRC=DST loops, spider-fan for sub-14 px collisions debounced to `zoomend`.\n- **Colorblind-preset live colors** — `routeRamp` per preset (viridis / plasma / pure-luminance) written to `--mc-rt-ramp-0..4` CSS vars and hot-recoloured on `cb-preset-changed` / `theme-changed`.\n- **Desktop chrome** — drag-to-resize sidebar persisted to localStorage, Material/Drive-style collapse chevron centred on the right edge.\n- **Mobile bottom sheet** — anchored above bottom-nav + safe-area inset, thin summary line when collapsed (`TYPE · N hops · X km · M obs`), expands to ~75 vh. All three legacy mobile detail panels closed on route entry.\n\nCloses: #1418, #1419, #1422 · PR #1423\n\nRelated map work that landed this cycle: role-aware marker shapes + outline rings (#1334), WCAG 2.2 AA pass on cluster bubbles + role pills + multi-byte labels (#1357), thinner always-on marker outline (#1347).\n\n---\n\n## ⚡ Startup\n\n- **Hot startup window** — new `packetStore.hotStartupHours` config loads only N hours of packet history synchronously and fills the rest in background daily chunks. Large DBs (multi-GB) no longer block the HTTP listener on a full-retention load before serving traffic (#1187).\n- **Ingestor now owns the neighbor-graph and all schema migrations**; the server is read-only. Removes a class of startup races where two processes raced the same migration (#1286, #1289).\n\n---\n\n## 🔬 NEW: Protocol decoder coverage\n\nThe MeshCore decoder now handles every documented payload type, with the legend updated end-to-end so operators see real names instead of `Type 6`.\n\n- **GRP_DATA + MULTIPART** wire-format decoded (#1280) — group-data sensor payloads + multipart fragmentation are no longer opaque blobs.\n- **CONTROL flags** + `advertRole` fix — accurate role attribution on ADVERTs from edge cases that were previously misclassified (#1280).\n- **`payloadTypeNames` table** centralised, exposed at the API + UI; new TransportCodes / Feat1 / Feat2 enums; RAW_CUSTOM type plumbed through; sensor-payload docs published (#1291).\n\n---\n\n## 🆕 NEW: Operator features\n\n- **Repeater \"usefulness score\" (bridge axis)** — secon\n…",
      "notesHtml": "<blockquote>\n<p>⚠️ <strong>Superseded by <a href=\"https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.8.1\" target=\"_blank\" rel=\"noopener noreferrer\">v3.8.1</a></strong> — released the same day with a community-contributed <code>/api/nodes</code> TTL fix (#1425, @efiten). Use v3.8.1 instead.</p>\n</blockquote>\n<hr />\n<h1>CoreScope v3.8.0 — Route, Refined</h1>\n<p><em>Released 2026-05-27. Previous: <a href=\"https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.7.2\" target=\"_blank\" rel=\"noopener noreferrer\">v3.7.2</a> (2026-05-06).</em></p>\n<p>Three weeks, 135 merged PRs. The headline is the <strong>Route Viewer redesign</strong> — packets and the paths they travel, finally rendered as one coherent story. Underneath: startup-window improvements for large DBs, a near-complete mobile chrome overhaul.</p>\n<hr />\n<h2>🗺️ NEW: Route Viewer</h2>\n<p>The new route viewer (<code>route-view.js</code> + <code>route-view.css</code>) puts packet context, observer paths, and resolved hops in one sidebar — and reflows cleanly to a bottom sheet on mobile.</p>\n<ul>\n<li><strong>Packet context block</strong> in the sidebar header — per-type fact list (ADVERT name/role/sig/pubkey, DM src→dst with encryption state, GRP_TXT channel + decrypted preview, TRACE official-vs-observed hops, PATH src→dst with payload-source chips). Merges <code>pkt.decoded_json</code> + <code>obs.decoded_json</code> and falls back to byte-level <code>raw_hex</code> parsing for encrypted DMs.</li>\n<li><strong>Multi-path picker</strong> — chip per unique observer-path (<code>&lt;count&gt;/&lt;total&gt;</code> + hex hop string). Click to isolate; \"All\" renders an edge-deduplicated UNION view (each unique edge once, stroke weight = observer count).</li>\n<li><strong>Deep-link URLs</strong> — <code>#/map?packet=&lt;hash&gt;&amp;obs=&lt;id&gt;</code>. Bookmarkable, shareable, single source of truth. sessionStorage flow removed.</li>\n<li><strong>Hop resolution priority chain</strong> — server <code>resolved_path</code> → shared <code>HopResolver</code> (observer-IATA-aware, same as packets page) → raw prefix. Kills a whole class of \"route view named hops differently than packet detail\" bugs.</li>\n<li><strong>Markers</strong> — uniform 22 px filled circle with seq number inside, hollow endpoint ring for SRC/DST, double concentric ring on SRC=DST loops, spider-fan for sub-14 px collisions debounced to <code>zoomend</code>.</li>\n<li><strong>Colorblind-preset live colors</strong> — <code>routeRamp</code> per preset (viridis / plasma / pure-luminance) written to <code>--mc-rt-ramp-0..4</code> CSS vars and hot-recoloured on <code>cb-preset-changed</code> / <code>theme-changed</code>.</li>\n<li><strong>Desktop chrome</strong> — drag-to-resize sidebar persisted to localStorage, Material/Drive-style collapse chevron centred on the right edge.</li>\n<li><strong>Mobile bottom sheet</strong> — anchored above bottom-nav + safe-area inset, thin summary line when collapsed (<code>TYPE · N hops · X km · M obs</code>), expands to ~75 vh. All three legacy mobile detail panels closed on route entry.</li>\n</ul>\n<p>Closes: #1418, #1419, #1422 · PR #1423</p>\n<p>Related map work that landed this cycle: role-aware marker shapes + outline rings (#1334), WCAG 2.2 AA pass on cluster bubbles + role pills + multi-byte labels (#1357), thinner always-on marker outline (#1347).</p>\n<hr />\n<h2>⚡ Startup</h2>\n<ul>\n<li><strong>Hot startup window</strong> — new <code>packetStore.hotStartupHours</code> config loads only N hours of packet history synchronously and fills the rest in background daily chunks. Large DBs (multi-GB) no longer block the HTTP listener on a full-retention load before serving traffic (#1187).</li>\n<li><strong>Ingestor now owns the neighbor-graph and all schema migrations</strong>; the server is read-only. Removes a class of startup races where two processes raced the same migration (#1286, #1289).</li>\n</ul>\n<hr />\n<h2>🔬 NEW: Protocol decoder coverage</h2>\n<p>The MeshCore decoder now handles every documented payload type, with the legend updated end-to-end so operators see real names instead of <code>Type 6</code>.</p>\n<ul>\n<li><strong>GRP_DATA + MULTIPART</strong> wire-format decoded (#1280) — group-data sensor payloads + multipart fragmentation are no longer opaque blobs.</li>\n<li><strong>CONTROL flags</strong> + <code>advertRole</code> fix — accurate role attribution on ADVERTs from edge cases that were previously misclassified (#1280).</li>\n<li><strong><code>payloadTypeNames</code> table</strong> centralised, exposed at the API + UI; new TransportCodes / Feat1 / Feat2 enums; RAW_CUSTOM type plumbed through; sensor-payload docs published (#1291).</li>\n</ul>\n<hr />\n<h2>🆕 NEW: Operator features</h2>\n<ul>\n<li><strong>Repeater \"usefulness score\" (bridge axis)</strong> — secon\n…</li>\n</ul>\n"
    },
    {
      "version": "v3.7.2",
      "name": "v3.7.2",
      "datetime": "2026-05-06T19:33:26Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.7.2",
      "prerelease": false,
      "notesHtml": null
    },
    {
      "version": "v3.7.1",
      "name": "v3.7.1",
      "datetime": "2026-05-05T00:08:03Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.7.1",
      "prerelease": false,
      "notes": "Identical to v3.7.0 + live PSK decrypt fix (#1031). This is the tagged release with the Docker image on GHCR.\n\n```\ndocker pull ghcr.io/kpa-clawbot/corescope:v3.7.1\n```\n\nSee [v3.7.0 release notes](https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.7.0) for full changelog.",
      "notesHtml": "<p>Identical to v3.7.0 + live PSK decrypt fix (#1031). This is the tagged release with the Docker image on GHCR.</p>\n<pre><code>docker pull ghcr.io/kpa-clawbot/corescope:v3.7.1\n</code></pre>\n<p>See <a href=\"https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.7.0\" target=\"_blank\" rel=\"noopener noreferrer\">v3.7.0 release notes</a> for full changelog.</p>\n"
    },
    {
      "version": "v3.7.0",
      "name": "v3.7.0 — Operator's Toolkit",
      "datetime": "2026-05-04T05:13:52Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.7.0",
      "prerelease": false,
      "notes": "# CoreScope v3.7.0 — \"Operator's Toolkit\"\n\n50 commits · 30 issues closed · 95 files changed · +7,710 / -293 LOC\n\n---\n\n### Channel Decryption\nRebuilt from scratch. Pure-JS AES-128-ECB + SHA-256/HMAC — works on HTTP and HTTPS. PSK channels: add by key, name them, delete them, see decrypt count. Live WebSocket auto-decrypt with unread badges. Channel hash-byte dedup fixed.\n\n### Analytics\nSelectable timeframes (1h/24h/7d/30d). Region attribution by repeater home, not observer. \"All\" filter fixed. Topology dedup (no more triple-counted repeaters). Timestamp format applied to chart axes.\n\n### New Surfaces\n- `/#/roles` — role distribution + per-role clock-skew\n- `/api/backup` — API-key gated SQLite export via VACUUM INTO\n- `/api/healthz` — readiness probe\n\n### Packets & Filters\nTransport route type filter (`transport == true`, `T_FLOOD`, `T_DIRECT`). Clear filters button. Scroll position preserved. Null hash/timestamp packets cleaned. GRP_DATA type 6 support.\n\n### Map & Nodes\nShort pubkey-prefix URLs (📡 Copy short URL). Multi-byte hash support indicator on markers. Channel color picker fixed.\n\n### MQTT & Ingestor\nAsync path_json backfill (no more startup blocks). Per-source `region` field. MQTT startup resilience (unreachable sources don't block). Configurable connect timeout. Observer IATA whitelist + blacklist. Per-hop TRACE SNR extraction in ingestor.\n\n### Observer & Node\nFlood/direct filter on comparison page. TRACE per-hop SNR in byte breakdown. Clock skew evidence UI. Separate last-status vs last-packet timestamps. Soft-deleted observers excluded. Export/import includes favorites.\n\n### Infra\nCORS allowlist. RW SQLite connection cache. GeoFilter Builder draft save/load/download. False-positive path exclusion.\n\n---\n\n**Upgrade:** New config fields in `config.example.json`: `observerIATAWhitelist`, `observerBlacklist`, per-source `region`/`connectTimeoutSec`, CORS `allowedOrigins`. One-time DB migration runs async on startup.\n\n---\n\n> ⚠️ **Docker image not available for this tag** (CI trigger issue). Use **v3.7.1** for the container image: `ghcr.io/kpa-clawbot/corescope:v3.7.1`",
      "notesHtml": "<h1>CoreScope v3.7.0 — \"Operator's Toolkit\"</h1>\n<p>50 commits · 30 issues closed · 95 files changed · +7,710 / -293 LOC</p>\n<hr />\n<h3>Channel Decryption</h3>\n<p>Rebuilt from scratch. Pure-JS AES-128-ECB + SHA-256/HMAC — works on HTTP and HTTPS. PSK channels: add by key, name them, delete them, see decrypt count. Live WebSocket auto-decrypt with unread badges. Channel hash-byte dedup fixed.</p>\n<h3>Analytics</h3>\n<p>Selectable timeframes (1h/24h/7d/30d). Region attribution by repeater home, not observer. \"All\" filter fixed. Topology dedup (no more triple-counted repeaters). Timestamp format applied to chart axes.</p>\n<h3>New Surfaces</h3>\n<ul>\n<li><code>/#/roles</code> — role distribution + per-role clock-skew</li>\n<li><code>/api/backup</code> — API-key gated SQLite export via VACUUM INTO</li>\n<li><code>/api/healthz</code> — readiness probe</li>\n</ul>\n<h3>Packets &amp; Filters</h3>\n<p>Transport route type filter (<code>transport == true</code>, <code>T_FLOOD</code>, <code>T_DIRECT</code>). Clear filters button. Scroll position preserved. Null hash/timestamp packets cleaned. GRP_DATA type 6 support.</p>\n<h3>Map &amp; Nodes</h3>\n<p>Short pubkey-prefix URLs (📡 Copy short URL). Multi-byte hash support indicator on markers. Channel color picker fixed.</p>\n<h3>MQTT &amp; Ingestor</h3>\n<p>Async path_json backfill (no more startup blocks). Per-source <code>region</code> field. MQTT startup resilience (unreachable sources don't block). Configurable connect timeout. Observer IATA whitelist + blacklist. Per-hop TRACE SNR extraction in ingestor.</p>\n<h3>Observer &amp; Node</h3>\n<p>Flood/direct filter on comparison page. TRACE per-hop SNR in byte breakdown. Clock skew evidence UI. Separate last-status vs last-packet timestamps. Soft-deleted observers excluded. Export/import includes favorites.</p>\n<h3>Infra</h3>\n<p>CORS allowlist. RW SQLite connection cache. GeoFilter Builder draft save/load/download. False-positive path exclusion.</p>\n<hr />\n<p><strong>Upgrade:</strong> New config fields in <code>config.example.json</code>: <code>observerIATAWhitelist</code>, <code>observerBlacklist</code>, per-source <code>region</code>/<code>connectTimeoutSec</code>, CORS <code>allowedOrigins</code>. One-time DB migration runs async on startup.</p>\n<hr />\n<blockquote>\n<p>⚠️ <strong>Docker image not available for this tag</strong> (CI trigger issue). Use <strong>v3.7.1</strong> for the container image: <code>ghcr.io/kpa-clawbot/corescope:v3.7.1</code></p>\n</blockquote>\n"
    },
    {
      "version": "v3.6.0",
      "name": "v3.6.0 - The Forensics",
      "datetime": "2026-05-01T09:25:39Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.6.0",
      "prerelease": false,
      "notes": "# v3.6.0 - The Forensics\n\nCoreScope just got eyes everywhere. This release drops **path inspection**, **color-by-hash markers**, **clock skew detection**, **full channel encryption**, an **observer graph**, and a pile of robustness fixes that make your mesh network feel like it's being watched by someone who actually cares.\n\n134 commits, 105 PRs merged, 18K+ lines added. Here's what shipped.\n\n---\n\n## 🚀 New Features\n\n### Path-Prefix Candidate Inspector (#944, #945)\nThe marquee feature. Click any path segment and CoreScope opens an interactive inspector showing every candidate node that could match that hop prefix - plotted on a map with scoring by neighbor-graph affinity and geographic centroid. Ambiguous hops? Now you can see *why* they're ambiguous and pick the right one.\n\n**Why you'll love it:** No more guessing which `0xA3` is the real repeater. The inspector lays out every candidate, scores them, and lets you drill in visually.\n\n### Color-by-Hash Packet Markers (#948, #951)\nEvery packet type gets a vivid, hash-derived color - on the live feed, map polylines, and flying-packet animations. Bright fill with dark outline for contrast. No more monochrome blobs - you can visually track packet flows by color at a glance.\n\n### Node Filter on Live Page (#924, #771)\nFilter the live packet stream to show only traffic flowing through a specific node. Pick a repeater, see exactly what it's carrying. That simple.\n\n### Clock Skew Detection (#746, #752, #828, #850)\nFull pipeline: backend computes drift using Theil-Sen regression with outlier rejection (#828), the UI shows per-node badges, detail sparklines, and fleet-wide analytics (#752). Bimodal clock severity (#850) surfaces flaky-RTC nodes that toggle between accurate and drifted - instead of hiding them as \"No Clock.\"\n\n**Why you'll love it:** Nodes with bad clocks silently corrupt your timeline. Now they glow red before they ruin your analysis.\n\n### Observer Graph (M1+M2) (#774)\nObservers are now first-class graph citizens. CoreScope builds a neighbor graph from observation overlaps, scores hop-resolver candidates by graph edges (#876), and uses geographic centroid for tiebreaking. The observer topology is visible and queryable.\n\n### Channel Encryption - Full Stack (#726, #733, #750, #760)\nThree milestones landed as one: DB-backed channel message history (#726), client-side PSK decryption in the browser (#733), and PSK channel management with add/remove UX and message caching (#750). Add a channel key in the UI, and CoreScope decrypts messages client-side - no server-side key storage. The add-channel button (#760) makes it dead simple.\n\n**Why you'll love it:** Encrypted channels are no longer black boxes. Add your PSK, see the messages, search history - all without exposing keys to the server.\n\n### Hash Collision Inspector (#758)\nThe Hash Usage Matrix now shows collision details for all hash sizes. When two nodes share a prefix, you see exactly who collides and at what size.\n\n### Geofilter Builder - In-App (#735, #900)\nThe geofilter polygon builder is now served directly from CoreScope with a full docs page (#900). No more hunting for external tools. Link from the customizer, draw your polygon, done.\n\n### Node Blacklist (#742)\n`nodeBlacklist` in config hides abusive or troll nodes from all views. They're gone.\n\n### Observer Retention (#764)\nStale observers are automatically pruned after a configurable number of days. Your observer list stays clean without manual intervention.\n\n### Advert Signature Validation (#794)\nCorrupt packets with invalid advert signatures are now rejected at ingest. Bad data never hits your store.\n\n### Bounded Cold Load (#790)\n`Load()` now respects a memory budget - no more OOM on cold start with a fat database. Combined with retention-hours cutoff (#917), cold start is safe on constrained hardware.\n\n### Multi-Arch Docker Images (#869)\nOfficial images now publish `amd64` + `arm64` in a single multi-arch manifest. Raspberry Pi operators: pull and run. No specia\n…",
      "notesHtml": "<h1>v3.6.0 - The Forensics</h1>\n<p>CoreScope just got eyes everywhere. This release drops <strong>path inspection</strong>, <strong>color-by-hash markers</strong>, <strong>clock skew detection</strong>, <strong>full channel encryption</strong>, an <strong>observer graph</strong>, and a pile of robustness fixes that make your mesh network feel like it's being watched by someone who actually cares.</p>\n<p>134 commits, 105 PRs merged, 18K+ lines added. Here's what shipped.</p>\n<hr />\n<h2>🚀 New Features</h2>\n<h3>Path-Prefix Candidate Inspector (#944, #945)</h3>\n<p>The marquee feature. Click any path segment and CoreScope opens an interactive inspector showing every candidate node that could match that hop prefix - plotted on a map with scoring by neighbor-graph affinity and geographic centroid. Ambiguous hops? Now you can see <em>why</em> they're ambiguous and pick the right one.</p>\n<p><strong>Why you'll love it:</strong> No more guessing which <code>0xA3</code> is the real repeater. The inspector lays out every candidate, scores them, and lets you drill in visually.</p>\n<h3>Color-by-Hash Packet Markers (#948, #951)</h3>\n<p>Every packet type gets a vivid, hash-derived color - on the live feed, map polylines, and flying-packet animations. Bright fill with dark outline for contrast. No more monochrome blobs - you can visually track packet flows by color at a glance.</p>\n<h3>Node Filter on Live Page (#924, #771)</h3>\n<p>Filter the live packet stream to show only traffic flowing through a specific node. Pick a repeater, see exactly what it's carrying. That simple.</p>\n<h3>Clock Skew Detection (#746, #752, #828, #850)</h3>\n<p>Full pipeline: backend computes drift using Theil-Sen regression with outlier rejection (#828), the UI shows per-node badges, detail sparklines, and fleet-wide analytics (#752). Bimodal clock severity (#850) surfaces flaky-RTC nodes that toggle between accurate and drifted - instead of hiding them as \"No Clock.\"</p>\n<p><strong>Why you'll love it:</strong> Nodes with bad clocks silently corrupt your timeline. Now they glow red before they ruin your analysis.</p>\n<h3>Observer Graph (M1+M2) (#774)</h3>\n<p>Observers are now first-class graph citizens. CoreScope builds a neighbor graph from observation overlaps, scores hop-resolver candidates by graph edges (#876), and uses geographic centroid for tiebreaking. The observer topology is visible and queryable.</p>\n<h3>Channel Encryption - Full Stack (#726, #733, #750, #760)</h3>\n<p>Three milestones landed as one: DB-backed channel message history (#726), client-side PSK decryption in the browser (#733), and PSK channel management with add/remove UX and message caching (#750). Add a channel key in the UI, and CoreScope decrypts messages client-side - no server-side key storage. The add-channel button (#760) makes it dead simple.</p>\n<p><strong>Why you'll love it:</strong> Encrypted channels are no longer black boxes. Add your PSK, see the messages, search history - all without exposing keys to the server.</p>\n<h3>Hash Collision Inspector (#758)</h3>\n<p>The Hash Usage Matrix now shows collision details for all hash sizes. When two nodes share a prefix, you see exactly who collides and at what size.</p>\n<h3>Geofilter Builder - In-App (#735, #900)</h3>\n<p>The geofilter polygon builder is now served directly from CoreScope with a full docs page (#900). No more hunting for external tools. Link from the customizer, draw your polygon, done.</p>\n<h3>Node Blacklist (#742)</h3>\n<p><code>nodeBlacklist</code> in config hides abusive or troll nodes from all views. They're gone.</p>\n<h3>Observer Retention (#764)</h3>\n<p>Stale observers are automatically pruned after a configurable number of days. Your observer list stays clean without manual intervention.</p>\n<h3>Advert Signature Validation (#794)</h3>\n<p>Corrupt packets with invalid advert signatures are now rejected at ingest. Bad data never hits your store.</p>\n<h3>Bounded Cold Load (#790)</h3>\n<p><code>Load()</code> now respects a memory budget - no more OOM on cold start with a fat database. Combined with retention-hours cutoff (#917), cold start is safe on constrained hardware.</p>\n<h3>Multi-Arch Docker Images (#869)</h3>\n<p>Official images now publish <code>amd64</code> + <code>arm64</code> in a single multi-arch manifest. Raspberry Pi operators: pull and run. No specia\n…</p>\n"
    },
    {
      "version": "v3.5.2",
      "name": "CoreScope v3.5.2",
      "datetime": "2026-04-13T05:49:59Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.5.2",
      "prerelease": false,
      "notes": "## CoreScope v3.5.2\n\n### 🔧 New: `corescope-decrypt` CLI\nStandalone tool for retroactive channel message decryption. Decrypt hashtag channel messages from your SQLite database — even packets ingested before the channel key was configured.\n\n```bash\ncorescope-decrypt --channel \"#test\" --db meshcore.db --format irc\ncorescope-decrypt --channel \"#wardriving\" --db meshcore.db --format json --output messages.json\ncorescope-decrypt --channel \"#sf\" --db meshcore.db --format html --output viewer.html\n```\n\nThree output formats: JSON (full metadata), HTML (interactive viewer), IRC/log (greppable plain text). Included in the Docker image at `/app/corescope-decrypt`. Only works with hashtag channels (`#name`) — the `public` channel uses a pre-shared key, not hashtag derivation.\n\n### ⚡ Eviction redesign\nReplaced `HeapAlloc`-based eviction with self-accounting `trackedBytes` counter. No more cascading eviction on large databases. High/low watermark hysteresis + 25% safety cap.\n\n### 📊 Cache invalidation tuning\nWired the `invalidationDebounce` config (was dead code). Default cooldown 10s → 300s. Collision cache only clears on new nodes. Expected cache hit rate: 7% → 50-80%.\n\n### 🔬 Multi-byte capability\nRepeater multi-byte detection integrated into Hash Adopters table. Filter buttons fixed. TRACE packets excluded from suspected detection (pre-1.14 repeaters can forward multi-byte TRACEs).\n\n### 🐛 Fixes\n- Relay-only nodes now appear as alive (path hop indexing + debounced DB touch)\n- Live feed timestamps refresh every 10s (no more stale \"10s ago\")\n- Perf page shows tracked memory, not heap\n- SQLite busy_timeout on all write connections\n- Dockerfile includes `internal/sigvalidate`\n\n### Downloads\n- `corescope-decrypt-linux-amd64` — x86_64 static binary\n- `corescope-decrypt-linux-arm64` — ARM64 static binary (Raspberry Pi, etc.)\n\n### Upgrade\n```bash\ndocker pull ghcr.io/kpa-clawbot/corescope:v3.5.2\n```",
      "notesHtml": "<h2>CoreScope v3.5.2</h2>\n<h3>🔧 New: <code>corescope-decrypt</code> CLI</h3>\n<p>Standalone tool for retroactive channel message decryption. Decrypt hashtag channel messages from your SQLite database — even packets ingested before the channel key was configured.</p>\n<pre><code>corescope-decrypt --channel \"#test\" --db meshcore.db --format irc\ncorescope-decrypt --channel \"#wardriving\" --db meshcore.db --format json --output messages.json\ncorescope-decrypt --channel \"#sf\" --db meshcore.db --format html --output viewer.html\n</code></pre>\n<p>Three output formats: JSON (full metadata), HTML (interactive viewer), IRC/log (greppable plain text). Included in the Docker image at <code>/app/corescope-decrypt</code>. Only works with hashtag channels (<code>#name</code>) — the <code>public</code> channel uses a pre-shared key, not hashtag derivation.</p>\n<h3>⚡ Eviction redesign</h3>\n<p>Replaced <code>HeapAlloc</code>-based eviction with self-accounting <code>trackedBytes</code> counter. No more cascading eviction on large databases. High/low watermark hysteresis + 25% safety cap.</p>\n<h3>📊 Cache invalidation tuning</h3>\n<p>Wired the <code>invalidationDebounce</code> config (was dead code). Default cooldown 10s → 300s. Collision cache only clears on new nodes. Expected cache hit rate: 7% → 50-80%.</p>\n<h3>🔬 Multi-byte capability</h3>\n<p>Repeater multi-byte detection integrated into Hash Adopters table. Filter buttons fixed. TRACE packets excluded from suspected detection (pre-1.14 repeaters can forward multi-byte TRACEs).</p>\n<h3>🐛 Fixes</h3>\n<ul>\n<li>Relay-only nodes now appear as alive (path hop indexing + debounced DB touch)</li>\n<li>Live feed timestamps refresh every 10s (no more stale \"10s ago\")</li>\n<li>Perf page shows tracked memory, not heap</li>\n<li>SQLite busy_timeout on all write connections</li>\n<li>Dockerfile includes <code>internal/sigvalidate</code></li>\n</ul>\n<h3>Downloads</h3>\n<ul>\n<li><code>corescope-decrypt-linux-amd64</code> — x86_64 static binary</li>\n<li><code>corescope-decrypt-linux-arm64</code> — ARM64 static binary (Raspberry Pi, etc.)</li>\n</ul>\n<h3>Upgrade</h3>\n<pre><code>docker pull ghcr.io/kpa-clawbot/corescope:v3.5.2\n</code></pre>\n"
    },
    {
      "version": "v3.5.0",
      "name": "CoreScope v3.5.0 🚀",
      "datetime": "2026-04-08T07:27:29Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.5.0",
      "prerelease": false,
      "notes": "# CoreScope v3.5.0 🚀\n\nThe \"stop building from source and start analyzing your mesh\" release. 95 commits.\n\n---\n\n## 🐳 Pre-built Docker Images\n\nCoreScope now ships as a ready-to-run Docker image on GitHub Container Registry. No cloning, no building, no dependencies — just pull and run.\n\n```bash\ndocker run -d --name corescope -p 80:80 -p 443:443 -p 1883:1883 \\\n  -v corescope-data:/app/data \\\n  ghcr.io/kpa-clawbot/corescope:v3.5.0\n```\n\n**Using HTTPS with a custom domain?** Mount your Caddyfile and certs directory:\n```bash\ndocker run -d --name corescope -p 80:80 -p 443:443 -p 1883:1883 \\\n  -v /your/data:/app/data \\\n  -v /your/Caddyfile:/etc/caddy/Caddyfile:ro \\\n  -v /your/caddy-data:/data/caddy \\\n  ghcr.io/kpa-clawbot/corescope:v3.5.0\n```\nCaddy auto-provisions Let's Encrypt certs. Your Caddyfile just needs:\n```\nyourdomain.example.com {\n    reverse_proxy localhost:3000\n}\n```\n\nThat's it. Zero config required — MQTT broker, Caddy HTTPS, and SQLite are built in.\n\n**Already running CoreScope?**\n```bash\n# 1. Find your running container name\ndocker ps --format '{{.Names}}'\n\n# 2. Stop and remove it\ndocker stop <container-name> && docker rm <container-name>\n\n# 3. Pull the pre-built image\ndocker pull ghcr.io/kpa-clawbot/corescope:v3.5.0\n\n# 4. Run with your existing data directory\ndocker run -d --name corescope -p 80:80 -p 443:443 -p 1883:1883 \\\n  -v /your/data:/app/data \\\n  -v /your/Caddyfile:/etc/caddy/Caddyfile:ro \\\n  -v /your/caddy-data:/data/caddy \\\n  ghcr.io/kpa-clawbot/corescope:v3.5.0\n```\nYour data volume stays. Nothing to migrate.\n\nTags: `v3.5.0` (this release) · `latest` (latest tagged release) · `edge` (master tip, for testing). Env: `DISABLE_CADDY=true` / `DISABLE_MOSQUITTO=true` if you bring your own.\n\n---\n\n## ⚡ 83% Faster\n\n35 performance commits. Packets endpoint p50 dropped from 16.7ms → 2.7ms. Server now serves HTTP within 2 minutes on *any* DB size — async background backfill means you're never staring at a loading screen. N+1 API calls killed everywhere. Prefix map memory cut 10x. WebSocket renders batched via rAF.\n\n---\n\n## 🔬 RF Health Dashboard\n\nNew Analytics tab. Per-observer noise floor as color-coded columns (green/yellow/red), airtime utilization, error rates, battery levels. Click any observer for the full breakdown. Region-filterable. This is the beginning of making CoreScope more than just a packet viewer.\n\n---\n\n## 🗺️ See Where Traces Actually Go\n\nSend a trace → watch it on the live map. Solid animated line shows how far it got. Dashed ghost shows where it didn't reach. Finally know *where* your trace failed, not just *that* it failed.\n\n---\n\n## 📊 Things That Were Lying To You\n\n- \"By Repeaters\" was counting companions. Fixed.\n- Zero-hop adverts claimed \"1 byte hash\" when the hash size was unknowable. Fixed.\n- \"Packets through this node\" showed packets through a *different* node with the same prefix. Fixed — now uses the neighbor affinity graph.\n- Table sorting on nodes/neighbors/observers silently did nothing. Fixed.\n\n---\n\n## 🔗 Deep Links · 🎨 Channel Colors · 📱 Mobile · 🔑 Security\n\n**Deep links** — every page state goes in the URL. Share a link to a specific node, filter, or analytics tab.\n\n**Channel colors** — click the color dot next to any channel, pick from 8 colors, see it highlighted across the feed. Persists in localStorage.\n\n**Distance units** — km, miles, or auto-detect from locale. Customizer → Display.\n\n**Mobile** — 44px touch targets, ARIA labels, responsive breakpoints.\n\n**Security** — weak API keys rejected at startup. License: GPL v3.\n\n---\n\n## 📡 Full API Documentation\n\nEvery endpoint is now documented with an auto-generated OpenAPI 3.0 spec — always in sync with the running server.\n\n- **Interactive Swagger UI:** [analyzer.00id.net/api/docs](https://analyzer.00id.net/api/docs) — browse and test all 40+ endpoints\n- **Machine-readable spec:** [analyzer.00id.net/api/spec](https://analyzer.00id.net/api/spec) — import into Postman, Insomnia, or use for bot/integration development\n\nOn your own instanc\n…",
      "notesHtml": "<h1>CoreScope v3.5.0 🚀</h1>\n<p>The \"stop building from source and start analyzing your mesh\" release. 95 commits.</p>\n<hr />\n<h2>🐳 Pre-built Docker Images</h2>\n<p>CoreScope now ships as a ready-to-run Docker image on GitHub Container Registry. No cloning, no building, no dependencies — just pull and run.</p>\n<pre><code>docker run -d --name corescope -p 80:80 -p 443:443 -p 1883:1883 \\\n  -v corescope-data:/app/data \\\n  ghcr.io/kpa-clawbot/corescope:v3.5.0\n</code></pre>\n<p><strong>Using HTTPS with a custom domain?</strong> Mount your Caddyfile and certs directory:</p>\n<pre><code>docker run -d --name corescope -p 80:80 -p 443:443 -p 1883:1883 \\\n  -v /your/data:/app/data \\\n  -v /your/Caddyfile:/etc/caddy/Caddyfile:ro \\\n  -v /your/caddy-data:/data/caddy \\\n  ghcr.io/kpa-clawbot/corescope:v3.5.0\n</code></pre>\n<p>Caddy auto-provisions Let's Encrypt certs. Your Caddyfile just needs:</p>\n<pre><code>yourdomain.example.com {\n    reverse_proxy localhost:3000\n}\n</code></pre>\n<p>That's it. Zero config required — MQTT broker, Caddy HTTPS, and SQLite are built in.</p>\n<p><strong>Already running CoreScope?</strong></p>\n<pre><code># 1. Find your running container name\ndocker ps --format '{{.Names}}'\n\n# 2. Stop and remove it\ndocker stop &lt;container-name&gt; &amp;&amp; docker rm &lt;container-name&gt;\n\n# 3. Pull the pre-built image\ndocker pull ghcr.io/kpa-clawbot/corescope:v3.5.0\n\n# 4. Run with your existing data directory\ndocker run -d --name corescope -p 80:80 -p 443:443 -p 1883:1883 \\\n  -v /your/data:/app/data \\\n  -v /your/Caddyfile:/etc/caddy/Caddyfile:ro \\\n  -v /your/caddy-data:/data/caddy \\\n  ghcr.io/kpa-clawbot/corescope:v3.5.0\n</code></pre>\n<p>Your data volume stays. Nothing to migrate.</p>\n<p>Tags: <code>v3.5.0</code> (this release) · <code>latest</code> (latest tagged release) · <code>edge</code> (master tip, for testing). Env: <code>DISABLE_CADDY=true</code> / <code>DISABLE_MOSQUITTO=true</code> if you bring your own.</p>\n<hr />\n<h2>⚡ 83% Faster</h2>\n<p>35 performance commits. Packets endpoint p50 dropped from 16.7ms → 2.7ms. Server now serves HTTP within 2 minutes on <em>any</em> DB size — async background backfill means you're never staring at a loading screen. N+1 API calls killed everywhere. Prefix map memory cut 10x. WebSocket renders batched via rAF.</p>\n<hr />\n<h2>🔬 RF Health Dashboard</h2>\n<p>New Analytics tab. Per-observer noise floor as color-coded columns (green/yellow/red), airtime utilization, error rates, battery levels. Click any observer for the full breakdown. Region-filterable. This is the beginning of making CoreScope more than just a packet viewer.</p>\n<hr />\n<h2>🗺️ See Where Traces Actually Go</h2>\n<p>Send a trace → watch it on the live map. Solid animated line shows how far it got. Dashed ghost shows where it didn't reach. Finally know <em>where</em> your trace failed, not just <em>that</em> it failed.</p>\n<hr />\n<h2>📊 Things That Were Lying To You</h2>\n<ul>\n<li>\"By Repeaters\" was counting companions. Fixed.</li>\n<li>Zero-hop adverts claimed \"1 byte hash\" when the hash size was unknowable. Fixed.</li>\n<li>\"Packets through this node\" showed packets through a <em>different</em> node with the same prefix. Fixed — now uses the neighbor affinity graph.</li>\n<li>Table sorting on nodes/neighbors/observers silently did nothing. Fixed.</li>\n</ul>\n<hr />\n<h2>🔗 Deep Links · 🎨 Channel Colors · 📱 Mobile · 🔑 Security</h2>\n<p><strong>Deep links</strong> — every page state goes in the URL. Share a link to a specific node, filter, or analytics tab.</p>\n<p><strong>Channel colors</strong> — click the color dot next to any channel, pick from 8 colors, see it highlighted across the feed. Persists in localStorage.</p>\n<p><strong>Distance units</strong> — km, miles, or auto-detect from locale. Customizer → Display.</p>\n<p><strong>Mobile</strong> — 44px touch targets, ARIA labels, responsive breakpoints.</p>\n<p><strong>Security</strong> — weak API keys rejected at startup. License: GPL v3.</p>\n<hr />\n<h2>📡 Full API Documentation</h2>\n<p>Every endpoint is now documented with an auto-generated OpenAPI 3.0 spec — always in sync with the running server.</p>\n<ul>\n<li><strong>Interactive Swagger UI:</strong> <a href=\"https://analyzer.00id.net/api/docs\" target=\"_blank\" rel=\"noopener noreferrer\">analyzer.00id.net/api/docs</a> — browse and test all 40+ endpoints</li>\n<li><strong>Machine-readable spec:</strong> <a href=\"https://analyzer.00id.net/api/spec\" target=\"_blank\" rel=\"noopener noreferrer\">analyzer.00id.net/api/spec</a> — import into Postman, Insomnia, or use for bot/integration development</li>\n</ul>\n<p>On your own instanc\n…</p>\n"
    },
    {
      "version": "v3.4.1",
      "name": "v3.4.1 — Server-Side Hop Resolution & Performance",
      "datetime": "2026-04-04T08:36:55Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.4.1",
      "prerelease": false,
      "notes": "## v3.4.1 — Server-Side Hop Resolution & Performance\n\n### ⭐ Headline: Resolved Paths\n\nHop prefixes are now resolved to full node identities **at ingest time** using a persisted neighbor affinity graph. No more guessing — the server knows which \"D6\" is which.\n\n- **Persisted neighbor graph** (`neighbor_edges` table) — builds automatically on first run, loads instantly on restart. 4-tier resolution: affinity → geo → GPS → first match.\n- **`resolved_path` on every observation** — full 64-char pubkeys stored alongside raw `path_json`. Ambiguous prefixes that the old client-side resolver got wrong are now correct.\n- **Frontend prefers server-resolved paths** — packets page, live map, Show Route, packet detail all use `resolved_path` with automatic fallback for old data.\n- **Zero-cost on subsequent startups** — edges and resolved paths persist in SQLite. First run does a one-time backfill.\n\n### 🚀 Performance\n\n- **Distance index rebuild debounced** — was triggering on every ingest cycle, now at most every 30s. Eliminates CPU hot loop on busy meshes.\n- **Neighbor graph build optimized** — cached `strings.ToLower`, cached JSON parsing via `sync.Once`, TTL bumped 60s → 5min. Cuts cold start time.\n- **O(n²) observation dedup → O(n)** — map-based replacement.\n- **O(n²) selection sort → sort.Slice** — standard library sort.\n- **Parallelized expanded group fetches** — hashIndex Map lookup instead of linear scan.\n- **Advert pubkey tracking incremental** — eliminates per-request JSON parsing.\n- **Rate-limited cache invalidation** — prevents 0% hit rate under sustained ingest.\n- **VCR replay chunked** — prevents UI freezes on large replays.\n\n### 🐛 Fixes\n\n- **`hasResolvedPath` flag race** — `detectSchema()` ran before column was added, causing full re-backfill on every restart. Fixed.\n- **`resolved_path` missing from grouped packets** — `groupByHash=true` response wasn't including resolved paths. Fixed.\n- **Memory leak in `pruneStaleNodes`** — `nodeActivity` map entries never cleaned up. Fixed.\n- **iOS tap-to-scroll broken** — scroll container restructured for status bar tap.\n- **Observer filter dropped groups** — grouped packets view lost groups when observer filter was active. Fixed.\n- **Null crash on ADVERT detail** — `pathHops` null guard added.\n- **Mobile filter dropdown** — CSS specificity prevented expansion. Fixed.\n- **Hash collision analysis** — now only counts repeaters, not all nodes.\n- **Virtual scroll height** — accounts for expanded group rows.\n\n### 🔧 Infra\n\n- CI build and deploy jobs pinned to `meshcore-vm` runner.\n- Deep linking rule added to AGENTS.md.\n\n### ⚠️ Known Issues / Heads Up\n\n- **First restart after upgrade takes ~4 minutes on large DBs** — The server builds the neighbor graph (~40s), then backfills `resolved_path` for all existing observations (~2 min), plus normal data load (~35s). With 1M+ observations, expect heavy CPU and SQLite locking during this window. MQTT ingest may disconnect and reconnect. **This is a one-time cost** — subsequent restarts load from SQLite in ~15s. Do not restart the server during this process or it will start over.\n\n### 📊 By the Numbers\n\n- 22 commits, 13 files changed\n- 1.1M observations backfilled with resolved paths\n- 408K neighbor edges persisted\n- Cold start: ~4 min first run → ~15s subsequent",
      "notesHtml": "<h2>v3.4.1 — Server-Side Hop Resolution &amp; Performance</h2>\n<h3>⭐ Headline: Resolved Paths</h3>\n<p>Hop prefixes are now resolved to full node identities <strong>at ingest time</strong> using a persisted neighbor affinity graph. No more guessing — the server knows which \"D6\" is which.</p>\n<ul>\n<li><strong>Persisted neighbor graph</strong> (<code>neighbor_edges</code> table) — builds automatically on first run, loads instantly on restart. 4-tier resolution: affinity → geo → GPS → first match.</li>\n<li><strong><code>resolved_path</code> on every observation</strong> — full 64-char pubkeys stored alongside raw <code>path_json</code>. Ambiguous prefixes that the old client-side resolver got wrong are now correct.</li>\n<li><strong>Frontend prefers server-resolved paths</strong> — packets page, live map, Show Route, packet detail all use <code>resolved_path</code> with automatic fallback for old data.</li>\n<li><strong>Zero-cost on subsequent startups</strong> — edges and resolved paths persist in SQLite. First run does a one-time backfill.</li>\n</ul>\n<h3>🚀 Performance</h3>\n<ul>\n<li><strong>Distance index rebuild debounced</strong> — was triggering on every ingest cycle, now at most every 30s. Eliminates CPU hot loop on busy meshes.</li>\n<li><strong>Neighbor graph build optimized</strong> — cached <code>strings.ToLower</code>, cached JSON parsing via <code>sync.Once</code>, TTL bumped 60s → 5min. Cuts cold start time.</li>\n<li><strong>O(n²) observation dedup → O(n)</strong> — map-based replacement.</li>\n<li><strong>O(n²) selection sort → sort.Slice</strong> — standard library sort.</li>\n<li><strong>Parallelized expanded group fetches</strong> — hashIndex Map lookup instead of linear scan.</li>\n<li><strong>Advert pubkey tracking incremental</strong> — eliminates per-request JSON parsing.</li>\n<li><strong>Rate-limited cache invalidation</strong> — prevents 0% hit rate under sustained ingest.</li>\n<li><strong>VCR replay chunked</strong> — prevents UI freezes on large replays.</li>\n</ul>\n<h3>🐛 Fixes</h3>\n<ul>\n<li><strong><code>hasResolvedPath</code> flag race</strong> — <code>detectSchema()</code> ran before column was added, causing full re-backfill on every restart. Fixed.</li>\n<li><strong><code>resolved_path</code> missing from grouped packets</strong> — <code>groupByHash=true</code> response wasn't including resolved paths. Fixed.</li>\n<li><strong>Memory leak in <code>pruneStaleNodes</code></strong> — <code>nodeActivity</code> map entries never cleaned up. Fixed.</li>\n<li><strong>iOS tap-to-scroll broken</strong> — scroll container restructured for status bar tap.</li>\n<li><strong>Observer filter dropped groups</strong> — grouped packets view lost groups when observer filter was active. Fixed.</li>\n<li><strong>Null crash on ADVERT detail</strong> — <code>pathHops</code> null guard added.</li>\n<li><strong>Mobile filter dropdown</strong> — CSS specificity prevented expansion. Fixed.</li>\n<li><strong>Hash collision analysis</strong> — now only counts repeaters, not all nodes.</li>\n<li><strong>Virtual scroll height</strong> — accounts for expanded group rows.</li>\n</ul>\n<h3>🔧 Infra</h3>\n<ul>\n<li>CI build and deploy jobs pinned to <code>meshcore-vm</code> runner.</li>\n<li>Deep linking rule added to AGENTS.md.</li>\n</ul>\n<h3>⚠️ Known Issues / Heads Up</h3>\n<ul>\n<li><strong>First restart after upgrade takes ~4 minutes on large DBs</strong> — The server builds the neighbor graph (~40s), then backfills <code>resolved_path</code> for all existing observations (~2 min), plus normal data load (~35s). With 1M+ observations, expect heavy CPU and SQLite locking during this window. MQTT ingest may disconnect and reconnect. <strong>This is a one-time cost</strong> — subsequent restarts load from SQLite in ~15s. Do not restart the server during this process or it will start over.</li>\n</ul>\n<h3>📊 By the Numbers</h3>\n<ul>\n<li>22 commits, 13 files changed</li>\n<li>1.1M observations backfilled with resolved paths</li>\n<li>408K neighbor edges persisted</li>\n<li>Cold start: ~4 min first run → ~15s subsequent</li>\n</ul>\n"
    },
    {
      "version": "v3.4.0",
      "name": "CoreScope v3.4.0 — Neighbor Affinity",
      "datetime": "2026-04-03T08:56:39Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.4.0",
      "prerelease": false,
      "notes": "# CoreScope v3.4 Release Notes\r\n\r\n**The neighbor affinity release.** CoreScope now understands how nodes relate to each other — not just that they exist, but how strongly they're connected. This powers smarter hop resolution, richer node detail pages, and a new graph visualization in analytics.\r\n\r\n---\r\n\r\n## 🎯 Features\r\n\r\n### Neighbor Affinity System (7 milestones)\r\nA complete neighbor relationship engine, from backend graph building to frontend visualization:\r\n\r\n- **Affinity graph builder** — computes neighbor relationships and connection strength from packet traffic (#507)\r\n- **Affinity API endpoints** — REST endpoints to query neighbor data (#508)\r\n- **Show Neighbors via affinity API** — the existing Show Neighbors feature now uses real affinity data instead of raw packet heuristics (#512, fixes #484)\r\n- **Affinity-aware hop resolution** — hop resolver uses neighbor affinity to pick better paths (#511)\r\n- **Node detail neighbors section** — dedicated neighbors panel on the node detail page (#510)\r\n- **Affinity debugging tools** — inspect and troubleshoot affinity calculations (#521)\r\n- **Neighbor graph visualization** — interactive neighbor graph in the analytics tab (#513)\r\n\r\n### Customizer v2\r\n- Event-driven state management replaces the old imperative approach — cleaner, more predictable theme/config updates (#503)\r\n\r\n---\r\n\r\n## 🐛 Bug Fixes\r\n\r\n- **Stale parsed cache on observation packets** — observation packets now correctly invalidate the JSON parse cache (#505)\r\n- **Null-guard rAF callbacks** — live page no longer crashes when `requestAnimationFrame` callbacks fire after cleanup (#506)\r\n- **Customizer v2 phantom overrides** — fixed phantom config entries, missing defaults, and stale dark mode state (#520)\r\n- **Neighbor affinity empty results** — fixed pubKey field name mismatch causing empty affinity graphs (#524)\r\n- **Home defaults in server theme** — server-side theme config now includes home page defaults (#526)\r\n- **Neighbor UI crash + dark mode** — fixed Show Neighbors crash and improved dark mode contrast (#527)\r\n- **Home page steps + FAQ** — both steps AND FAQ now render correctly on the home page (#529)\r\n\r\n---\r\n\r\n## ⚡ Performance\r\n\r\n- **Cached JSON.parse for packet data** — packet payloads are parsed once and cached, avoiding redundant `JSON.parse` calls on repeated access (#400)\r\n\r\n---\r\n\r\n## Known Limitations\r\n\r\n- **Affinity graph scales with traffic volume** — networks with very low packet rates may show weak or missing neighbor relationships until enough data accumulates\r\n- **Debugging tools are developer-facing** — the affinity debug panel (#521) is functional but not polished for end-user consumption\r\n- **Customizer v2 migration** — custom themes saved under v1 may need to be re-applied after upgrade",
      "notesHtml": "<h1>CoreScope v3.4 Release Notes</h1>\n<p><strong>The neighbor affinity release.</strong> CoreScope now understands how nodes relate to each other — not just that they exist, but how strongly they're connected. This powers smarter hop resolution, richer node detail pages, and a new graph visualization in analytics.</p>\n<hr />\n<h2>🎯 Features</h2>\n<h3>Neighbor Affinity System (7 milestones)</h3>\n<p>A complete neighbor relationship engine, from backend graph building to frontend visualization:</p>\n<ul>\n<li><strong>Affinity graph builder</strong> — computes neighbor relationships and connection strength from packet traffic (#507)</li>\n<li><strong>Affinity API endpoints</strong> — REST endpoints to query neighbor data (#508)</li>\n<li><strong>Show Neighbors via affinity API</strong> — the existing Show Neighbors feature now uses real affinity data instead of raw packet heuristics (#512, fixes #484)</li>\n<li><strong>Affinity-aware hop resolution</strong> — hop resolver uses neighbor affinity to pick better paths (#511)</li>\n<li><strong>Node detail neighbors section</strong> — dedicated neighbors panel on the node detail page (#510)</li>\n<li><strong>Affinity debugging tools</strong> — inspect and troubleshoot affinity calculations (#521)</li>\n<li><strong>Neighbor graph visualization</strong> — interactive neighbor graph in the analytics tab (#513)</li>\n</ul>\n<h3>Customizer v2</h3>\n<ul>\n<li>Event-driven state management replaces the old imperative approach — cleaner, more predictable theme/config updates (#503)</li>\n</ul>\n<hr />\n<h2>🐛 Bug Fixes</h2>\n<ul>\n<li><strong>Stale parsed cache on observation packets</strong> — observation packets now correctly invalidate the JSON parse cache (#505)</li>\n<li><strong>Null-guard rAF callbacks</strong> — live page no longer crashes when <code>requestAnimationFrame</code> callbacks fire after cleanup (#506)</li>\n<li><strong>Customizer v2 phantom overrides</strong> — fixed phantom config entries, missing defaults, and stale dark mode state (#520)</li>\n<li><strong>Neighbor affinity empty results</strong> — fixed pubKey field name mismatch causing empty affinity graphs (#524)</li>\n<li><strong>Home defaults in server theme</strong> — server-side theme config now includes home page defaults (#526)</li>\n<li><strong>Neighbor UI crash + dark mode</strong> — fixed Show Neighbors crash and improved dark mode contrast (#527)</li>\n<li><strong>Home page steps + FAQ</strong> — both steps AND FAQ now render correctly on the home page (#529)</li>\n</ul>\n<hr />\n<h2>⚡ Performance</h2>\n<ul>\n<li><strong>Cached JSON.parse for packet data</strong> — packet payloads are parsed once and cached, avoiding redundant <code>JSON.parse</code> calls on repeated access (#400)</li>\n</ul>\n<hr />\n<h2>Known Limitations</h2>\n<ul>\n<li><strong>Affinity graph scales with traffic volume</strong> — networks with very low packet rates may show weak or missing neighbor relationships until enough data accumulates</li>\n<li><strong>Debugging tools are developer-facing</strong> — the affinity debug panel (#521) is functional but not polished for end-user consumption</li>\n<li><strong>Customizer v2 migration</strong> — custom themes saved under v1 may need to be re-applied after upgrade</li>\n</ul>\n"
    },
    {
      "version": "v3.3.0",
      "name": "CoreScope v3.3.0 — Performance & Polish",
      "datetime": "2026-04-02T07:16:23Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.3.0",
      "prerelease": false,
      "notes": "# CoreScope v3.3.0 — Performance & Polish\n\n28 changes since v3.2.0. This release is all about making CoreScope faster, more accurate, and harder to break.\n\n## ⚡ Performance Overhaul\n\n- **Virtual scroll for packets table** — handles 10K+ packets without breaking a sweat (#402)\n- **Observer Map lookups** — O(1) instead of linear scans on every render (#468)\n- **requestAnimationFrame animations** — live page ditches setInterval, no more stacking when tab-switching (#470)\n- **Client-side My Nodes filter** — instant toggle, zero server round-trips (#401)\n- **Server-side collision analysis** — heavy computation moved off the browser (#415)\n- **In-place ADVERT upserts** — node updates without full page reload (#461)\n- **Targeted cache invalidation** — analytics refresh only what changed (#379)\n- **Faster /api/packets and /api/channels** — query optimizations for large stores (#328)\n\n## 🗺️ New Features\n\n- **Show Direct Neighbors** — click any node on the map to filter to its 1-hop neighbors (#480)\n- **Auto cache busters** — `__BUST__` placeholders replaced at startup, no more merge conflicts over timestamps (#481)\n- **Release tag pinning** — `manage.sh update v3.3.0` pins to exact versions (#456)\n\n## 🔧 Fixes\n\n- **Haversine distances** — hop distances now use proper great-circle math instead of flat-earth approximation (#478)\n- **Observer online status** — packet ingestion updates last_seen, fixing false \"offline\" flags (#479)\n- **Hash collision region filter** — analytics respects region selection (#477)\n- **Channel hash display** — shows hex (0x1A) instead of confusing decimal (#471)\n- **VCR timezone** — timeline and clock respect UTC/local toggle (#459)\n- **Graceful shutdown** — WAL checkpoint on container stop (#453)\n- **PerfStats data race** — mutex synchronization eliminates concurrent access bugs (#469)\n- **Multiple null-guard fixes** — no more crashes on navigate-away (#454, #462)\n- **Score/direction extraction** — MQTT fields properly parsed with unit stripping (#371)\n- **Config reset** — no more SITE_CONFIG contamination after reset (#460)\n- **Staging config** — always refreshes from prod (#467)\n\n## 📊 By the Numbers\n\n- 28 merged PRs\n- 8 performance improvements\n- 13 bug fixes\n- 3 new features\n- 2 CI/docs improvements\n- ~1,400 lines added across 23 files",
      "notesHtml": "<h1>CoreScope v3.3.0 — Performance &amp; Polish</h1>\n<p>28 changes since v3.2.0. This release is all about making CoreScope faster, more accurate, and harder to break.</p>\n<h2>⚡ Performance Overhaul</h2>\n<ul>\n<li><strong>Virtual scroll for packets table</strong> — handles 10K+ packets without breaking a sweat (#402)</li>\n<li><strong>Observer Map lookups</strong> — O(1) instead of linear scans on every render (#468)</li>\n<li><strong>requestAnimationFrame animations</strong> — live page ditches setInterval, no more stacking when tab-switching (#470)</li>\n<li><strong>Client-side My Nodes filter</strong> — instant toggle, zero server round-trips (#401)</li>\n<li><strong>Server-side collision analysis</strong> — heavy computation moved off the browser (#415)</li>\n<li><strong>In-place ADVERT upserts</strong> — node updates without full page reload (#461)</li>\n<li><strong>Targeted cache invalidation</strong> — analytics refresh only what changed (#379)</li>\n<li><strong>Faster /api/packets and /api/channels</strong> — query optimizations for large stores (#328)</li>\n</ul>\n<h2>🗺️ New Features</h2>\n<ul>\n<li><strong>Show Direct Neighbors</strong> — click any node on the map to filter to its 1-hop neighbors (#480)</li>\n<li><strong>Auto cache busters</strong> — <code>__BUST__</code> placeholders replaced at startup, no more merge conflicts over timestamps (#481)</li>\n<li><strong>Release tag pinning</strong> — <code>manage.sh update v3.3.0</code> pins to exact versions (#456)</li>\n</ul>\n<h2>🔧 Fixes</h2>\n<ul>\n<li><strong>Haversine distances</strong> — hop distances now use proper great-circle math instead of flat-earth approximation (#478)</li>\n<li><strong>Observer online status</strong> — packet ingestion updates last_seen, fixing false \"offline\" flags (#479)</li>\n<li><strong>Hash collision region filter</strong> — analytics respects region selection (#477)</li>\n<li><strong>Channel hash display</strong> — shows hex (0x1A) instead of confusing decimal (#471)</li>\n<li><strong>VCR timezone</strong> — timeline and clock respect UTC/local toggle (#459)</li>\n<li><strong>Graceful shutdown</strong> — WAL checkpoint on container stop (#453)</li>\n<li><strong>PerfStats data race</strong> — mutex synchronization eliminates concurrent access bugs (#469)</li>\n<li><strong>Multiple null-guard fixes</strong> — no more crashes on navigate-away (#454, #462)</li>\n<li><strong>Score/direction extraction</strong> — MQTT fields properly parsed with unit stripping (#371)</li>\n<li><strong>Config reset</strong> — no more SITE_CONFIG contamination after reset (#460)</li>\n<li><strong>Staging config</strong> — always refreshes from prod (#467)</li>\n</ul>\n<h2>📊 By the Numbers</h2>\n<ul>\n<li>28 merged PRs</li>\n<li>8 performance improvements</li>\n<li>13 bug fixes</li>\n<li>3 new features</li>\n<li>2 CI/docs improvements</li>\n<li>~1,400 lines added across 23 files</li>\n</ul>\n"
    },
    {
      "version": "v3.2.0",
      "name": "CoreScope v3.2.0 — Stability & Data Integrity",
      "datetime": "2026-04-01T06:40:19Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.2.0",
      "prerelease": false,
      "notes": "A stability and data integrity release — fixing critical crashes, broken observer data, and WebSocket storms, while adding transport code visibility, geo-filtering, and a fully responsive navigation bar.\n\n**15 commits · 35 files · +1,659 / -229 · 3 contributors · 9 issues closed**\n\n---\n\n## 🐛 Critical Fixes\n\n**Packets page crash on mobile** (#326) — `Number(null)` silently set the time window to \"All time\", loading 50K+ packets and crashing iOS Safari/Edge. Fixed with proper guard, 1K packet cap on mobile, and restricted time window options.\n\n**Observer metadata broken since Go migration** (#320, #321) — Battery, uptime, noise floor read at wrong nesting level; SNR/RSSI case-sensitive with no fallback. All fields now work with 10 regression tests.\n\n**Hash stats frozen on historical data** (#303) — Reconfigured repeaters now show updated hash size immediately instead of requiring dozens of adverts to shift the statistical mode.\n\n**WebSocket replaying entire database** (#349) — Corrupted DB caused the poller to start from ID 0, broadcasting 76K packets at ~2,000/sec. Poller now cross-checks against the in-memory store.\n\n## ✨ Features\n\n**Transport code badge** (#241) — \"T\" badge on packets using `T_FLOOD` / `T_DIRECT` routing — instant visibility into transport adoption across the mesh.\n\n**Geo-filter enforcement** (#215) — Server-side polygon boundary filtering, automatic DB pruning for out-of-bounds nodes, and a visual geofilter-builder tool.\n\n**Responsive navigation** (#322) — Hamburger menu at ≤1023px with drawer scroll, outside-click close, Escape key, and `aria-expanded`. Priority+ pattern on tablets (768–1023px): 5 primary tabs inline + \"More ▾\" dropdown for the rest.\n\n## 🎨 Improvements\n\n**Side pane contrast** (#334) — Card-style backgrounds on node detail side pane sections, fixing poor dark mode contrast.\n\n**WebSocket time filtering** — WS packets now respect the selected time window instead of showing data from days ago.\n\n**CI runner cleanup** (#333) — Automatic Docker image pruning after deploy. Runner was 100% full (18.8GB of dangling images).\n\n---\n\n**Full changelog:** [`v3.1.0...v3.2.0`](https://github.com/Kpa-clawbot/CoreScope/compare/v3.1.0...v3.2.0)",
      "notesHtml": "<p>A stability and data integrity release — fixing critical crashes, broken observer data, and WebSocket storms, while adding transport code visibility, geo-filtering, and a fully responsive navigation bar.</p>\n<p><strong>15 commits · 35 files · +1,659 / -229 · 3 contributors · 9 issues closed</strong></p>\n<hr />\n<h2>🐛 Critical Fixes</h2>\n<p><strong>Packets page crash on mobile</strong> (#326) — <code>Number(null)</code> silently set the time window to \"All time\", loading 50K+ packets and crashing iOS Safari/Edge. Fixed with proper guard, 1K packet cap on mobile, and restricted time window options.</p>\n<p><strong>Observer metadata broken since Go migration</strong> (#320, #321) — Battery, uptime, noise floor read at wrong nesting level; SNR/RSSI case-sensitive with no fallback. All fields now work with 10 regression tests.</p>\n<p><strong>Hash stats frozen on historical data</strong> (#303) — Reconfigured repeaters now show updated hash size immediately instead of requiring dozens of adverts to shift the statistical mode.</p>\n<p><strong>WebSocket replaying entire database</strong> (#349) — Corrupted DB caused the poller to start from ID 0, broadcasting 76K packets at ~2,000/sec. Poller now cross-checks against the in-memory store.</p>\n<h2>✨ Features</h2>\n<p><strong>Transport code badge</strong> (#241) — \"T\" badge on packets using <code>T_FLOOD</code> / <code>T_DIRECT</code> routing — instant visibility into transport adoption across the mesh.</p>\n<p><strong>Geo-filter enforcement</strong> (#215) — Server-side polygon boundary filtering, automatic DB pruning for out-of-bounds nodes, and a visual geofilter-builder tool.</p>\n<p><strong>Responsive navigation</strong> (#322) — Hamburger menu at ≤1023px with drawer scroll, outside-click close, Escape key, and <code>aria-expanded</code>. Priority+ pattern on tablets (768–1023px): 5 primary tabs inline + \"More ▾\" dropdown for the rest.</p>\n<h2>🎨 Improvements</h2>\n<p><strong>Side pane contrast</strong> (#334) — Card-style backgrounds on node detail side pane sections, fixing poor dark mode contrast.</p>\n<p><strong>WebSocket time filtering</strong> — WS packets now respect the selected time window instead of showing data from days ago.</p>\n<p><strong>CI runner cleanup</strong> (#333) — Automatic Docker image pruning after deploy. Runner was 100% full (18.8GB of dangling images).</p>\n<hr />\n<p><strong>Full changelog:</strong> <a href=\"https://github.com/Kpa-clawbot/CoreScope/compare/v3.1.0...v3.2.0\" target=\"_blank\" rel=\"noopener noreferrer\"><code>v3.1.0...v3.2.0</code></a></p>\n"
    },
    {
      "version": "v3.1.0",
      "name": "CoreScope v3.1.0",
      "datetime": "2026-03-31T08:01:07Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.1.0",
      "prerelease": false,
      "notes": "**152 commits · 240 files changed · 51K insertions / 55K deletions · 44 PRs merged · 7 contributors**\r\n\r\n## Highlights\r\n\r\n**Timestamp Controls** — Toggle between relative (\"3m ago\") and absolute timestamps across all pages. UTC/local timezone, format presets, admin-configurable defaults. New Display tab in customizer.\r\n\r\n**Channel Region Filtering** — Multi-region deployments no longer show cross-region message crosstalk. Server-side SQL prefilter + frontend WS guard.\r\n\r\n**Disable Internal Mosquitto** — `DISABLE_MOSQUITTO=true` in `.env` skips the built-in broker for users running their own.\r\n\r\n**Port Negotiation** — `manage.sh setup` detects port conflicts, suggests alternatives, persists choices to `.env`.\r\n\r\n**Packet Store Eviction** — In-memory packet store now has configurable memory limits with LRU eviction to prevent OOM on long-running instances.\r\n\r\n**Telemetry Decoding** — Battery voltage and temperature decoded from sensor adverts and displayed on node detail pages.\r\n\r\n**Observer Identity** — Go ingestor now persists model, firmware, client version, and radio from observer status messages.\r\n\r\n**Hash Size Analytics** — Dominant hash size computation (mode, not last-seen), multi-byte hash usage matrix with stats and tooltips, distribution by repeaters.\r\n\r\n**Performance** — O(n) slice prepend eliminated on packet ingest, parallel coverage collector (8min → 30-60s), packet store query optimizations.\r\n\r\n**Decoder Alignment** — Packet decoder aligned with MeshCore firmware spec, TRACE path hops parsed correctly.\r\n\r\n**manage.sh Overhaul** — Compose-only wrapper, `.env` as single source of truth, config migration from repo root to data directory, CRLF auto-fix, tilde expansion.\r\n\r\n**CI/CD** — Go server E2E tests replace Node.js, Playwright suite, `workflow_dispatch` for manual triggers, repo-wide LF normalization with blame history preservation.\r\n\r\n**Mobile Responsive** — Live bottom-sheet, perf layout, nodes column hiding for small screens.\r\n\r\n**Configurable Health Thresholds** — Node health thresholds now configurable in hours via config.json.\r\n\r\n**pprof Profiling** — Optional pprof endpoints controlled by `ENABLE_PPROF` env var for production debugging.\r\n\r\n## Breaking Changes\r\n\r\n- **Config location moved** — `config.json` now lives in the data directory (`~/meshcore-data/config.json`). `manage.sh` auto-migrates from repo root on `update`/`start`.\r\n- **Node.js backend fully removed** — all backend code is Go. If you were running the Node.js server directly, you must switch to the Docker image.\r\n\r\n## Known Issues\r\n\r\n- FAQ/home page customizer editing can cause steps to disappear — needs architectural fix (#284, #325)\r\n- Observer telemetry (battery, uptime, noise_floor) reads wrong JSON level (#320)\r\n- SNR/RSSI case-sensitive key lookup may miss lowercase variants (#321)\r\n- Repeater hash stats may not update after hash size change (#303)\r\n- VCR timeline ignores UTC/local timezone setting (#324)\r\n- Top nav bar controls invisible on narrow screens (#322)",
      "notesHtml": "<p><strong>152 commits · 240 files changed · 51K insertions / 55K deletions · 44 PRs merged · 7 contributors</strong></p>\n<h2>Highlights</h2>\n<p><strong>Timestamp Controls</strong> — Toggle between relative (\"3m ago\") and absolute timestamps across all pages. UTC/local timezone, format presets, admin-configurable defaults. New Display tab in customizer.</p>\n<p><strong>Channel Region Filtering</strong> — Multi-region deployments no longer show cross-region message crosstalk. Server-side SQL prefilter + frontend WS guard.</p>\n<p><strong>Disable Internal Mosquitto</strong> — <code>DISABLE_MOSQUITTO=true</code> in <code>.env</code> skips the built-in broker for users running their own.</p>\n<p><strong>Port Negotiation</strong> — <code>manage.sh setup</code> detects port conflicts, suggests alternatives, persists choices to <code>.env</code>.</p>\n<p><strong>Packet Store Eviction</strong> — In-memory packet store now has configurable memory limits with LRU eviction to prevent OOM on long-running instances.</p>\n<p><strong>Telemetry Decoding</strong> — Battery voltage and temperature decoded from sensor adverts and displayed on node detail pages.</p>\n<p><strong>Observer Identity</strong> — Go ingestor now persists model, firmware, client version, and radio from observer status messages.</p>\n<p><strong>Hash Size Analytics</strong> — Dominant hash size computation (mode, not last-seen), multi-byte hash usage matrix with stats and tooltips, distribution by repeaters.</p>\n<p><strong>Performance</strong> — O(n) slice prepend eliminated on packet ingest, parallel coverage collector (8min → 30-60s), packet store query optimizations.</p>\n<p><strong>Decoder Alignment</strong> — Packet decoder aligned with MeshCore firmware spec, TRACE path hops parsed correctly.</p>\n<p><strong>manage.sh Overhaul</strong> — Compose-only wrapper, <code>.env</code> as single source of truth, config migration from repo root to data directory, CRLF auto-fix, tilde expansion.</p>\n<p><strong>CI/CD</strong> — Go server E2E tests replace Node.js, Playwright suite, <code>workflow_dispatch</code> for manual triggers, repo-wide LF normalization with blame history preservation.</p>\n<p><strong>Mobile Responsive</strong> — Live bottom-sheet, perf layout, nodes column hiding for small screens.</p>\n<p><strong>Configurable Health Thresholds</strong> — Node health thresholds now configurable in hours via config.json.</p>\n<p><strong>pprof Profiling</strong> — Optional pprof endpoints controlled by <code>ENABLE_PPROF</code> env var for production debugging.</p>\n<h2>Breaking Changes</h2>\n<ul>\n<li><strong>Config location moved</strong> — <code>config.json</code> now lives in the data directory (<code>~/meshcore-data/config.json</code>). <code>manage.sh</code> auto-migrates from repo root on <code>update</code>/<code>start</code>.</li>\n<li><strong>Node.js backend fully removed</strong> — all backend code is Go. If you were running the Node.js server directly, you must switch to the Docker image.</li>\n</ul>\n<h2>Known Issues</h2>\n<ul>\n<li>FAQ/home page customizer editing can cause steps to disappear — needs architectural fix (#284, #325)</li>\n<li>Observer telemetry (battery, uptime, noise_floor) reads wrong JSON level (#320)</li>\n<li>SNR/RSSI case-sensitive key lookup may miss lowercase variants (#321)</li>\n<li>Repeater hash stats may not update after hash size change (#303)</li>\n<li>VCR timeline ignores UTC/local timezone setting (#324)</li>\n<li>Top nav bar controls invisible on narrow screens (#322)</li>\n</ul>\n"
    },
    {
      "version": "v3.0.0",
      "name": "v3.0.0 — Go Backend Rewrite",
      "datetime": "2026-03-28T08:56:09Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v3.0.0",
      "prerelease": false,
      "notes": "# v3.0.0 — The Go Rewrite\r\n\r\nMeshCore Analyzer is now powered by Go. The entire backend — MQTT ingestion, packet decoding, API server, WebSocket broadcast — has been rewritten from Node.js to Go. Same features, same UI, same database. Dramatically faster.\r\n\r\nThis is the biggest change in the project's history. Over 200 commits, 58 issues closed, and a ground-up reimplementation that delivers real, measurable performance gains on every endpoint.\r\n\r\n---\r\n\r\n## ⚡ Performance\r\n\r\nThese are real numbers from production with 56K+ packets:\r\n\r\n| Endpoint | Node.js | Go |\r\n|----------|---------|-----|\r\n| Packet queries | 30-100ms | **sub-millisecond** (in-memory store) |\r\n| GroupByHash | 437ms (9s before store) | **97ms** |\r\n| Analytics (RF, topology, distance) | 1-8 seconds | **all under 100ms** |\r\n| Node health calculation | 13 seconds | **instant** (precomputed) |\r\n| Server startup (56K packets) | ~9 seconds | **< 1 second** |\r\n| Memory (56K packets) | ~1.3 GB | **~300 MB** |\r\n\r\nThe Go server loads all packets into an in-memory store at startup and serves queries directly from RAM. Analytics are precomputed at ingest time — no more scanning the full packet table on every request. TTL caches protect expensive aggregations. The result: every page in the UI feels instant.\r\n\r\n---\r\n\r\n## 🆕 New Features\r\n\r\n### Protobuf API Contract\r\n10 `.proto` files define the exact shape of all 40+ API endpoints and WebSocket messages. Golden fixture tests ensure the Go server matches the Node.js response format byte-for-byte. API drift is caught in CI before it reaches production.\r\n\r\n### Go Runtime Metrics\r\nThe performance page now shows Go-specific runtime stats when connected to a Go backend: goroutine count, heap allocation, GC pause percentiles, and memory breakdown. The engine badge in the stats bar shows **[go]** or **[node]** so you always know which backend you're running.\r\n\r\n### Build Identity\r\nEvery API response from `/api/stats` and `/api/health` now includes `engine`, `version`, `commit`, and `buildTime` fields. The stats bar in the UI shows the commit hash as a clickable link to the exact source.\r\n\r\n### Observer Packet Comparison (#129)\r\nNew `#/compare` page lets you compare what different observers saw for the same packet — side-by-side diffs of paths, timestamps, and signal data.\r\n\r\n### Auto-Updating Nodes List\r\nThe Nodes tab now updates in real-time when ADVERT packets arrive via WebSocket. No more manual refresh to see new nodes.\r\n\r\n### Channel Improvements\r\n- Channel hash displayed for undecrypted GRP_TXT messages — you can see *which* channel even without the key\r\n- Sortable channels table with persistent column sort preferences\r\n- Garbage decryption detection — wrong keys no longer produce garbled \"decrypted\" text\r\n- AES-128-CTR channel decryption natively in Go\r\n\r\n### Node Pruning (#202)\r\nNodes past the retention window are automatically moved to an `inactive_nodes` table instead of polluting the active node list. Pruning runs hourly.\r\n\r\n### Correct Advert Counts\r\nAdvert counts now reflect unique transmissions, not total observations. A packet seen by 8 observers counts as 1 advert, not 8.\r\n\r\n---\r\n\r\n## 🐛 Bug Fixes\r\n\r\n- **Phantom nodes from hop prefixes** (#133) — `autoLearnHopNodes` no longer creates fake nodes from 1-byte repeater IDs. Active node counts, live page counter, and topology analytics all filtered to real nodes only.\r\n- **Offline nodes on map** (#126) — ambiguous hop prefixes excluded from path-seen tracking. Stale nodes dim on the live map instead of disappearing.\r\n- **Disappearing live map nodes** (#130) — stale nodes are dimmed, not removed, preventing the jarring vanish-and-reappear cycle.\r\n- **packetsLastHour always zero** (#182) — early `break` in observer loop prevented counting; fixed across all observers.\r\n- **Corrupted packet decoder crash** (#183) — bounds check on path hops prevents buffer overrun on malformed packets.\r\n- **Node detail rendering crashes** (#190) — `Number()` casts and `Array.isArray` guards \n…",
      "notesHtml": "<h1>v3.0.0 — The Go Rewrite</h1>\n<p>MeshCore Analyzer is now powered by Go. The entire backend — MQTT ingestion, packet decoding, API server, WebSocket broadcast — has been rewritten from Node.js to Go. Same features, same UI, same database. Dramatically faster.</p>\n<p>This is the biggest change in the project's history. Over 200 commits, 58 issues closed, and a ground-up reimplementation that delivers real, measurable performance gains on every endpoint.</p>\n<hr />\n<h2>⚡ Performance</h2>\n<p>These are real numbers from production with 56K+ packets:</p>\n<table>\n<thead>\n<tr>\n<th>Endpoint</th>\n<th>Node.js</th>\n<th>Go</th>\n</tr>\n</thead>\n<tbody><tr>\n<td>Packet queries</td>\n<td>30-100ms</td>\n<td><strong>sub-millisecond</strong> (in-memory store)</td>\n</tr>\n<tr>\n<td>GroupByHash</td>\n<td>437ms (9s before store)</td>\n<td><strong>97ms</strong></td>\n</tr>\n<tr>\n<td>Analytics (RF, topology, distance)</td>\n<td>1-8 seconds</td>\n<td><strong>all under 100ms</strong></td>\n</tr>\n<tr>\n<td>Node health calculation</td>\n<td>13 seconds</td>\n<td><strong>instant</strong> (precomputed)</td>\n</tr>\n<tr>\n<td>Server startup (56K packets)</td>\n<td>~9 seconds</td>\n<td><strong>&lt; 1 second</strong></td>\n</tr>\n<tr>\n<td>Memory (56K packets)</td>\n<td>~1.3 GB</td>\n<td><strong>~300 MB</strong></td>\n</tr>\n</tbody></table>\n<p>The Go server loads all packets into an in-memory store at startup and serves queries directly from RAM. Analytics are precomputed at ingest time — no more scanning the full packet table on every request. TTL caches protect expensive aggregations. The result: every page in the UI feels instant.</p>\n<hr />\n<h2>🆕 New Features</h2>\n<h3>Protobuf API Contract</h3>\n<p>10 <code>.proto</code> files define the exact shape of all 40+ API endpoints and WebSocket messages. Golden fixture tests ensure the Go server matches the Node.js response format byte-for-byte. API drift is caught in CI before it reaches production.</p>\n<h3>Go Runtime Metrics</h3>\n<p>The performance page now shows Go-specific runtime stats when connected to a Go backend: goroutine count, heap allocation, GC pause percentiles, and memory breakdown. The engine badge in the stats bar shows <strong>[go]</strong> or <strong>[node]</strong> so you always know which backend you're running.</p>\n<h3>Build Identity</h3>\n<p>Every API response from <code>/api/stats</code> and <code>/api/health</code> now includes <code>engine</code>, <code>version</code>, <code>commit</code>, and <code>buildTime</code> fields. The stats bar in the UI shows the commit hash as a clickable link to the exact source.</p>\n<h3>Observer Packet Comparison (#129)</h3>\n<p>New <code>#/compare</code> page lets you compare what different observers saw for the same packet — side-by-side diffs of paths, timestamps, and signal data.</p>\n<h3>Auto-Updating Nodes List</h3>\n<p>The Nodes tab now updates in real-time when ADVERT packets arrive via WebSocket. No more manual refresh to see new nodes.</p>\n<h3>Channel Improvements</h3>\n<ul>\n<li>Channel hash displayed for undecrypted GRP_TXT messages — you can see <em>which</em> channel even without the key</li>\n<li>Sortable channels table with persistent column sort preferences</li>\n<li>Garbage decryption detection — wrong keys no longer produce garbled \"decrypted\" text</li>\n<li>AES-128-CTR channel decryption natively in Go</li>\n</ul>\n<h3>Node Pruning (#202)</h3>\n<p>Nodes past the retention window are automatically moved to an <code>inactive_nodes</code> table instead of polluting the active node list. Pruning runs hourly.</p>\n<h3>Correct Advert Counts</h3>\n<p>Advert counts now reflect unique transmissions, not total observations. A packet seen by 8 observers counts as 1 advert, not 8.</p>\n<hr />\n<h2>🐛 Bug Fixes</h2>\n<ul>\n<li><strong>Phantom nodes from hop prefixes</strong> (#133) — <code>autoLearnHopNodes</code> no longer creates fake nodes from 1-byte repeater IDs. Active node counts, live page counter, and topology analytics all filtered to real nodes only.</li>\n<li><strong>Offline nodes on map</strong> (#126) — ambiguous hop prefixes excluded from path-seen tracking. Stale nodes dim on the live map instead of disappearing.</li>\n<li><strong>Disappearing live map nodes</strong> (#130) — stale nodes are dimmed, not removed, preventing the jarring vanish-and-reappear cycle.</li>\n<li><strong>packetsLastHour always zero</strong> (#182) — early <code>break</code> in observer loop prevented counting; fixed across all observers.</li>\n<li><strong>Corrupted packet decoder crash</strong> (#183) — bounds check on path hops prevents buffer overrun on malformed packets.</li>\n<li><strong>Node detail rendering crashes</strong> (#190) — <code>Number()</code> casts and <code>Array.isArray</code> guards \n…</li>\n</ul>\n"
    },
    {
      "version": "v2.9.0",
      "name": "v2.9.0 — Database Optimization & Developer Experience",
      "datetime": "2026-03-25T22:50:49Z",
      "url": "https://github.com/Kpa-clawbot/CoreScope/releases/tag/v2.9.0",
      "prerelease": false,
      "notes": "## 🗜️ Database Optimization\n\nThe observations table has been completely rearchitected for efficiency. Existing instances **migrate automatically on startup** — no manual action needed.\n\n- **70% smaller database** — 478MB → 141MB on a real-world instance with 950K observations\n- Observer IDs stored as integer foreign keys instead of 64-char hex strings repeated per row\n- Redundant columns removed (`hash`, `observer_name`, `created_at`)\n- Timestamps stored as epoch integers instead of ISO strings\n- In-memory dedup Set prevents expensive unique index lookups during ingestion\n\n### Migration Safety\n- Timestamped backup created automatically before migration (never overwrites previous backups)\n- Detects already-migrated databases (safe to restart mid-migration)\n- Falls back gracefully if migration fails — original data preserved\n- `packets_v` view maintains full backward compatibility — no frontend changes needed\n\n### WAL Management\n- Daily `TRUNCATE` checkpoint at 2:00 AM UTC reclaims WAL file space\n- `VACUUM` + `TRUNCATE` checkpoint runs after migration completes\n\n## 📊 SQLite Observability\n\nNew section in the Performance Dashboard (`#/perf`) showing:\n- DB file size, WAL size, freelist (wasted space)\n- Row counts for all tables\n- WAL busy pages — shows if checkpointing is keeping up\n- Color-coded thresholds (green/yellow/red)\n\n## 🐛 Bug Fixes\n\n- **`disambiguateHops`**: Restored `known` field that was dropped during a refactor — hop resolution in analytics now correctly reports whether a prefix was resolved\n- **E2E tests**: Fixed channel message queries for `#hashtag` channels (missing URL encoding)\n- **MQTT in tests**: Tests no longer connect to real MQTT brokers — `NODE_ENV=test` skips MQTT setup\n\n## 🏗️ Setup & Operations\n\n- `manage.sh` — new idempotent setup wizard for Docker deployments\n- Backup/restore now includes config, Caddyfile, and theme\n- Deployment guide rewritten for beginners with Mermaid diagrams\n- Live page packet rendering unified into `renderPacketTree()`\n\n## 🧪 Tests\n\n839 tests across 9 suites, 0 failures:\n- 30 new migration tests (schema upgrade, idempotency, backup safety, crash recovery)\n- 19 new v3 schema tests (ingestion, dedup, view compatibility)\n- All pre-existing test suites updated and passing",
      "notesHtml": "<h2>🗜️ Database Optimization</h2>\n<p>The observations table has been completely rearchitected for efficiency. Existing instances <strong>migrate automatically on startup</strong> — no manual action needed.</p>\n<ul>\n<li><strong>70% smaller database</strong> — 478MB → 141MB on a real-world instance with 950K observations</li>\n<li>Observer IDs stored as integer foreign keys instead of 64-char hex strings repeated per row</li>\n<li>Redundant columns removed (<code>hash</code>, <code>observer_name</code>, <code>created_at</code>)</li>\n<li>Timestamps stored as epoch integers instead of ISO strings</li>\n<li>In-memory dedup Set prevents expensive unique index lookups during ingestion</li>\n</ul>\n<h3>Migration Safety</h3>\n<ul>\n<li>Timestamped backup created automatically before migration (never overwrites previous backups)</li>\n<li>Detects already-migrated databases (safe to restart mid-migration)</li>\n<li>Falls back gracefully if migration fails — original data preserved</li>\n<li><code>packets_v</code> view maintains full backward compatibility — no frontend changes needed</li>\n</ul>\n<h3>WAL Management</h3>\n<ul>\n<li>Daily <code>TRUNCATE</code> checkpoint at 2:00 AM UTC reclaims WAL file space</li>\n<li><code>VACUUM</code> + <code>TRUNCATE</code> checkpoint runs after migration completes</li>\n</ul>\n<h2>📊 SQLite Observability</h2>\n<p>New section in the Performance Dashboard (<code>#/perf</code>) showing:</p>\n<ul>\n<li>DB file size, WAL size, freelist (wasted space)</li>\n<li>Row counts for all tables</li>\n<li>WAL busy pages — shows if checkpointing is keeping up</li>\n<li>Color-coded thresholds (green/yellow/red)</li>\n</ul>\n<h2>🐛 Bug Fixes</h2>\n<ul>\n<li><strong><code>disambiguateHops</code></strong>: Restored <code>known</code> field that was dropped during a refactor — hop resolution in analytics now correctly reports whether a prefix was resolved</li>\n<li><strong>E2E tests</strong>: Fixed channel message queries for <code>#hashtag</code> channels (missing URL encoding)</li>\n<li><strong>MQTT in tests</strong>: Tests no longer connect to real MQTT brokers — <code>NODE_ENV=test</code> skips MQTT setup</li>\n</ul>\n<h2>🏗️ Setup &amp; Operations</h2>\n<ul>\n<li><code>manage.sh</code> — new idempotent setup wizard for Docker deployments</li>\n<li>Backup/restore now includes config, Caddyfile, and theme</li>\n<li>Deployment guide rewritten for beginners with Mermaid diagrams</li>\n<li>Live page packet rendering unified into <code>renderPacketTree()</code></li>\n</ul>\n<h2>🧪 Tests</h2>\n<p>839 tests across 9 suites, 0 failures:</p>\n<ul>\n<li>30 new migration tests (schema upgrade, idempotency, backup safety, crash recovery)</li>\n<li>19 new v3 schema tests (ingestion, dedup, view compatibility)</li>\n<li>All pre-existing test suites updated and passing</li>\n</ul>\n"
    }
  ],
  "changelogSource": "github",
  "changelogUpdatedAt": "2026-06-23T18:52:33.442Z"
}
