Events¶
An event in Tomoda is a plan to be somewhere together, for a few hours, soon. It is the load-bearing primitive of the product — the thing the map exists to surface, the thing chat exists to support, the thing friends exist to fill.
The story¶
Group chats are how friend groups talk. They are not how friend groups meet. The space between "are you free Saturday?" and "ok, see you at 8 at the wine bar" is where plans go to die — half the people miss the message, half forget the address, half show up at 9. We built events to collapse that space. An event in Tomoda has a start, an end, a place, a capacity, and a chat room — and every one of those things is a first-class object, not a string in a group chat.
The shape we kept refining was the lifecycle. An event is upcoming
until its start time, ongoing from start to end, completed once it
ends, and archived once its retention window expires — at which point its
messages are purged but the bare row sticks around as history. A scheduler
job ticks every five minutes and walks the table, so the state is always a
function of the wall clock plus the host's intent (cancel, start now,
transfer ownership). We deliberately don't show "active" events forever;
plans rot if you let them linger past their end time.
The other shape we cared about was who can see and who can join. An
event has three visibility modes — public (anyone can find and join),
friends (only the host's accepted friends can join), and invite_only
(only people with the access code or an approved-by-host slot can join).
Capacity is a hard number, waitlist is optional, and approval can be
auto-on or host-gated. The host can transfer ownership; on transfer the
old host becomes a participant and the room's admin role moves with them.
If a host leaves and the event is now hostless and empty, the lifecycle
worker drops it. If a host leaves but participants remain, the event is
cancelled rather than deleted — so the conversation has somewhere to land.
What we're optimising for¶
- Plans, not posts. Events expire because plans expire. We're not a "look what I did" service.
- Spontaneous over scheduled. The UX assumes "next two hours", not "Tuesday three weeks from now". The lifecycle worker is built around it.
- Hosts have authority. The host approves participants, can transfer ownership, can cancel, can force-start. Co-host is a future shape; today the host is the room.
- Capacity is a promise. If you set a cap of 8, the 9th person waits. Waitlist is optional and the host decides whether it's on.
- Privacy by visibility tier. Public is opt-in.
friendsis the middle road.invite_onlyis for the dinner party. - Chat is part of the event, not a separate thing. Joining the event joins the room. Cancellation leaves the room intact so the apology thread has a home.
What an event is not¶
- It is not a recurring calendar item. There is no
events_series. If you do the same thing every Friday, you create the same event every Friday. - It is not a ticket. We don't gate entry behind purchase.
- It is not a campaign or "drop". Sponsorship tiers exist (so a few large events can punch through cluster aggregation on the map at low zoom) but they don't change anything about the social contract of the event itself.
See also¶
- Domain spec: Domains → Events
- Backend implementation: Backend → Events service
- Discovery side (how events surface on the map): Bible → Discovery
- Chat side (how the event room behaves): Bible → Chat
- UX for create / join / host flows:
../ux/events.md(coming soon)