iOS2026On TestFlight — App Store release in preparation

Eily

A meeting assistant that understands the conversation on your phone, in real time, and turns it into tracked work.

Eily on an iPhone on a table: a meeting recording with the brand glow while live cards for tasks, decisions and questions appear.
The app on an iPhone 17 Pro simulator with scripted mock services: a meeting recording, live cards surfacing while people talk. The phone is drawn around the recording; the room is generated.
52k
lines of Swift, plus 25k of tests
1,585
tests
3
AI backends behind one interface, two of them on-device
0
bytes of audio sent to a server by default

At a glance

  • On-device by default Apple Foundation Models, Qwen 3 4B via llama.cpp, or GPT
  • Live cards while you talk Tasks, decisions and questions surface in real time
  • Consent first, confirm before write Nothing is written anywhere until you say so
  • Writes into real tools Todoist, Linear, Google Calendar, Jira, Asana and more
  • Speaker diarization Core ML pyannote + WeSpeaker, on-device, per sentence
  • Token-only design system Colors, type, motion, glass — no hardcoded values

The problem

Commitments that land nowhere

Most meeting assistants give you a transcript and a summary after the fact. But the commitments made in a 1:1, an interview or a client call still don't land anywhere: they live in a Notion page nobody reopens. And the remote-only bots can't join a table with three people around it.

So Eily captures the conversation on the phone, understands it while it happens, and writes the outcomes — tasks, decisions, open questions, follow-up meetings — straight into the tools where work already lives. The audio stays on the phone unless you turn on backup.

A bot joins remote calls only The call ends Transcript, summary after the fact A page nobody reopens A phone on any table Understood live on the device You confirm nothing written before Todoist · Linear · Calendar where work already lives A meeting assistant today Eily

What I built

One microphone, four listeners

A native iOS app in SwiftUI. One AVAudioEngine tap fans audio to four consumers — a level meter for the brand glow, the speech engine, an .m4a writer, and diarization, which now labels speakers sentence by sentence while you record. Speech comes from Apple's SpeechAnalyzer or, for Russian, a 272 MB on-device GigaAM model. Understanding runs on Apple Intelligence's Foundation Models by default, with Qwen 3 4B through llama.cpp and a cloud GPT path — routed through Eily's own backend, so no key ships in the app — behind the same protocol.

Understanding — one protocol Mic One audio tap AVAudioEngine Level meter → the brand glow Speech SpeechAnalyzer; GigaAM on device for Russian .m4a writer Who is speaking pyannote + WeSpeaker, Core ML, per sentence Apple Foundation Models the default Qwen 3 4B · llama.cpp on the device, 2.5 GB GPT through the Eily backend Live cards while you talk Final pass background job text
The three backends answer the same protocol, so the rest of the app doesn't care which one answered. Live cards come out during the recording; the final pass runs as a background job that survives the app being killed.
The Eily recording screen: a meeting at 12:48 with the brand glow, and five live cards reading meeting detected, question raised, decision recorded, important moment noted and task detected.
Recording, with live cards surfacing during the conversation. They auto-dismiss after three seconds; the consent gate runs before every recording.

After the meeting the page holds the outcome, the key moments with their timestamps, and every decision, question and task as an artifact waiting for you. The Live Activity keeps the recording on the lock screen, with Pause and Stop.

The meeting page of an Onboarding Strategy Call: 2 decisions, 2 questions, 2 tasks; the meeting outcome; a low-confidence warning; key moments with timestamps; a follow-up email.The Artifacts tab: “Nothing is created until you confirm.”, then decisions marked Decided or Needs review, and open questions marked Open or Resolved.The lock screen with a Live Activity: Recording, In-person meeting, 02:27, Pause and Stop.
The meeting page — overview and artifacts — and the Live Activity, from the app, 6 September. Screens from the main branch, before the move to indigo.

Design decisions

A recording is unrepeatable

Confirm before write

Nothing is written anywhere before you confirm it — the confirm gate is the one rule in the codebase marked "sacred". Confirmed items go, each to the destination you pick, through the backend to Todoist, Linear, Google Calendar, Gmail, Outlook, Trello, Jira or Asana, with the device's own Reminders and Calendar as a safety net if the backend is unreachable.

Cards, artifacts tasks · decisions · questions · meetings You confirm the “sacred” rule Eily backend per-item routing Todoist Linear Google Calendar Gmail Outlook Trello Jira Asana Reminders · Calendar on the phone when the backend is unreachable

A token-only design system

Colors, type, spacing, radii, motion, glass — all tokens, injected through the environment. A Figma value with no token means stop and ask. Type is SF Pro, and the code converts Figma's line heights using its measured line-height ratio (1.19335). Plain line spacing would be off by 2.5–3.3 pt on every screen. The design itself has its own case.

Eily — the design case, in preparation

One glow through the whole flow

The same brand glow runs from splash to Home to Recording to Processing to Success, driven by mic level and stage progress, with a dither texture to fight OLED banding. Motion tokens have intent names — stateFlip, disclosure, celebrationPop, liveCardIn — and Reduce Motion keeps a 0.15 s cross-fade instead of a hard cut, because a hard cut hides what changed.

Slide to stop

A mis-tap must not end a meeting you can't hold again. Stopping is a deliberate gesture, and under Reduce Motion it becomes a plain button.

Fall back, keep recording

If the local model fails at selection, at preparation, or because an asset is missing, each layer falls back to the next — down to audio-only with an honest banner.

The chosen backend The next available one Audio only and a banner that says so fails fails A failure at selection, at preparation or a missing model file is a step down, never a lost meeting.

The hard parts

On-device models in real time

One inference at a time, nothing dropped

A 16.66-second inference over an opening 'hello' once swallowed the only sentence in a 25-second recording that contained a task and a meeting. LiveInferenceGate now runs one inference at a time; a trigger that arrives while it is busy waits and runs next instead of being dropped.

hello a task, and a meeting inference over “hello” — 16.66 s inference over “hello” — 16.66 s replayed: task + meeting refused — dropped refused — deferred, replayed when the slot frees One 25-second recording Speech Before With the gate 0 s 25 s

A cold model must not leave a hole

Loading an on-device model takes seconds. A separate transcription queue buffers audio while the model warms up, so the first sentence of the meeting is never lost.

On-device topic segmentation

A quantized MiniLM embedding graph — mean pooling and normalization inside the Core ML graph, Swift only tokenizes — detects topic change points, and an incremental processor only works on windows safely behind the open semantic tail. By the time you stop recording, most of the final pass is already done.

Offline-tolerant sync

Each changed meeting is flagged and pushed on its own, so one failure doesn't block the rest. The flags survive the app being killed, and the app retries on launch, on return to the foreground and after each recording. Pull uses an updated_since cursor with last-write-wins — except that anything you acted on locally always survives.

Release engineering that fails fast

The fastlane lane preflights every trap before spending minutes on an archive: a non-UTF-8 Ruby locale that makes the build tool swallow the real error, missing secrets, missing App Store Connect keys. The build number is read from App Store Connect, not kept in the project.

How it was built

Specced, planned, then built against the plan

Three of us built the iOS app: me, Andrey Ezhov and Rashit Bayazitov. 1,075 commits across all branches in fifteen weeks, 68% of them co-authored with Claude. The repo is set up for agent work: an AGENTS.md map, Cursor rules for design tokens and QA screenshots, a paste-in onboarding prompt for a new agent, and 41 specs with 36 plans beside them. 1,585 tests, 33 of them UI tests, and a documented audit of every feature as real, local, mock or stub with file-and-line evidence.

Where it stands: build 1.0 is on TestFlight, account deletion — the last requirement before external testers — shipped on 20 September.

Commits a week to the iOS app, all branches — 1,075 Jun Jul Aug upload Sep 204
The early-July peak is live understanding and the three backends; the September one is release — the TestFlight lane, account deletion, projects and People, and every screen matched to Figma.

Details

Role
Founder, product owner, lead design engineer — team of three
Stack
SwiftUISwiftDataApple Foundation ModelsCore MLllama.cppWidgetKitActivityKit
Timeline
  1. 18 Jun · First iOS commit
  2. Jul · Live understanding + three backends
  3. Aug · Integrations, widgets, Live Activity
  4. 3 Sep · 1.0 (1) uploaded to App Store Connect
  5. 20 Sep · Account deletion — the last blocker for external testing
  6. 26 Sep · Projects, People, project tabs
  7. 28 Sep · Every screen matched to Figma; indigo replaces green

Next

Your gaming PC on the TV, as a console. No desktop, no launchers, no PIN to type.

In developmentAndroid TV, webOSKotlin, C2026

CouchDeck

Your gaming PC on the TV, as a console.