- Compute hash_size from ADVERT packets in /api/nodes response
- Show colored rectangle markers with hash size (e.g. '2B') for repeaters
- Add 'Hash size labels' toggle in map controls (default ON, saved to localStorage)
- Non-repeater markers unchanged
After dedup migration, packet IDs from the legacy 'packets' table differ
from transmission IDs in the 'transmissions' table. URLs using numeric IDs
became invalid after restart when _loadNormalized() assigned different IDs.
Changes:
- All packet URLs now use 16-char hex hashes instead of numeric IDs
(#/packets/HASH instead of #/packet/ID)
- selectPacket() accepts hash parameter, uses hash-based URLs
- Copy Link generates hash-based URLs
- Search results link to hash-based URLs
- /api/packets/:id endpoint accepts both numeric IDs and 16-char hashes
- insert() now calls insertTransmission() to get stable transmission IDs
- Added db.getTransmission() for direct transmission table lookup
- Removed redundant byTransmission map (identical to byHash)
- All byTransmission references replaced with byHash
After dedup, table rows have transmission IDs but getById() maps
observation IDs. Added byTxId index so /api/packets/:id resolves
both observation and transmission IDs correctly.
After dedup migration, transmission IDs != old packet IDs.
Hash-based links (#/packets/HASH) are stable across the migration.
Affected: node detail, channel messages, live page packet cards.
- API returns observer_name + path_json per observation
- Timeline shows resolved names with links to #/observers/<id>
- Path Visualization replaced with SVG graph showing all observed paths
- Removed redundant Observer Details table (same data as timeline)
- Full Detail links to #/nodes/<pubkey> (was #/nodes?selected= which just showed list)
- Heard By observer names link to #/observers/<id>
- Recent Packets link to #/packets?hash=<hash>
- packets.js: Show observation_count badge (👁 N) on grouped rows
- nodes.js: Use totalTransmissions (fallback totalPackets), show observation badges on recent packets
- home.js: Use totalTransmissions for network stats
- node-analytics.js: Use totalTransmissions for throughput display
- analytics.js: Use totalTransmissions for overview stats and node rankings
- live.js: Use totalTransmissions in node detail, show observation badges in feed and recent packets
- style.css: Add .badge-obs style for observation count badges
- index.html: Bump cache busters on all changed JS/CSS files
All changes have backward compat fallbacks to totalPackets.
- GET /api/packets: returns transmissions with observation_count, strip
observations[] by default (use ?expand=observations to include)
- GET /api/packets/🆔 includes observation_count and observations[]
- GET /api/nodes/:pubkey/health: stats.totalTransmissions + totalObservations
(totalPackets kept for backward compat)
- GET /api/nodes/bulk-health: same transmission/observation split
- WebSocket broadcast: includes observation_count
- db.js getStats(): adds totalTransmissions count
- All backward-compatible: old field names preserved alongside new ones
- Add transmissions and observations schema to db.js init
- Add insertTransmission() function: upsert transmission by hash,
always insert observation row
- All 6 pktStore.insert() call sites in server.js now also call
db.insertTransmission() with try/catch (non-fatal on error)
- packet-store.js: add byTransmission Map index (hash → transmission
with observations array) for future M3 query migration
- Existing insertPacket() and all read paths unchanged
Creates transmissions and observations tables from existing packets table.
- Groups packets by hash → 1 transmission per unique hash
- Creates 1 observation per original packet row with FK to transmission
- Idempotent: drops and recreates new tables on each run
- Does NOT modify the original packets table
- Prints stats and verifies counts match
Tested on test DB: 33813 packets → 11530 transmissions (2.93x dedup ratio)
- Click any node marker to see name, role, status, location, stats
- Heard By observers and recent packets shown
- Links to full node detail and analytics pages
- Slide-in panel from right with blur background, matches live page style
- Uses shared ROLE_COLORS and HEALTH_THRESHOLDS
- Observer ID is uppercase, node pubkey is lowercase — added COLLATE NOCASE
- New /api/config/regions endpoint merges config regions + observed IATAs
- map.js and packets.js fetch regions from API instead of hardcoded maps
- Removed dead 'MQTT Connected Only' checkbox (never worked)
- Added 'observer' role type with purple star marker
- Observer locations computed from average of nodes they've seen
- Observer popup with name, IATA, packets, link to detail page
- Role filter checkbox includes observers with count
The div-based spark bar was always getting crushed to 0px by
table-layout:auto + max-width:0 on td. Inline spans with fixed
width survive because they participate in text flow, not block layout.
Client was matching field names that only exist on ADVERTs. Now sends
pubkeys to server, which uses findPacketsForNode() (byNode index +
text search) to find ALL packet types referencing those nodes.
Lincomatic MQTT packets include origin field with friendly name but
we were only using it in status handler. Now both packet and observer
records get the name.
Scans DB for known #channel names, derives 16-byte AES keys
algorithmically. No need to manually add hashtag channels to config.
Private channels still need manual config.
RF analytics filtered on snr!=null, showing only 3 packets when most
lincomatic data has no SNR. Now shows total packets prominently and
signal-data count as a separate stat.