Files
mantra-kmp/composeApp
Kgothatso Ngako 74c352ab35 fix: show NIP-17 messages that arrive, not just the ones you send
A NIP-17 room only ever displayed your own words. Sending worked end to end --
sendChatMessage queues the gift wrap and writes a local ChatMessage so you see
what you typed -- but nothing on the inbound side ever wrote a row for a message
that arrived. The kind-14 branch decrypted the payload, stored it, built the
chat room from its p-tags, updated the subject, queued profile and relay-list
syncs, and stopped. The feed is `SELECT * FROM ChatMessage WHERE chatRoomId = ?`,
so with no row written there was nothing to show.

Every write of a ChatMessage in the tree confirms it: the sender's own copy in
DatabaseChatRepository, three MLS outbound sites, ChatMessage.fromGroupEventResult
for inbound MLS group events, one commented out in the welcome branch, and the
ritual notices. Nothing for an inbound gift wrap. MLS rooms were never affected,
which is why this survived -- CONVENIENT groups render both directions.

persistInboundChatMessage files the message once the room is known to exist.

## Two arrivals are deliberately not filed

A message already filed. Relays redeliver and negentropy re-syncs the same gift
wraps, and the same wrap yields the same payload id every time, so a lookup on
giftWrapPayloadId makes a redelivery a no-op. It has to be checked rather than
relied on: ChatMessage.id is autogenerated, so a second insert is simply a second
line in the conversation.

Our own words coming back. sealGiftWrapPayload wraps a copy to every participant
of the room including the sender, so a message returns to the device that sent it
-- and that device already wrote the row on the way out. Left alone, every
message you sent would appear twice.

The two copies of your own message cannot be matched on the payload id, which is
the interesting part: the outbound row is keyed on EventHasher.hashId over the
rumor, while GiftWrapSeal.decryptGiftWrapPayload keys the inbound one on the
seal's id. Same message, two ids -- and since every recipient gets their own
seal, the same message has a different id on every device that receives it. So
the sender is matched instead, which costs multi-device: a second install of the
same identity will not pick up messages sent from the first. Keying the inbound
payload on the rumor it came from would fix both, and would make payload ids
agree across devices, but it changes identity for every gift-wrapped kind rather
than just this one and belongs in its own change.

## Timestamps come from the rumor

NIP-17 fuzzes the seal and the wrap by up to two days to frustrate correlation,
so ordering the feed by either would shuffle the conversation into nonsense. The
rumor keeps the real time and that is what the row records.

## Scope

Kind 14 only, which is the kind this app sends. A kind 15 file message from
another client still falls through to the "Unsupported event" log, as before.

Not covered by tests: this is Room writes on the inbound path, which does not run
under :composeApp:testDebugUnitTest. Verified by compilation and by tracing the
branch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-05 00:59:07 +02:00
..