← All workNative apps

Veto

Everyone secretly vetoes what they won't tolerate — and the database, not the app, is what keeps those vetoes secret.

Year
2026–
Role
Design + Development
Veto

Veto settles group decisions by asking the opposite question. Instead of voting for what you want, everyone privately vetoes the options they won't tolerate, and the app reveals what survived. If every option gets vetoed by somebody, the least-hated one wins — which is usually where the group was going to grind its way to anyway. It runs two ways: a hosted round your friends join from their own phones, and a pass-and-play mode where one phone goes around the table and nobody needs an account.

The problem

Polls measure enthusiasm; groups decide on tolerance

A poll in a group chat asks what people want, and what comes back is a spread of first choices nobody can turn into a decision. Four options, four votes, and now you're relitigating it. Worse, the format rewards whoever is loudest and quickest — the first reply anchors everyone else, and the person with the strongest opinion about Thai food wins by default.

The information the group actually needs is the negative space. Somebody had that place last week. Somebody can't eat there. Somebody has a bad memory of it they'd rather not explain in the thread. None of that surfaces in a poll, because saying it in front of everyone is socially expensive and people would rather go along.

Veto asks for that instead, and asks for it privately. You mark what you won't tolerate, nobody sees what you marked, and the app reveals only what made it through. The secrecy isn't a feature bolted on the side — it's the entire reason people answer honestly, which makes it the thing the system has to protect above everything else.

What we built

A lobby, a code, and a phone that goes around the table

With friends, one person hosts a named round and shares a code or an iMessage link. Everyone joins from their own phone and options get added collaboratively in a lobby, which matters more than it sounds — half the value is discovering that somebody already had a suggestion nobody would have volunteered. Then the round moves to the veto step, everyone marks in secret, and the host reveals.

Afterward you can save the crew, and the next round pre-adds everyone. The groups you make decisions with repeat; making that a one-time setup instead of a per-round chore is the difference between an app people try twice and one that's still on the home screen in a month.

The app is four tabs: Play, Rounds, Groups, You. Rounds took the most thought. It's a live inbox of what's waiting on you, because the failure mode of any turn-based group app is a round that stalls for two days while everybody assumes somebody else is holding it up.

The hard part

Secrecy enforced in Postgres, not in the app

Individual vetoes are the sensitive data in this system. If one leaks, the app isn't degraded, it's finished — the whole premise is that you can mark something honestly without it becoming a conversation. So the enforcement lives in the database, where a client bug can't reach it.

Veto rows are locked to their owner with row-level security in Postgres. That's the part everyone does. The part that gets skipped is the realtime publication: RLS on a table means nothing if the rows still go out over the realtime wire, and a subscription is a completely separate path to the same data. It's the leak nobody checks, because the policy looks correct in isolation. So veto rows are excluded from the publication entirely. There is no channel a client can subscribe to that carries another player's veto, because those rows never enter the stream.

The reveal is a Postgres function. reveal_round() computes the winner server-side — fewest vetoes, ties broken randomly — and returns the result, not the inputs. The client is never handed the raw votes and asked to be discreet with them, which is the design where a refactor two years from now quietly turns a secret into a payload. Enforcing this in the database rather than in application code means the app can be wrong and the secret still holds.

The hard part, continued

A social app that has to work with no account and no network

Pass-and-play looks like it contradicts the rest of the product. One phone, handed around a table, no accounts, no server, no network. It's also the mode that gets used first, almost every time.

The reason is that the hosted mode charges an onboarding cost to the least invested person in the group. Somebody has to install an app and sign in while five people wait, and that is precisely the moment a group gives up and just picks a place. Pass-and-play removes the ask entirely: the person who already has it hands over the phone, and the round is done before anyone has time to reconsider.

It costs a second code path through the whole veto flow, one that stores locally and never touches Supabase. That duplication is a real tax and we took it deliberately, because a decision app that requires the entire table to onboard before it can settle anything doesn't get to settle anything.

Shipping it

SwiftUI and Observation, with nothing borrowed for the UI

The app is SwiftUI on iOS 17 with the Observation framework, and no third-party UI dependencies at all. Every screen, transition, and control is built in-house. That's more work up front and it removes a category of problem that tends to arrive at the worst possible time — an OS release that breaks a component library you don't own, right when you need to ship a fix.

Supabase handles auth, realtime, and storage through supabase-swift. The realtime layer is what makes a round feel live: people appearing in the lobby, options landing as they're added, the reveal hitting everyone's phone at once instead of after a pull-to-refresh.

Widgets carry the same job as the Rounds tab — surfacing that a round is waiting on you without requiring you to remember to check. A group decision app lives or dies on the latency between the question and the answer, and most of that latency is people forgetting.

Stack
  • SwiftUI
  • iOS 17+
  • Observation
  • supabase-swift
  • Supabase Auth + Realtime
  • Postgres + RLS
  • Postgres functions
  • WidgetKit
Outcome

Shipped, with a hosted mode built on Supabase realtime and a pass-and-play mode that runs with no account and no network.

Have something like this to build?

Work with us