The problem
New York has more events on any given night than a person could evaluate in a week, and almost none of them are relevant to any particular person. We ran 20 user interviews before writing product code. Two findings shaped everything after.
The first was fragmentation: 87% of the 18–35 New Yorkers we spoke to used three or more apps to figure out what to do, usually some mix of Google Maps, Eventbrite, Instagram, Resy, and a group chat. The second finding was a negative result, and it mattered more. Zero interview subjects asked for another social feed. What they described wanting was an answer, not more to look through.
“We spend most of our time looking into activities we end up not even going to. We just get overwhelmed and give up.”
Sam, 23 · user interview“Honestly? I don’t want another social media app. I just want to know what’s good this weekend.”
Recurring theme across 20 interviewsThat pushed us away from a feed and toward a ranked deck, one card at a time, ordered by predicted fit, creating a cold-start problem. A recommender with no signal cannot rank, so we did the obvious thing and asked the user up front.
What I built
The client is React Native on Expo and the backend is Supabase. Most of the work went into the ingest pipeline and the scoring loop.
Six live scrapers pull from Dice, Resident Advisor, Luma, Fever, NYC museums, and Eventbrite, refreshed every three days via GitHub Actions. Claude Sonnet finds what the scrapers miss and pulls in a curated "high-taste" list of events, mostly involving pop-ups, sample sales and gallery openings. Then everything gets cut hard. An LLM rubric rejects tourist traps, corporate spam, and children's events, which removes roughly 85% of incoming inventory. What survives gets a vibe score from 1–10 via gpt-4o-mini; anything under a score of 5 never loads.
Aggregators need the firehose because their economics depend on total inventory. Ours didn't, so we could afford to throw most of it away. Rejection is the product.
On the client, every swipe carries intent. Each gesture updates four independent signals (category, tag, borough, price band) and the deck re-ranks in under 200ms.
| Gesture | Intent | Signal effect | Weight |
|---|---|---|---|
| Right | Going | Category, each tag, borough | +0.15 / +0.08 / +0.06 |
| Left | Not now | No taste effect; resurfaces in 2–5 days | 0.00 |
| Down | Not interested | Category penalty; 3 strikes hides permanently | −0.05 |
| Up | Inspect | Opens detail, deliberately no signal | — |
Cold start blends quality and timing for roughly the first 20 swipes, then hands over to personalized ranking. That threshold is what confidence measures. I built it as an internal diagnostic, a way to check if he recommender had enough signal before we trusted it. It turned out to be the most important number in the product.
What the data said
We launched with no paid acquisition. The App Store funnel was healthy. 157 impressions, 79 product page views, 18 first-time downloads, a 20.5% conversion against Apple’s typical 5–7%. With 23 seeded TestFlight users still active, that put us at 41 people.
Then I pulled the onboarding funnel expecting a drop-off curve, and got something that didn’t resolve.
The mechanism was in our own design and we hadn’t noticed it. Cold start hands over to personalized ranking after about 20 swipes. Twenty swipes takes under two minutes. The taste-setter was asking users to spend ninety seconds declaring preferences the deck would infer from two minutes of ordinary use.
The onboarding flow wasn’t a bottleneck we needed to widen. It was a question the product was already answering by watching.
The findingThis surfaced because two independent sources agreed. A heuristic UX audit flagged the flow as skippable friction; separately, the scoring code showed confidence accumulating for users who had never touched it. Neither alone would have been convincing, since the audit could have just been my taste and the score could have been a bug. The finding lives in the agreement between them.
Every figure here comes from a launch cohort of 41 users. The percentages are honest but the denominators are small: 60% is roughly 25 people, and the zero is a genuine zero rather than a rounding artifact. I’d re-run all of it at 1,000 users before treating any of it as settled. The direction was clear enough to act on though, and a design decision that costs every new user ninety seconds doesn’t need p < 0.05 to be worth revisiting.
What I'd do next
Cutting the taste-setter is the obvious move and the least interesting one. What I'd actually want to know is what else in the product asks for information it could observe instead.
- Cut the taste-setter entirely; keep one optional neighbourhood prompt at first save, where intent already exists
- Surface confidence to the user as a reason (you’re seeing this because you saved three like it), turning an internal diagnostic into trust
- Instrument the inverse: which users never reach confidence, and what they have in common
- Re-run the audit against the scoring code each quarter, since that pairing is what caught this
Instrument the implicit path before you build the explicit one. That's the lesson I took into everything after. We built the questionnaire because it was the legible solution, and only found out it was redundant because we happened to have logged the alternative.



