Case study

GoWeya

Get out. Go together.

Find people who are up for it. Activity-based social discovery, in beta on iOS and Android.

GoWeya's map view, showing activity pins and clusters around Ortigas, Mandaluyong and Pasig.
A GoWeya activity page for badminton doubles, noting that the exact meeting point is shared after you are accepted and the fee is paid to the host, never through GoWeya.

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.

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.

GoWeya's sign-in screen, asking Up for a ride? over a photograph of a bicycle at sunset, above a sheet offering Continue with Apple and Continue with Google.
GoWeya's Explore tab, with the heading Find your next good day, a search for things to do and category tiles.
GoWeya's Explore search with ba typed, listing matching activity types such as badminton, baking, basketball and pickleball, each with its own drawn mark.
GoWeya's Post an activity screen, asking whether it happens in person, online or just talking before anything else.

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 protect it with a rule. Rules get forgotten.

  1. Approximate location

    A guest sees a fuzzed point and a bucketed distance. In the app: somewhere within about 150 m of here.

  2. Request to join

    Still approximate. Asking does not reveal anything, so nobody can farm addresses by requesting.

  3. Host accepts

    The decision sits with the person who owns the activity, not with a setting nobody reads.

  4. 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.

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.

In people's hands, not on a roadmap.

Finding something to do

  • 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.

  • Invite-only activities

    Post something visible to nobody but the people you send it to.

  • Recurring series

    The weekly badminton game posts itself.

Going together

  • Join requests

    Hosts accept or decline. Acceptance is what releases the exact location.

  • 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. Part of Premium.

Staying safe

  • Reliability

    Event-sourced from whether people turned up. A tier, counts and a show rate, never a score.

  • Identity checks

    An optional identity check, surfaced as a badge rather than as a document.

  • Reporting and blocking

    Report, block, and a moderation path that can actually read the evidence.

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, a show rate 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 Google Maps on both platforms. 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.


Want this kind of thinking on your product?

Tell us what you are building and where it is up to. We will tell you what it would take.