Events
Events are scheduled, location-anchored gatherings. The UX covers creating, finding, joining, leaving, and hosting — plus the chat room that opens to participants once they're in.
Entry points
| From |
Action |
| Discover map |
Tap an event marker, or long-press an empty location to create one. |
| Discover list |
Tap an event card. |
| Floating "+" on the map |
Opens the event-create modal. |
| Horizon → Events |
The user's personal events list (mine + hosted + pending). |
| Share link |
tomoda.life/events/:id opens the public share page; "Open in app" hands off to the in-app detail. |
| Notification deep link |
(Future: approval / start-soon notifications open the event detail.) |
Primary flow — discover and join
- Discover an event. The user taps a marker or list card. A detail sheet opens with title, host avatar, date/time, location, capacity, description, and a participant strip.
- Inspect. Scrolling reveals the full description, the location map, the participant list, and the host's other upcoming events. The sheet header pins to the top.
- Tap "Join". If the event is
public and auto_accept, the user is added immediately as approved and the action bar flips to "Leave" + "Open chat". If auto_accept is off, the user becomes pending and the action bar flips to "Awaiting approval".
- Open chat. Approved participants get a "Chat" affordance that opens the event's embedded group chat room. The chat is unavailable while pending.
- Pre-event reminder. (Stub — no notification service exists yet.) The user is expected to surface event start time via Horizon → Events; the dedicated "starts in N minutes" reminder is not yet wired.
Primary flow — create
- Open the modal. The "+" on the map or a "Create" action on Horizon opens the create-event sheet.
- Pick a location. Either tap-and-hold on the map (location prefilled), or use the location search. The selected place name + address appear.
- Set time. Pick a start and (optionally) end time. The timezone is resolved from the location's coordinates and shown next to the time.
- Configure. Title, description, category, capacity, visibility (
public / friends / invite_only), auto-accept toggle, optional access code, waitlist toggle.
- Publish. The event appears on the map within seconds. The creator is auto-added as the host (admin in the chat room).
- Manage. The host sees a different action bar on the detail sheet: pending requests show a count badge; tapping reveals an approval list.
Sub-flows
| Sub-flow |
Behavior |
| RSVP'd vs not |
Action bar varies: not joined → "Join". Pending → "Awaiting approval" + "Withdraw". Approved → "Leave" + "Chat". Host → "Manage" + "Chat". |
| Host approval |
Host opens the participant list. Pending requests have ✓ and ✗ actions inline. Approve adds the user to the chat room; reject removes them. |
| Access-code events |
"Join" prompts for the code first. Wrong code → an inline error appears under the input. Correct code → the join proceeds. |
| Waitlist |
When capacity is full and waitlist_enabled is on, "Join" places the user on the waitlist. If a participant leaves, the next waitlisted user is auto-promoted (and they see a state change next time they open the event). |
| Required attestations |
If the event needs an attestation the user doesn't have (verified email, complete profile, etc.), the "Join" action is disabled with a tooltip explaining which attestation is missing. |
| Past events |
After end time, the action bar collapses to "View chat" (read-only after retention window). Detail sheet shows a "Completed" badge in place of the join button. |
| Cancellation |
The host has a destructive "Cancel event" action in the manage menu. A confirmation modal lists the participant count. Confirming flips the event to cancelled and broadcasts a system message to the chat. |
| Transfer host |
"Transfer host" picker lists current participants. Selecting one makes them host; the previous host is demoted to participant. The chat room follows. |
| Leave |
Participants tap "Leave" → confirmation modal. Leaving removes them from the chat room. If the host leaves and there are no other participants, the event is deleted entirely; if others remain, the event flips to cancelled so the chat history survives. |
| Public share page |
/events/:id is reachable without sign-in. Visitors see the event card with a sign-up CTA. Authenticated visitors see the same in-app detail. |
States
| State |
What the user sees |
| Loading detail |
Skeleton avatar + skeleton lines. Action bar is a single shimmering pill. |
| Approved + chat ready |
Action bar: "Leave" + "Chat". Chat icon shows an unread badge if there are new messages. |
| Pending |
Action bar: "Awaiting approval". A muted line: "The host hasn't approved you yet." |
| Host (own event) |
"Manage" bar with counts of pending / approved / waitlisted participants. |
| Capacity full, waitlist off |
"Join" is disabled, replaced by "Event is full". |
| Capacity full, waitlist on |
"Join waitlist" with a position indicator after the first join. |
| Past / completed |
"Completed" badge on the header. Action bar replaced with "View chat" (or removed if retention has elapsed). |
| Cancelled |
A red "Cancelled" banner spans the header. The chat is read-only. |
| Invite-only and not invited |
The user reaches the page (via a link) but the detail sheet shows "This event is invite-only" — no action bar, no participant list. |
Edge cases & gotchas
- Join eligibility is checked in order. Access code → capacity → required attestations → visibility (friends-only requires friendship with the host) → auto-accept. The error message reflects the first failure, not all of them. A user who is missing an attestation and is not a friend will see only the attestation error.
- Chat room opens immediately on auto-accept. If the event is public + auto-accept, the user is in the chat room the moment they tap Join, with no approval delay.
- Friends-only check is at the host level. "Friends" visibility means friends of the host, not friends of anyone already in the room.
- Withdraw a pending join. The user can withdraw before approval — it cancels the request. The host's pending list updates within a few seconds.
- Approval after capacity reached. If the host delays approving a pending user past the point where the event is now full, approval still proceeds — pending users aren't subject to capacity, only fresh joins are.
- Leaving the chat room. Leaving the event removes the user from the chat room as a side effect. There is no separate "leave chat" for an event chat.
- Past-event messages are purged. Once
end_time + retention_days is past, the event's chat history is hard-deleted by a background job. The user sees the chat go empty next time they open it.
- Event cancellation broadcast. Participants get a system message in the chat ("Event cancelled by host") and the event flips to a cancelled banner on the detail sheet — both happen on the same broadcast.
- Backgrounding mid-join. If the app is backgrounded between the tap and the response, the join completes server-side; on resume, the action bar reflects the new state without re-tap.
- Profile incomplete. A user with no username / DOB cannot reach the create flow — the auth gate redirects them to onboarding first.
See also