Case study
GoWeya
Get out. Go together.
Find people who are up for it. Activity-based social discovery, in beta on iOS and Android.


The problem
You are at the mall this afternoon and you want to watch a film, but only with two or three other people. There is no app for that. Group chats are full of people who are busy, event apps are full of things happening next month, and every social app that puts people first eventually turns into a dating app whether or not that was the plan.

The product decision
Activities first. People second.
You browse things to do, not people to meet. There is no user directory, no way to browse profiles, and no fields for romantic intent. That is not a content policy we bolted on, it is the data model. A product where you cannot search for people is a product that cannot quietly become something else.
It shows up in the create flow too. The first question is not who you are, it is whether this is happening in person, online, or is just a conversation.

Privacy by architecture
Location is a type problem, not a rule.
Meeting strangers in physical space is the whole product, so location is the whole risk. Most apps handle it with a rule: only send the exact address to people who have been accepted. Rules get forgotten.
- 01
Approximate location
A guest sees a fuzzed point and a bucketed distance. In the app: somewhere within about 150 m of here.
- 02
Request to join
Still approximate. Asking does not reveal anything, so nobody can farm addresses by requesting.
- 03
Host accepts
The decision sits with the person who owns the activity, not with a setting nobody reads.
- 04
Exact meeting point
Only now do the real coordinates, the address and the meeting point exist for that viewer.
The reason that holds is that a public location and an entitled location are two unrelated types. Not one type with optional fields, not a base class with a subclass. Neither can be assigned to the other, and a single function decides which one a given viewer receives.
- Distances are computed from the fuzzed point, never the real one, because a precise distance from an imprecise point can be trilaterated back.
- Display geometry is generated once at write time and stored, never recomputed per request.
- Precision can loosen over time. It can never tighten in a way that takes information back from someone who already had it.
- A development-time tripwire throws if an exact coordinate ever reaches a public projection.
Designed for real devices
Constraints we chose on purpose.
360px
Minimum supported layout width
The type scale and every layout assume a 5.5 inch, 720p phone. Nothing is designed on a laptop and then squeezed.
Low data
Budgeted payloads, compressed on device
Data frugality is a design constraint, not an optimisation pass. The launch market is prepaid.
iOS + Android
One shared codebase
React Native and Expo, with native modules where the platform demands them.
What shipped
In people's hands, not on a roadmap.
Activity discovery
Browse by what is happening near you, filtered by interest, time and distance.
Map view
Every activity as a pin, placed on its fuzzed point rather than its real one.
Activity creation
In person, online or just talking. Free to host, a minute to post.
Join requests
Hosts accept or decline. Acceptance is what releases the exact location.
Invite-only activities
Post something visible to nobody but the people you send it to.
Activity chat
A thread per activity, readable by moderation because reporting depends on it.
Direct messages
End-to-end encrypted, and only after proven co-attendance.
Circles
The people you actually do things with, without a follower graph.
Recurring series
The weekly badminton game posts itself.
Reliability
Event-sourced from whether people turned up. A tier and counts, never a score.
Identity checks
Optional verification, surfaced as a badge rather than as a document.
Reporting and blocking
Report, block, and a moderation path that can actually read the evidence.
Engineering
For whoever wants the detail.
Where messages are encrypted, and where they are not
Two surfaces are end-to-end encrypted: a direct thread, and a one-seat activity, which the server seals by construction because two people in a room with no third seat is a direct message. Both are sealed on the device. The server holds ciphertext and never a key.
Group activity chats are deliberately not encrypted, and we would rather say so than round the claim up. Moderation, reporting and the contact gate all depend on being able to read them. An app that tells you everything is encrypted, when most of its traffic is not, has told you something false about the part that matters.
Reputation without star ratings
Nobody is rated out of five. Five-star systems compress a person into a number, punish newcomers, and turn every interaction into a performance. Reputation here is event-sourced from things that actually happened, such as turning up to something you said you would. What other people see is a tier and a few concrete counts. The underlying score is never sent to any client, including your own.
The stack, and why
React Native and Expo for one codebase across both stores, with Apple Maps on iOS and Google Maps on Android. Supabase and PostgreSQL underneath, with row-level security doing the work that application code would otherwise be trusted to remember. Capacity is enforced atomically in a transaction with a row lock, so two people cannot take the last seat. Every state change publishes a domain event through a transactional outbox.
The release plumbing is part of the product: signing, provisioning, store review, staged rollouts and over-the-air updates, so a fix does not have to wait on review.
What it took
The hard parts were not the screens. They were deciding what the product must refuse to do, and then making those refusals structural instead of remembered. That is the work we would want to do on yours.