Inspiration

Couples who live together often can't say the one thing that matters most tonight: "How about today?" Asking risks a no; a no hurts; so nobody asks. The problem is not desire, it is the cost of bringing it up. Tacit removes that cost by putting a machine in between: each person answers privately, and only when both say yes does anything happen.

What it does

Tacit is a Home Screen widget for two people. Two dots sit side by side — the left one is you, the right one is your partner.

  • Press twice to send. The first press only arms the dot; if you don't press again within ten seconds it quietly goes back. A mistaken tap never leaves your phone.
  • One bit reaches your partner. Nothing else — no message, no time, no count.
  • Both on = a quiet match notification. One on = nothing. Silence is a no, and no one has to say it.
  • Nothing is kept. No history, calendar, streaks, statistics, read receipts or online status. Everything clears when the date changes.
  • It reads as nothing from across the room. The widget shows no words (only "Set up" before pairing). Notifications are deliberately vague.
  • No accounts. No email, no password, no server of ours. Everything stays inside the couple's own iCloud.
  • Pairing is physical. The only way to pair is to hold both phones together and scan a QR code. There is no way to find or reach a stranger.
  • Free, with a Tip Jar. Every feature is free. An optional Tip Jar (three consumable tips) lives inside the app. Paying changes nothing — and the app says so.

How we built it

  • Swift / SwiftUI, iOS 17+. The app itself is setup-only; the product is the widget.
  • WidgetKit interactive widgets + App Intents for the two-tap arming flow, with the arming state shared through an App Group container so the widget and the app agree.
  • CloudKit only. A shared record zone per couple, one Signal record plus a handful of Ping records that exist solely to drive silent push notifications. No custom backend, no third-party analytics.
  • RevenueCat powers the Tip Jar. It is isolated in its own Swift package target (TacitTip) so the SDK never links into the widget extension. We call it with an anonymous ID only, never logIn(), and no signal data is ever passed to it — RevenueCat sees Apple's receipt and nothing else. This let us keep App Privacy honest: the only disclosed data type is Purchase History.
  • XcodeGen + fastlane + a local asc CLI for a fully scripted release path, with the human kept in the loop only at the four gates (requirements, plan, verification, release).
  • Built almost entirely by an AI engineering team (Claude Code sessions) coordinated through GitHub Issues, with a human approving each gate.

Challenges we ran into

  • A widget that must never lie. WidgetKit cannot promise timely redraws, and a widget extension can read but not reliably write shared state. We designed the arming flow so that every stale-state case fails closed (shows off) rather than open, and verified the partner's state right before confirming a match so neither phone shows a false "Both of you, today".
  • Privacy as a hard constraint, not a setting. Vague notification copy, no text on the widget, no server — each of these removed a whole class of features we would otherwise have shipped.
  • Shipping the first IAP. A consumable's first submission must ride along with a new app version, so the Tip Jar could only go live with 1.0.2, after 1.0 and 1.0.1 were already in the store.
  • Keeping a large AI-built codebase honest. We ended up writing our own lint rules (comment hygiene, citation drift, forbidden shortcuts, pipe-safe test helpers) because "the test says green" turned out to be the most dangerous sentence in the project.

Accomplishments that we're proud of

  • Shipped 1.0 on August 16, 2026, two updates since, and a working RevenueCat purchase flow — during the Shipaton window.
  • An app that stays useful with zero stored data and zero accounts.
  • A release pipeline where a single command archives, uploads, stages metadata in two languages and validates readiness.

What we learned

  • Restraint is a feature. Every time we asked "what should we add?" the better answer was "what can we refuse to know?"
  • A monetization layer can be added without touching the privacy story, if it is isolated by construction.
  • Machine-checked rules beat written rules: the moment a guideline became a lint, it stopped being violated.

What's next for Tacit

  • Real-device regression on the App Store build (pairing, signals, notifications).
  • Closing the remaining widget edge cases (stale mirrors, missed pushes).
  • Localization and a short App Preview video — the two things we deliberately left out to ship on time.

Built With

  • app-intents
  • cloudkit
  • ios
  • revenuecat
  • storekit
  • swift
  • swiftui
  • widgetkit
Share this project:

Updates

Submission history