Chat
Chat covers all messaging surfaces: 1:1 direct messages, freeform group chats, and the embedded chat rooms that every event spawns. The UX is unified — the user moves between flavours with the same gestures.
Entry points
| From |
Action |
| Nav dock |
The chat bubble icon opens the inbox at /connect. |
| Profile → "Message" |
Opens or creates a 1:1 room with that user. |
| Event detail → "Chat" |
Opens the event's embedded group room. Available to approved participants only. |
| Friend marker on the map |
Profile-mini → "Message" → 1:1 room. |
| New chat (+) |
The compose action in the inbox opens a contact picker. |
| Notification deep link |
(Future: push notification opens the room.) |
Primary flow — open a room and send
- Inbox. The inbox lists the user's rooms sorted by last activity. Each row shows: avatar (or group mosaic), name, last-message preview, time, unread count.
- Tap a room. Mobile pushes the room as a fullscreen view; desktop / wide tablet shows the room in a right-pane split.
- Connect. The app opens a WebSocket to the room. Existing messages render bottom-up. A "Joined room" indicator briefly appears for the user, visible only to others.
- Compose. The composer at the bottom accepts text. Tapping the attachment icon offers Image (camera or library). Tapping the smile icon offers an emoji picker.
- Send. The message appears immediately in the user's view with a "sending" tick. On server ack it flips to "sent". On read by the other party, it flips to "read" (when receipts are enabled by both sides).
- Receive. Incoming messages slide in from the bottom. If the user is scrolled away from the bottom, a "↓ N new" pill appears.
- Leave the room. Back-navigates to the inbox; the WebSocket disconnects with a clean close. Other users see the user's presence drop.
Sub-flows
| Sub-flow |
Behavior |
| 1:1 DM |
Two-person room. No "members" management. Profile avatar of the other user as the header. |
| Group chat (freeform) |
Created via "+ → New group". User picks members from friends. After creation, the group has a name, an avatar, and a settings page. |
| Event chat |
Auto-created when the event is created. The host is admin; approved participants are members. The room name follows the event title. |
| Reply to a message |
Long-press → "Reply" pins the target message above the composer as a preview chip. Sending bundles the reply reference; recipients see the preview inline above the new message. |
| Edit a message |
Long-press a sent message → "Edit". The composer prefills. Saved messages show an "edited" hint below the bubble. Edits push to other clients in real time. |
| Delete a message |
Long-press → "Delete". Soft-deletes the message — bubble collapses to "Message deleted" for everyone. |
| React to a message |
Long-press → emoji bar appears. Tap one to add a reaction. A user can have at most one reaction per message; tapping the same emoji removes it. |
| Read receipts |
When both parties have read receipts on, sent messages show a "read" tick when the recipient opens the room. Either user can disable them in preferences; the indicator goes blank for both sides. |
| Typing indicator |
While the other party is composing, an animated "..." appears under the room header. Stops when they pause or send. |
| Disappearing messages |
The group / DM settings has a "Disappearing messages" picker: off / seen / 24h / 7d. Toggling applies to future messages only — historic messages don't suddenly start expiring. |
| Image attachment |
Selecting an image uploads it; an inline progress overlay shows on the placeholder bubble. On completion, the bubble becomes the image with rounded corners. |
| Forward |
Long-press → "Forward" opens a picker of recent rooms; selecting one inserts the message there as a forwarded card. |
| Mute |
Group settings → "Mute". Mute hides notifications for the room. The icon next to the room name shows a muted indicator. |
| Nickname |
Each user can set a nickname for themselves in a group room. Other members see them by nickname. |
| Add / remove member |
Group admin: settings → "Members" → "+" for picker, swipe-left on a row for remove. A system message announces the change. |
| Search |
Tap the search icon in the room header for in-room search, or use the inbox search for cross-room search. Results highlight the matched term. |
States
| State |
What the user sees |
| Inbox empty |
A muted illustration + copy: "No conversations yet — find a friend or join an event." |
| Inbox loading |
Skeleton rows. |
| Room loading |
A spinner in the center; composer is disabled. |
| WebSocket disconnected |
A thin banner under the header: "Reconnecting..." Messages typed during disconnect queue locally; on reconnect they send in order. |
| Send failed |
The message shows a red ⚠ icon. Tapping retries the send. |
| No messages yet |
The room shows the room avatar + name in the center with copy: "Say hi." |
| Anonymized sender |
When the original sender deleted their account, their bubbles render as "Deleted user" with a neutral avatar. History is preserved. |
| Disappearing-msg ticker |
Messages with an expiry show a small timer pill in the corner indicating remaining time. |
| Expired in-session |
When a disappearing message's TTL elapses while the user is reading, the bubble fades out with a brief animation and disappears. |
Edge cases & gotchas
- Heartbeat keeps the room open. The client pings every 30 seconds. Backgrounding the app pauses the ping; the OS may close the socket. On foreground, the client reconnects automatically and re-fetches anything missed.
- Reconnect is exponential with a cap. Five attempts at
1s, 2s, 4s, 8s, 10s. After that, the user sees a persistent "Couldn't reconnect — pull to refresh" banner.
- Edits and deletes apply locally first. They appear immediately and confirm asynchronously. If the server rejects (e.g., editing someone else's message), the change reverts and a toast appears.
- Read receipts are mutual. If the other side has disabled them, the user can't see their own "read" ticks either — there's no one-way leak.
- Disappearing messages are forward-only. Toggling
7d on does not retroactively age old messages; only messages sent after the toggle have an expiry. This is by design and is mentioned in the settings sheet.
- Event room retention. Once the parent event passes
end_time + retention_days, the event chat's messages are wiped by a background job. The user sees the room go empty on next open. The room itself stays (in case anyone wants to revisit the participant list).
- DM retention. Direct and freeform group rooms are not purged by the retention worker — only event rooms are.
- Friends-only check on DMs. Sending a DM does not require friendship — anyone can initiate. (Product rule may tighten this; see
product/domains/.)
- Forwarding a friends-only moment. Forwarded preview cards inherit the original visibility; a non-friend recipient sees "Not available" in place of the preview.
- Image upload during background. The OS may suspend the upload. On resume, the bubble shows a retry icon — the user can re-send.
- Multi-device. A user with the app on two devices opens both — both connect to the same room. Messages render on both; the read receipt is set when either device opens the room.
See also