fix(messages): always show outbound status + long-press copy#104
Merged
Conversation
Root cause of the silent status: a state only existed for an outbound
message after 'await send()' resolved — getSendState(msgId) had no
idToSeq mapping until then, so MessageBubble rendered no status row at
all. With no route to the peer the native send promise can stall
indefinitely (known send-hang), so the offline-sent bubble stayed bare
forever; sendMsg/handleMedia also had no catch, so a bridge throw left
the same silent bubble plus an unhandled rejection.
Wire the existing states through the whole lifecycle instead:
- beginOutbound registers a pseudo-seq (negative msgId, the trick the
failed path already used) as 'queued' BEFORE awaiting the native send,
so every outbound bubble shows clock/'queued' from the moment it
exists and the queued banner counts it immediately.
- The pseudo entry feeds seqQueuedAt, so a send whose promise never
settles flips to 'stale' ("Waiting for peer…") after 45s — the honest
fallback when the data layer reports nothing.
- adoptSeq migrates the bookkeeping to the real seq once native accepts,
without clobbering a state a native event already set; the existing
2s presume-'sent' timer and messageQueued/Delivered/Failed + DB-ack
reconciliation flows are unchanged on top of it.
- send() is wrapped in try/catch on all three paths (text/media/grid,
mirroring the existing QA-05 guard) → 'failed', and the no-peer /
node-down early returns now mark the bubble 'failed' too.
- resolveSeq refuses to downgrade terminal delivered/failed to
sent/queued, and the stale sweep only flips entries still 'queued',
so late timers can never roll back a confirmed outcome.
No new states invented: queued/sent/delivered/failed/stale and their
copy already existed in MessageBubble — they were just never assigned
on this path.
Long-pressing a bubble previously did nothing. The bubble container is now a Pressable: long-press (350ms, matching RecipientPicker) fires a light haptic via the design-system haptics helper, copies the message text with expo-clipboard (already a dependency — no new deps), and confirms with the imperative showToast, the documented surface for transient "copied" feedback. Subtle pressed opacity signals the affordance; plain taps remain inert. Files-only bubbles (empty text) don't attach the handler — there is no text to copy, and the inner FileRow keeps its own tap. No clipboard auto-clear: that pattern is reserved for sensitive material like group keys (ChannelShareSheet), not the user's own conversation text.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Outbound messages always reflect an honest status now, and message text is copyable.
send()resolved, so a slow or offline send showed nothing. The bubble is set toqueuedbefore the await, then transitions queued → sent → delivered, or → failed on throw — no silent sends.Scope
MessagesScreensend-state machine + message bubble.Test
tsc clean · tier0 config/services clean · fake-money + lint clean. Verified: terminal states are guarded, a
messageQueuedevent with no route keeps the bubble honestlyqueuedoff-grid, and a send throw maps tofailed(not a swallowed error).