From c686ae3f37e6e506059e84db248aa3731e35610f Mon Sep 17 00:00:00 2001 From: dborup Date: Fri, 17 Jul 2026 14:20:19 +0200 Subject: [PATCH] feat: surface transport scope on channel messages Adds scope_name to GetChannelMessages (both SQLite and in-memory store paths) and to the general packet response shape, then renders it as "Scope: " in the channel chat meta line so operators can see which region scope a message was transported under. --- cmd/server/db.go | 16 +++++++++++++--- cmd/server/store.go | 2 ++ cmd/server/types.go | 1 + public/channels.js | 3 +++ 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/cmd/server/db.go b/cmd/server/db.go index f2693894..4226c5fe 100644 --- a/cmd/server/db.go +++ b/cmd/server/db.go @@ -1856,10 +1856,14 @@ func (db *DB) GetChannelMessages(channelHash string, limit, offset int, region . idPlaceholders[i] = "?" obsArgs[i] = id } + scopeCol := "" + if db.hasScopeName { + scopeCol = ", t.scope_name" + } var obsSQL string if db.isV3 { obsSQL = `SELECT o.id, t.id, t.hash, t.decoded_json, t.first_seen, - obs.id, obs.name, o.snr, o.path_json, o.timestamp + obs.id, obs.name, o.snr, o.path_json, o.timestamp` + scopeCol + ` FROM observations o JOIN transmissions t ON t.id = o.transmission_id LEFT JOIN observers obs ON obs.rowid = o.observer_idx @@ -1867,7 +1871,7 @@ func (db *DB) GetChannelMessages(channelHash string, limit, offset int, region . ORDER BY o.id ASC` } else { obsSQL = `SELECT o.id, t.id, t.hash, t.decoded_json, t.first_seen, - o.observer_id, o.observer_name, o.snr, o.path_json, o.timestamp + o.observer_id, o.observer_name, o.snr, o.path_json, o.timestamp` + scopeCol + ` FROM observations o JOIN transmissions t ON t.id = o.transmission_id WHERE t.id IN (` + strings.Join(idPlaceholders, ",") + `) @@ -1892,7 +1896,12 @@ func (db *DB) GetChannelMessages(channelHash string, limit, offset int, region . var pktHash, dj, fs, obsID, obsName, pathJSON sql.NullString var snr sql.NullFloat64 var obsTs sql.NullInt64 - rows.Scan(&pktID, &txID, &pktHash, &dj, &fs, &obsID, &obsName, &snr, &pathJSON, &obsTs) + var scopeName sql.NullString + scanArgs := []interface{}{&pktID, &txID, &pktHash, &dj, &fs, &obsID, &obsName, &snr, &pathJSON, &obsTs} + if db.hasScopeName { + scanArgs = append(scanArgs, &scopeName) + } + rows.Scan(scanArgs...) if !dj.Valid { continue } @@ -1943,6 +1952,7 @@ func (db *DB) GetChannelMessages(channelHash string, limit, offset int, region . "observers": []string{}, "hops": hops, "snr": nullFloat(snr), + "scope": nullStr(scopeName), }, Repeats: 1, } diff --git a/cmd/server/store.go b/cmd/server/store.go index bfed9374..8707e711 100644 --- a/cmd/server/store.go +++ b/cmd/server/store.go @@ -3843,6 +3843,7 @@ func txToMap(tx *StoreTx, includeObservations ...bool) map[string]interface{} { "rssi": floatPtrOrNil(tx.RSSI), "path_json": strOrNil(tx.PathJSON), "direction": strOrNil(tx.Direction), + "scope_name": strOrNil(tx.ScopeName), } // Include parsed path array to match Node.js output shape if hops := txGetParsedPath(tx); len(hops) > 0 { @@ -5515,6 +5516,7 @@ func (s *PacketStore) GetChannelMessages(channelHash string, limit, offset int, "observers": observers, "hops": hops, "snr": snrVal, + "scope": strOrNil(tx.ScopeName), }, Repeats: 1, Observers: observers, diff --git a/cmd/server/types.go b/cmd/server/types.go index 88b36f31..54738397 100644 --- a/cmd/server/types.go +++ b/cmd/server/types.go @@ -905,6 +905,7 @@ type ChannelMessageResp struct { Observers []string `json:"observers"` Hops int `json:"hops"` SNR interface{} `json:"snr"` + Scope interface{} `json:"scope"` } type ChannelMessagesResponse struct { diff --git a/public/channels.js b/public/channels.js index b4688932..92b9586b 100644 --- a/public/channels.js +++ b/public/channels.js @@ -663,6 +663,7 @@ packetHash: c.packet.hash, packetId: c.packet.id, hops: d.path_len || 0, snr: c.packet.snr || null, observers: c.packet.observer_name ? [c.packet.observer_name] : [], + scope: c.packet.scope_name || null, repeats: 1 }); continue; @@ -679,6 +680,7 @@ packetHash: c.packet.hash, packetId: c.packet.id, hops: 0, snr: c.packet.snr || null, observers: c.packet.observer_name ? [c.packet.observer_name] : [], + scope: c.packet.scope_name || null, repeats: 1 }); } else { @@ -2258,6 +2260,7 @@ if (msg.observers?.length > 1) meta.push(`${msg.observers.length} observers`); if (msg.hops > 0) meta.push(`${msg.hops} hops`); if (msg.snr !== null && msg.snr !== undefined) meta.push(`SNR ${msg.snr}`); + if (msg.scope) meta.push(`Scope: ${escapeHtml(msg.scope)}`); const safeId = btoa(encodeURIComponent(sender)); // #1367: emit BOTH the new chat-app class names (.ch-message /