fix(mesh): Meshtastic 3ccc pkc_capable pill + Sideband image interop + critical CBOR wire-bloat fix

Merges in the meshtastic agent's now-finished work alongside this session's
continuation: stock-peer (3ccc) PKI-capability is now stamped through
get_contacts -> refresh_contacts -> MeshPeer.pkc_capable, so a directed DM to/from
a PKC-capable stock Meshtastic peer correctly shows the E2E pill on the Sent row,
not just received messages. Confirmed live: .198 sees "Meshtastic 3ccc" with
pkc_capable=true.

Also fixes two real interop/correctness bugs found while live-testing the
Reticulum <-> Sideband link:
  - Receive: the daemon only ever read LXMF's plain-text content, silently
    dropping native FIELD_IMAGE/FIELD_FILE_ATTACHMENTS fields — a stock
    Sideband/NomadNet photo vanished into a blank-space message. Now decoded
    into the same ContentInline typed envelope our own attachments use.
  - Send: images to a non-archy (stock) peer now use native LXMF FIELD_IMAGE
    instead of our own opaque CBOR wire format, which Sideband can't decode.
  - Root cause of a garbled MC-chunk-fragment bug: TypedEnvelope.v/.sig (the
    OUTER wrapper every message type uses) serialized raw bytes as a CBOR
    array-of-integers instead of a native byte string, bloating every
    message on the wire ~2-3.5x — enough to push even a tiny ReadReceipt
    over the 140-byte single-frame chunking threshold. Root-caused by
    reading ciborium's deserializer source directly (deserialize_bytes only
    works within its internal scratch buffer; deserialize_byte_buf streams
    unbounded).

Frontend: consolidated the attach/record buttons into a single animated "+"
menu (was overflowing the compose row).

857/857 tests pass. Verified live across all 5 deploy-roster nodes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-30 22:07:45 -04:00
co-authored by Claude Sonnet 5
parent f54c853128
commit 0eb5c258f5
14 changed files with 694 additions and 63 deletions
+16
View File
@@ -472,6 +472,22 @@ select.mesh-bitcoin-input option { background: #1a1a2e; color: rgba(255,255,255,
.mesh-chat-attach-btn.is-busy { opacity: 0.8; cursor: wait; }
.mesh-chat-record-btn.is-recording { background: rgba(239,68,68,0.25); animation: mesh-record-pulse 1.1s ease-in-out infinite; }
@keyframes mesh-record-pulse { 0%, 100% { box-shadow: 0 0 0 0 rgba(239,68,68,0.4); } 50% { box-shadow: 0 0 0 6px rgba(239,68,68,0); } }
/* "+" attach menu — replaces individually-visible attach/record buttons
(was overflowing the compose row) with one toggle + an animated stack. */
.mesh-attach-menu-anchor { position: relative; flex-shrink: 0; }
.mesh-chat-plus-btn { font-size: 1.3rem; line-height: 1; font-weight: 300; transition: transform 0.2s ease; }
.mesh-chat-plus-btn.is-open { transform: rotate(45deg); background: rgba(255,255,255,0.14); }
.mesh-attach-stack {
position: absolute; bottom: calc(100% + 8px); left: 0;
display: flex; flex-direction: column; gap: 8px;
}
.mesh-attach-stack-enter-active, .mesh-attach-stack-leave-active {
transition: opacity 0.18s ease, transform 0.18s ease;
}
.mesh-attach-stack-enter-from, .mesh-attach-stack-leave-to {
opacity: 0; transform: translateY(8px) scale(0.9);
}
.mesh-chat-reaction-btn.is-busy { background: rgba(251,146,60,0.25); }
.mesh-chat-reaction-btn:disabled { opacity: 0.6; cursor: wait; }