Per the crew-chat audit recommendations: - The crew Chat tab no longer silently switches transport on a coordinator tag. It is always NIP-17; coordinator routing lives only in the Private chat (MLS). useCordnChat / useCrewChatUnread / lib/cordn client+crypto are kept but unwired (headers marked). Drops the tab's Cordn-only unread dot. - Group reads (Groups list + crew thread) widened 200 -> 500 wraps; the shared decrypt cache makes the extra reads affordable. 1:1 list stays 200.
3.5 KiB
3.5 KiB
Crew chat — audit (2026-10-13)
Scope: the crew page's Chat tab (CrewChat.tsx) across both transports — the
built-in NIP-17 group chat and the Cordn sealed-relay path (useCordnChat)
— plus the shared DM readers it depends on (useDirectMessages.ts).
Findings
| # | Sev | Status | Finding |
|---|---|---|---|
| 1 | High | Fixed | A sent crew message didn't appear. useSendDirectMessage invalidated direct-messages / conversations / group-conversations, but the crew thread reads ['group-messages', gid, …] — never invalidated. For a silent signer it showed up on the next 60 s poll; for Amber (no polling, session-cached) it never did. Fixed with an optimistic cache insert of the sender's own plaintext (no refetch → no extra signer pop-ups). |
| 2 | High | Fixed (1.6.4) | Amber "didn't respond" on send. The per-recipient gift wraps (and the sealed-relay roster seal) were created with Promise.all — concurrent nostrsigner: intents collide, one is answered and the rest hit the 60 s native timeout. Now sequential for an interactive signer. |
| 3 | High | Fixed | Every chat view re-decrypted the whole inbox. Each reader (useConversations, useConversation, useGroupConversations, useGroupConversation) decrypted every wrap — 2 signer ops each — and each has its own query key, so opening Messages decrypted everything, then opening one crew thread decrypted it all again, and again per thread. Added a shared, coalesced, bounded session decrypt cache (decryptGiftWrap) keyed viewerPk:wrapId; only successes are cached, so a transient failure retries. |
| 4 | Med | Fixed | No dedup by rumor id in a thread — a re-sent rumor (same id, two wraps) could render twice. Threads now dedup by id before sorting. |
| 5 | Med | Fixed | Double-send: pressing Enter twice before React re-rendered isPending sent twice. Added an in-flight ref guard. |
| 6 | Med | Fixed | The Cordn path invalidated its query on every send → an Amber refetch storm. Now optimistically inserts the sent message and only invalidates for silent signers. |
| 7 | Med | Fixed | Transport switched silently. A crew with a coordinator tag used the sealed-relay Cordn path; without one, NIP-17. They have separate histories, so a crew that later gained a coordinator looked like it "lost" its chat — and new crews default to a coordinator. The Chat tab is now always NIP-17; coordinator routing lives only in the Private chat (MLS). The sealed-relay modules (useCordnChat, lib/cordn/{client,crypto}, useCrewChatUnread) are kept but unwired. |
| 8 | Med | Fixed | Read limit 200. Group reads (the Groups list + a crew thread) now use 500 (client-side filter is unavoidable — a gift wrap hides its participants — and the shared decrypt cache makes it affordable); the 1:1 list stays at 200. |
| 9 | Low | Open | No images/attachments in the crew Chat (text only). The MLS Private chat got images; the NIP-17 path didn't. |
| 10 | Info | — | With the cache, an Amber read is now one decrypt per wrap per session instead of one per view. Sends still cost 2 approvals per member (NIP-17) vs ~1 per message on the MLS path. |
Net effect
- Sending now shows your own message immediately.
- Opening Messages ⇄ a crew thread ⇄ another crew thread no longer re-prompts.
- Amber stops timing out mid-send.
Not click-tested in a real browser this session; npm test + check-i18n green.