Skip to content
Inflexion

Case study · Nuppo and Šeptem

How we took two macOS apps from the first line of code to release, and what went wrong on the way.

two macOS apps · Swift without Xcode · free release · updates via Sparkle

40 days

from first commit to first release, counted in calendar days: 40 for Nuppo, 39 for Šeptem. The release dates are on GitHub.

day with commits release

Each square is one day from 22 Sep to 2 Nov. Lit squares are days with at least one commit: 11 for Nuppo, 8 for Šeptem. In between are whole weeks without one.

  • 2apps, free to download from public GitHub releases
  • $0to download both. We don't pay for the Apple Developer Program.

You can check both on the GitHub release pages and the apps' websites.

The premise

Both apps are ours. So are the decisions, bad ones included.

Nuppo and Šeptem are two macOS apps the studio built for itself. No client, no brief, no deadline but our own. Both are free to download: Nuppo since 1 November, Šeptem since 2 November.

On client work, decisions can rest on the brief. Not here. We chose the scope, the free release, the transcription model and the look of the icon, and nobody else will answer for any of it. So this study also shows how we decide when a client leaves the decision to us.

We use modern technology to work and iterate faster. But expert human oversight stays on every decision, and every output is checked on a real machine. The sections below show where that came into play.

There's no client to hide behind.

Nuppo app icon
Nuppo 22 Sep → 1 Novcommits on 11 days · now on 0.9.0
Šeptem app icon
Šeptem 24 Sep → 2 Novcommits on 8 days · now on 0.10.0
Nuppo and Šeptem: first commit, release and current version

The starting point

Losing track of screenshots, and dictating Czech with the commas in.

Nuppo started with one problem: the studio's owner kept losing track of screenshots, new and old. The first version was a shelf in the Mac's notch that collected them, so you could drag each one wherever it was needed.

After that, it grew as needs came up. Downloaded files and notes on the first day; later the song that's playing, a timer, a list of local servers and the status of AI agents running in the terminal. A shelf for screenshots turned into a practical helper: more within reach, less hunting around.

Šeptem solves a different problem: dictating in Czech, with commas, names and English in mid-sentence. General-purpose dictation tools don't give Czech much attention, and our analysis on 24 September saw room to do better. Both are tools we wanted for ourselves.

We started with what annoyed us.

Nuppo · recorded in demo mode
Nuppo · a screenshot from the notch shelf straight into a terminal. Recorded from the real app.
Demo · nothing is recorded
Spoken

so um uh send me that by Friday

Inserted

Send me that by Friday.

Inserted: Send me that by Friday.

Šeptem · simulation: hold the button, or focus it and hold the space bar. The filler words drop out, and a capital letter and a full stop go in.

Before and after

The first commit had 1,045 lines and a different name.

On day one, Nuppo was called NotchShelf: 11 files, 1,045 lines, a panel in the notch, screenshots and notes. Šeptem started two days later and was usable straight away. Its first commit had 29 files, 2,726 lines and dictation on the fn key.

Today they're two released apps with websites and automatic updates. The first commits had almost none of that: no name, no icon, no way to reach users. What survived is the core idea.

From first commit to product.

first commit
  1. 22 SepNotchShelf+1,045 lines11 files · notch panel, screenshots, notes
  2. 24 SepŠeptem+2,726 lines29 files · dictation on the fn key
Figures from the git history. The repositories are private, so you can't check them.
Today · releasedNuppo app iconŠeptem app icon
  • name and icon
  • website
  • release on GitHub
  • automatic updates
What the first commit lacked and the apps have today

Phase 01Scope

The plan was bigger than the result. What's left is what held up.

The analysis for Šeptem, written on 24 September, planned five phases: more cloud transcription services, live transcription as you speak, an offline model, and custom commands including translation. The code ended up with one transcription service plus a fallback model, and history stored in files rather than a database.

Nuppo was meant to be paid: a licence, a trial period, a waiting list, notarisation. All of it was built. Then, on 1 November, we decided to make it free. It's a side project and a working tool, and we didn't want to pay Apple's annual fee or deal with selling it.

Šeptem, for its part, has no offline model, and the transcript arrives only after you let go of the key. Without internet, the recording is saved and waits. We'd rather say so than promise more than we can deliver.

We know how to cut.

Šeptem@@ plan, 24 Sep → reality @@

  1. Cut: More cloud transcription services (ElevenLabs, Soniox)
  2. Cut: Offline Parakeet model
  3. Cut: Live transcription as you speak
  4. Cut: History in a SQLite database
  5. Cut: Custom commands and translation
  6. Kept: Groq Whisper transcription with a fallback model
  7. Kept: History stored in files

5 deletions(−), 2 insertions(+)

Nuppo@@ paid → free, 1 Nov @@

  1. Cut: App Store sales (the sandbox would have blocked reading Desktop and Downloads)
  2. Cut: Developer ID, notarisation and a DMG
  3. Cut: Licence and a 14-day trial
  4. Cut: Waiting list
  5. Kept: Free release, signed with our own certificate

4 deletions(−), 1 insertion(+)

Phase 02Choosing the tools

Native Swift without Xcode, because fn and the notch won't work any other way.

Both apps are written in Swift and built from the terminal, without Xcode. Not out of ideology: the fn key, Accessibility and text insertion only work reliably in native code. The cost: we had to draw Nuppo's Liquid Glass look ourselves, because the native interface wouldn't compile with our SDK version.

Vendors measure their models on Wikipedia text read aloud. We dictate casually, with anglicisms, names and numbers. So we dictated 35 sentences in our own voice and had them transcribed.

Two Whisper models on Groq made it to the final comparison. The larger got 11.7% of words wrong and responded in 0.85 s; the faster got 12.2% wrong in 0.55 s. Each user enters their own key, so Šeptem runs no server of ours and none of your voice reaches us.

A measurement decided it, not an advert.

Transcription measured on 35 sentences in our own voice
ModelWord error rateMedian latency
Whisper large-v3 (primary)11.7%0.85 s
Whisper large-v3 turbo (fallback)12.2%0.55 s
Groq, 35 sentences in our own voice, edited recordings. The larger model is more accurate, the smaller one faster. The scales don't start at zero.

Phase 03Brand

The name, the icon and the typeface each took several rounds.

Nuppo was renamed before release. NotchShelf, Nupo and Pluck all clashed with existing products. The rename cost a data migration and one rule: the identifier macOS knows the app by kept its old value, so nobody would lose their settings.

Nuppo's icon, a little figure hanging from the notch with a screenshot in hand, went through three versions in a single day. Šeptem had three logo drafts and settled on the háček as a wave. The typeface took two rounds, first four candidates, then six serifs, and Gloock won.

You can't design a look blind. Nuppo's first Liquid Glass came out washed out and flat, and it took four rounds of screenshots to get it right.

You don't argue a brand. You choose it.

  1. Nuppo nameNotchShelfNupoPluckNuppothree names clashed with existing products
  2. Nuppo iconthe figure hanging from the notchthree versions in one day, 23 Sep
  3. Šeptem logothe háček as a wavethree drafts
  4. Šeptem typefaceGloocktwo rounds: four candidates, then six serifs
#ff5a28 Šeptem vermilion · Gloock typeface
Nuppo app icon#ff9f5a Nuppo peach · accent colour adjustable in the app

Phase 04Build

The recording is on disk before it goes anywhere. That's all there is to reliability.

Šeptem is a chain of five steps: you hold fn, the app records, the audio is written to disk and sent off for transcription, and the finished text is inserted wherever your cursor is. The order is what matters. The recording is on disk before anything leaves the Mac.

That order takes care of whatever goes wrong. If the network drops, the recording waits and gets transcribed later on its own. If the insert misses the text field, the text stays on the clipboard and a card offers Copy. If it lands in the wrong place, fn+V inserts it again.

The model is wrapped in filters. Fed silence, Whisper invents sentences like “Subtitles by JohnyX”, and the app throws them away. Then the text gets its commas and Czech typography. Modern tools help us write code faster, but a person decided how much the app is allowed to lose: nothing.

No dictation gets lost.

  1. Hold fnthe app starts recording — done
  2. Saved to diskbefore anything leaves the Mac — done
  3. TranscriptionGroq Whisper, your own key — done
  4. Filter and tidyinvented sentences out, commas in — done
  5. Insertwherever your cursor is — done
The text is wherever your cursor is. The whole chain ran.
One recording, step by step. The switches only simulate a failure; nothing is sent anywhere.

Phase 05The test

Real use showed what the code didn't.

We started using Šeptem on day one. AirPods switch to call mode and the sound quality drops, so the app records from the built-in microphone. Later we had to stop the system from picking AirPods or an iPhone as the default input. On two-minute dictations Whisper also garbles stretches and drops diacritics, so the damaged parts are transcribed again.

Meeting notes were the worst. After a device was set up, the audio engine restarted about five times a second and the microphone barely recorded. And on a call in Safari, macOS suppresses echo, so the recording was missing the voice. We fixed the causes and added a warning for a muted microphone.

In Nuppo, the ⋯ button ignored clicks and more SwiftUI tinkering didn't help, so we dropped a layer down into AppKit. Full-screen detection only works for Chrome with a third signal. We don't know why, and we've noted that.

Every bug has a commit in the history.

fixes · symptom, cause, fix
  1. NuppoThe ⋯ button ignores clicks

    cause
    The SwiftUI button wasn't receiving events, and more debugging didn't help
    fix
    AppKit handles the clicks, one layer down
  2. ŠeptemRecordings with AirPods sound bad

    cause
    Bluetooth headphones switch to call mode
    fix
    The built-in microphone is the default
  3. ŠeptemDictated text vanished

    cause
    The insert missed the text field and the clipboard was restored
    fix
    Text that fails to insert stays on the clipboard, with a Copy card
  4. ŠeptemThe system picked AirPods or an iPhone

    cause
    The system's default input changed
    fix
    The app keeps an eye on the default input
  5. ŠeptemLong dictations come back garbled in places

    cause
    Whisper garbles stretches of two-minute recordings
    fix
    Garbled stretches are transcribed again, separately
  6. ŠeptemMeeting notes with no voice

    cause
    The audio engine restarted in a loop, about five times a second
    fix
    Device setup without the loop, plus a check for audio dropouts
  7. ŠeptemThe voice is missing on calls

    cause
    During a call, macOS suppresses echo and sends a flat signal
    fix
    Voice processing, and a warning when the microphone is muted
  8. NuppoChrome in full screen

    status
    Detection needs a third signal. We don't know why, and we've noted that.

Release

No Apple account: our own certificate, and updates via Sparkle.

Apple charges $99 a year for the Apple Developer Program. Both apps are free and Nuppo is a side project, so we decided not to pay. The cost shows up straight away: on first launch, macOS says it couldn't check the app, and only opens it if you explicitly ask.

We do sign the apps, though, with our own certificate valid for ten years, not a one-off signature for a single build. Not for Gatekeeper's sake, since it doesn't accept that certificate, but for permissions: a signature that changed with every build would wipe access to Desktop, Downloads or Accessibility after every update.

Updates go through Sparkle, and we sign them with our own key. The chain is the same every time: code signature, ZIP with a SHA-256 checksum, update signature, version list and a GitHub release. The source code stays private. Only what you can download is public.

Free doesn't mean sloppy.

.zipSEPTEM DEV · SELF-SIGNED
  1. Signed with a certificateour own, valid for ten years
  2. ZIP and SHA-256 checksum
  3. Update signatureour own Sparkle key
  4. Version listappcast
  5. Release on GitHub
  6. Sparkle on the user's Mac
Gatekeeper dialog on macOS 14: Šeptem can't be opened because Apple cannot check it for malicious software
What the first launch from the ZIP looks like on macOS 14. To open the app, right-click it and choose Open.

Web

Two websites with no tracking, and a one-command install.

Both sites are static, built with Astro and hosted on Vercel. No analytics and no Google Fonts; the fonts ship with the site. Nuppo's site went up two days after the first commit, Šeptem's only in the last two days before release.

Alongside the ZIP, each app offers a one-command install. The script verifies the SHA-256 checksum, installs the app and removes the quarantine flag, so Gatekeeper doesn't step in. Installing from a script off the internet isn't for everyone, so there's also a guide to doing it by hand on macOS 14 and 15.

The sites show rather than tell. On Nuppo's you can try the demo without downloading anything, and the loops were recorded from the real app in demo mode. Šeptem's has a live text-editing demo.

Preview of the Nuppo website
Loads the real site, nuppo.inflexion.cz.
Preview of the Šeptem website
Loads the real site, septem.inflexion.cz.
one-command install
curl -fsSL https://nuppo.inflexion.cz/install.sh | sh
curl -fsSL https://septem.inflexion.cz/install.sh | sh

What we don't claim

What the apps can't do, and what you can check for yourself.

Some things the apps can't do, and we say so up front. Nuppo reads English, numbers and code from images, but not Czech, because text recognition in macOS 14 doesn't support it. Šeptem needs internet access and your own key; without them it can't transcribe. Both trigger a macOS warning on first launch.

The code is private, so you can't read it. That's why this study rests on what you can check: public releases, live websites and how the apps behave. One thing we saw with our own eyes: on 2 November, a copy of Šeptem 0.9.0 downloaded from GitHub found 0.9.1 on its own and installed it.

We'd rather say it ourselves.

Verified

  • 2 Nov · Šeptem 0.9.0 found 0.9.1 on its own and installed it through Sparkle (macOS 14)
  • 1 and 2 Nov · both releases are public on GitHub
  • 2 Nov · Gatekeeper on macOS 14: opening with a right-click (Šeptem)
  • 24 Sep · transcription measured on our own voice

Limits

  • Nuppo doesn't read Czech text in images
  • Šeptem needs internet access and your own key
  • macOS shows a warning on first launch

Aftercare

The work doesn't stop at release.

Šeptem came out on 2 November as 0.9.0, and twenty-five minutes later as 0.9.1. The new icon, website and About window missed the first version, so they went straight into the second.

Then came real use. It showed that the settings held things an ordinary user shouldn't have to think about: models, the API address, language. We're tucking them into a collapsed Advanced section with a Restore Defaults button. On top of that comes a first-run guide that ticks off the microphone, Accessibility, the fn key and the API key.

And then the test we'd been waiting for. We downloaded the 0.9.0 ZIP from GitHub, opened it with a right-click and chose Check for Updates. Sparkle offered 0.9.1 with release notes, and after a relaunch the new version was running. No Apple, no app store, just GitHub and our own signature.

Release is the start, not the finish.

  1. Nuppo 0.9.0
  2. Šeptem 0.9.0
  3. Šeptem 0.9.1
  4. update from 0.9.0 to 0.9.1 through Sparkle
  5. Šeptem 0.10.0
Gatekeeper dialog on macOS 14: Šeptem can't be opened because Apple cannot check it for malicious softwareSparkle window: a new version of Šeptem, 0.9.1, is available; you have 0.9.0Sparkle window “Updating Šeptem”: Ready to Install
Demo: the Sparkle windows are redrawn from real screenshots taken on 2 Nov (17:54 to 17:55, macOS 14), as they looked in 0.9.0.

In closing

Ship your own product and you learn what a client project really costs.

Both apps followed the same approach: an idea born of our own need, a small scope, measurement where possible, release, then fixes driven by real use. None of that is new. What's interesting is that you can see it through without everything that usually makes a release easier: no Apple, no notarisation, no reseller.

We threw away a finished licensing system, a finished waiting list and part of Šeptem's plan. Discarded work costs less than a discarded release, but it still costs. And the business model has to be settled before you build the technology behind it. That lesson goes into every client project.

Modern tools help us move faster. But people still judge what to build, what to cut and when to admit that something hasn't been verified. Clients get advice, not a defence of work already done.

Two apps that prove we see products through.

Two projects. One signature.

Method

What you won't find in this study.

Download or user numbers, revenue, comparisons with competitors, or the source code, which is private. What you can check for yourself: the releases and their dates, the live websites and how the apps behave. The figures from the git history (40 calendar days, 1,045 and 2,726 lines) can't be checked, because the repositories are private. We'd rather say so up front.

Want a product of your own instead of another business card?

Tell us what you need built, or first work out what a system of your own would save you.