feat(mesh,ui): per-message transport pill (Mesh/FIPS/Tor) + fix E2E pill

Adds a per-message transport badge to archy↔archy mesh chats and fixes the
long-broken E2E badge — both meshcore and meshtastic, styled like the existing
E2E pill.

Transport pill:
- New `MeshMessage.transport` ("lora"/"fips"/"tor"), surfaced in the UI beside
  the E2E badge (Mesh.vue transportLabel() → Mesh/FIPS/Tor, mesh-styles.css).
- Sent LoRa → "lora"; sent federation → finalized to the real leg ("fips"/"tor")
  once the background send resolves (req.send_json transport), via an id-keyed
  store update.
- Received: a post-dispatch stamp on handle_typed_envelope_direct's output
  (monotonic ids) tags both transports without threading through all 20 typed-
  dispatch sites — radio wrapper stamps "lora", federation injector stamps the
  peer's last_transport ("fips"/"tor", default tor; the inbound HTTP carries no
  FIPS-vs-Tor signal).
- Plain native/channel LoRa frames → "lora"; channel broadcasts stay non-E2E.

E2E pill fix:
- `encrypted` was hardcoded false at every MeshMessage construction site, so the
  UI badge (Mesh.vue `v-if="msg.encrypted"`) never showed. Now: federation
  envelopes are E2E (identity-signed over an encrypted transport); the meshcore
  native-DM receive path already had a real `encrypted` flag (now also tagged
  with transport). meshtastic-PKI radio E2E flag threading is a noted follow-up.

Backend cargo check + frontend vue-tsc build both green. Needs a live radio +
multi-transport pass on .116/.228 to confirm end-to-end (see
project_transport_pill / project_meshtastic_parity).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-29 04:29:25 -04:00
co-authored by Claude Opus 4.8
parent 169ff2e2cd
commit 11038cdcc9
7 changed files with 141 additions and 2 deletions
+12
View File
@@ -1076,6 +1076,17 @@ function isEditedMessage(msg: MeshMessage): number | null {
function isDeletedMessage(msg: MeshMessage): boolean {
return msg.message_type === 'delete' || msg.typed_payload?.deleted === true
}
/// Short label for the per-message transport pill (Mesh / FIPS / Tor), or null
/// when the transport isn't known. Covers both meshcore and meshtastic since
/// the field lives on the shared MeshMessage.
function transportLabel(msg: MeshMessage): string | null {
switch (msg.transport) {
case 'lora': return 'Mesh'
case 'fips': return 'FIPS'
case 'tor': return 'Tor'
default: return null
}
}
// Read-receipt: after render, if the bottom message is from the peer (direction='received')
// and has a MessageKey, fire mesh.send-read-receipt up to that seq. Debounced so scroll
@@ -1798,6 +1809,7 @@ function isImageMime(mime?: string): boolean {
<!-- Default: plain text -->
<div v-else class="mesh-chat-bubble-text">{{ msg.plaintext }}</div>
<div class="mesh-chat-bubble-meta">
<span v-if="transportLabel(msg)" class="mesh-chat-transport" :class="'transport-' + msg.transport" :title="'Delivered over ' + transportLabel(msg)">{{ transportLabel(msg) }}</span>
<span v-if="msg.encrypted" class="mesh-chat-e2e">E2E</span>
<span v-if="isEditedMessage(msg) !== null" class="mesh-chat-edited">(edited)</span>
<span v-if="msg.delivered && msg.direction === 'sent'" class="mesh-chat-ack">&#x2713;&#x2713;</span>
+5
View File
@@ -148,6 +148,11 @@
.mesh-chat-bubble-meta { display: flex; align-items: center; gap: 6px; margin-top: 4px; justify-content: flex-end; }
.mesh-chat-bubble-time { font-size: 0.65rem; color: rgba(255, 255, 255, 0.3); }
.mesh-chat-e2e { font-size: 0.55rem; font-weight: 700; color: #4ade80; padding: 0 3px; border: 1px solid rgba(74, 222, 128, 0.3); border-radius: 3px; }
/* Per-message transport pill (Mesh / FIPS / Tor), styled like the E2E badge. */
.mesh-chat-transport { font-size: 0.55rem; font-weight: 700; padding: 0 3px; border-radius: 3px; border: 1px solid currentColor; opacity: 0.85; }
.mesh-chat-transport.transport-lora { color: #f59e0b; } /* Mesh/LoRa — amber */
.mesh-chat-transport.transport-fips { color: #a78bfa; } /* FIPS — violet */
.mesh-chat-transport.transport-tor { color: #818cf8; } /* Tor — indigo */
.mesh-chat-ack { font-size: 0.7rem; color: #3b82f6; }
.mesh-chat-compose { padding: 12px 16px; border-top: 1px solid rgba(255, 255, 255, 0.06); flex-shrink: 0; }
.mesh-chat-send-error { color: #ef4444; font-size: 0.75rem; margin-bottom: 6px; }