mirror of
https://github.com/Kpa-clawbot/meshcore-analyzer.git
synced 2026-09-12 03:45:38 +00:00
## What
Adds an **Export JSON** button to the Nodes topbar that downloads the
currently visible node list as a MeshCore companion-app config file:
```json
{
"contacts": [
{
"type": 2,
"name": "BE-MGU-RP03 | ON7YT",
"custom_name": null,
"public_key": "7a7a37d4819fb27440ed1439ca7d281fdecceb83b27e61a69745feb004d726a4",
"flags": 0,
"latitude": "51.07307",
"longitude": "5.5796",
"last_advert": 1786545431,
"last_modified": 1786545431,
"out_path_list": null
}
]
}
```
That is the exact shape the companion app itself writes, so the file
imports straight back into a companion app as contacts. The practical
use case is per-area: pick an area in the area filter, export, and hand
someone the repeaters for that region instead of having them wait for
adverts.
## Scope of the export
WYSIWYG — the button exports the rows the table is showing, in the
table's current order, so area, region, role tab, search, last-heard and
status filters all carry over. The button label shows how many contacts
the file will contain and is disabled when that count is zero.
## Field mapping
| JSON field | Source | Notes |
|---|---|---|
| `type` | `node.role` | repeater→2, companion→1, room→3, sensor→4;
unknown/empty→1 |
| `name` | `node.name` | unchanged, emoji included |
| `custom_name` | — | always `null` |
| `public_key` | `node.public_key` | full 64-hex |
| `flags` | — | always `0` |
| `latitude` / `longitude` | `node.lat` / `node.lon` | stringified, as
the format expects |
| `last_advert` | `node.last_seen` | RFC3339 → unix seconds |
| `last_modified` | mirrors `last_advert` | no separate source exists |
| `out_path_list` | — | always `null`; the companion app discovers
routes itself |
Nodes are skipped when they have no name, a pubkey shorter than 64 hex
chars, or no usable position (missing, non-numeric, or null island).
Filename: `corescope_nodes_<area|all>_YYYY-MM-DD-HHMMSS.json`.
## Shape of the change
The mapping lives in a self-contained `public/nodes-export.js`
(`window.NodesExport.buildContacts/filename/download`); `nodes.js` only
gains the button markup, a click handler and a small
`updateExportBtn()`. No backend change, no new API call — the export
reuses the already-fetched node list, so there is nothing per-node to
fetch.
## Tests
- `test-nodes-export.js` — field mapping and key order, role→type table,
skip rules, order preservation, filename format (added to
`test-all.sh`).
- `test-nodes-export-wiring.js` — `index.html` loads the module before
`nodes.js`; the button lives in the topbar and hands the filtered
`nodes` array plus `AreaFilter.getSelected()` to
`NodesExport.download()` (added to `test-all.sh`).
- `test-nodes-export-e2e.js` — Playwright: downloads the file, validates
the JSON shape per contact, asserts the button count matches the file,
and that narrowing the search shrinks the export set.
## Browser validation
Deployed to staging and checked in Chromium:
- Desktop 1400×900 — button renders at the right of the topbar next to
the count pills, `Export JSON (1962)`.
- Mobile 390×844 — topbar stacks, button visible, no horizontal overflow
(`scrollWidth == clientWidth`).
- E2E against staging: 1761 contacts exported,
`corescope_nodes_all_2026-08-12-164349.json`, shape validated, search
narrowing confirmed.
- With the `BE-LIM` area filter active: 44 contacts,
`corescope_nodes_BE-LIM_2026-08-12-164445.json`, type histogram `{1: 1,
2: 42, 3: 1}`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
79 lines
2.9 KiB
Bash
Executable File
79 lines
2.9 KiB
Bash
Executable File
#!/bin/sh
|
|
# Run all tests with coverage
|
|
set -e
|
|
|
|
echo "═══════════════════════════════════════"
|
|
echo " CoreScope — Test Suite"
|
|
echo "═══════════════════════════════════════"
|
|
echo ""
|
|
|
|
# Unit tests (deterministic, fast)
|
|
echo "── Unit Tests ──"
|
|
node test-packet-filter.js
|
|
node test-packet-filter-ux.js
|
|
node test-aging.js
|
|
node test-issue-1065-gesture-hints-gates.js
|
|
node test-frontend-helpers.js
|
|
node test-fetch-all-nodes-pagination.js
|
|
node test-my-repeaters-dashboard.js
|
|
node test-repeater-metric-scatter.js
|
|
node test-top-routes-overlay.js
|
|
node test-url-state.js
|
|
node test-perf-go-runtime.js
|
|
node test-channel-psk-ux.js
|
|
node test-channel-sidebar-layout.js
|
|
node test-channel-fluid-layout.js
|
|
node test-channel-modal-ux.js
|
|
node test-channel-decrypt-insecure-context.js
|
|
node test-channel-qr.js
|
|
node test-channel-qr-wiring.js
|
|
node test-channel-issue-1087.js
|
|
node test-issue-1409-no-encrypted-flood.js
|
|
node test-analytics-channels-integration.js
|
|
node test-observers-headings.js
|
|
node test-issue-1789-observer-firmware-cols.js
|
|
node test-issue-1648-m1-emoji-scan.js
|
|
node test-issue-1648-m2-emoji-scan.js
|
|
node test-issue-1648-m3-emoji-scan.js
|
|
node test-issue-1648-m6-final-sweep.js
|
|
node test-issue-1648-m6-lint-self.js
|
|
node test-issue-1890-og-url.js
|
|
node test-traces.js
|
|
node test-live-multibyte-filter.js
|
|
|
|
# #1418 — route-view v2 (Tufte) coverage
|
|
node test-issue-1418-raw-hex-extraction.js
|
|
node test-issue-1418-edge-weights.js
|
|
node test-issue-1418-cb-preset-ramp.js
|
|
node test-issue-1418-spider-fan.js
|
|
node test-issue-1418-deeplink-hops-channels.js
|
|
node test-issue-1418-polish-review.js
|
|
node test-issue-1420-tile-providers.js
|
|
node test-issue-1614-tile-url-function.js
|
|
node test-issue-1438-marker-css-vars.js
|
|
node test-issue-1438-customizer-mcrole.js
|
|
node test-issue-1446-cb-preset-cascade.js
|
|
node test-issue-1380-cb-sim-overlay.js
|
|
node test-issue-1380-cb-reset-button.js
|
|
node test-issue-1450-logo-aspect.js
|
|
node test-issue-1454-channels-toggle.js
|
|
node test-issue-1456-score-labels.js
|
|
|
|
# #1461 mobile UX overhaul + #1470 node-detail tile helper (#1468 covered by E2E)
|
|
node test-issue-1461-mobile-page-actions.js
|
|
node test-issue-1470-node-tile-helper.js
|
|
node test-issue-1485-live-anim-z.js
|
|
node test-issue-1532-live-fullscreen.js
|
|
node test-naive-banner-tone.js
|
|
node test-issue-1473-reserved-prefixes.js
|
|
node test-issue-1473-prefix-generator.js
|
|
node test-issue-1770-mobile-row-clamp.js
|
|
node test-issue-1849-trace-hashbytes.js
|
|
node test-nodes-export.js
|
|
node test-nodes-export-wiring.js
|
|
|
|
echo ""
|
|
echo "═══════════════════════════════════════"
|
|
echo " All tests passed"
|
|
echo "═══════════════════════════════════════"
|