Case study

StillKoi

A wellness app, built end to end by one engineer.

Role
Product, design, mobile, backend, web and release
Timeline
Aug 2025 – Feb 2026
Platforms
iOS, Android and web
Status
Live on both stores, maintenance paused
Box breathing session
StillKoi home screen with a mix playing
Per-sound volume control

01The brief

One app instead of four.

People who use sound to sleep or focus usually juggle several apps: one for rain and white noise, one for breathing, another for meditation, another for habits. I wanted a single, complete place for all of it.

The core bet was the mixer. Instead of picking a pre-made track, you build your own environment, layer by layer, and the rest of the app (breathing, rest sessions, guided paths, rituals) grows around that.

02Approach

Press play first.
Ask questions later.

  1. 01

    Anonymous first

    The app creates a local identity on first launch, so the mixer, breathing and rest sessions work with no sign-up. Creating an account later migrates mixes, sessions, rituals and progress to the server in the background.

  2. 02

    Local first, server behind it

    Feature-specific Zustand stores persist to AsyncStorage, with tokens in SecureStore. Signed-in users sync through a REST API backed by Supabase Postgres, so the app never waits on the network to start playing.

  3. 03

    Hybrid media

    Core sounds ship inside the app for an instant start. Longer guided audio and rest videos stream from Supabase Storage through signed URLs, then stay cached on the device with LRU eviction and fallbacks.

03Architecture

Three codebases, one product.

A React Native app that works on its own, an API that takes over once you sign in, and a website that brings people in.

StillKoi system architectureThe mobile app stores data locally and talks to an Express API on Fly.io once the user signs in. The API uses Supabase Postgres and Storage, and OpenAI for mix suggestions. Media streams from Supabase Storage through signed URLs and is cached on the device. Sentry and PostHog monitor the app. The Nuxt website on Vercel links to both app stores.media streamed by signed URL, cached on deviceMobile appExpo SDK 54 · React Native · TSZustand feature storesAudioSessionManagerAsyncStorage · SecureStoreBundled + cached mediaAnonymous identityREST APIExpress · TypeScript · Fly.ioJWT auth · bcryptRoutes → controllers → servicesRate limits · validationAsset manifest · signed URLsAnonymous → account migrationSupabase Storageguided audio · rest videosprivate bucketSupabase Postgresusers · mixes · sessions · ritualsXP · badges · notificationsSentry · PostHog · RevenueCaterrors · opt-in analytics · billingOpenAImix suggestionsWebsite · Nuxt 4 · VercelEN/FR blog · SEO · store linkswhen signed in

04Hard problems

The bugs that only
real phones find.

Silent audio on Android

Problem
Moving from the mixer to a guided session could leave players reporting "playing" with nothing routed to the speaker. Logs showed AudioTrack failures clustered around the handoff, because Android audio focus is released asynchronously.
Fix
An AudioSessionManager that owns playback: it stops the active source, waits for focus to be released, then grants ownership to the next one, with an explicit create, load, play, stop and remove lifecycle for every player.

Keeping data across sign-up

Problem
Anonymous users build up mixes, sessions, XP and rituals locally. Signing in must keep all of it, without creating duplicates or leaving private data behind on the device.
Fix
User-scoped storage keys, typed migration payloads, a background migration after authentication, and cleanup of the anonymous records only once the server confirms the migration succeeded.

One user leaking into the next

Problem
After logging out and in as someone else, cached profile, progress and notifications could briefly show the previous person's data. User state lived across many stores, services and storage keys.
Fix
An audit of every store, singleton and key, then a central LogoutService that stops audio, resets each store and service, and clears user-scoped storage before the next account loads.

Media that would not load

Problem
Remote audio and video failed intermittently: expired signed URLs, stale caches, and IDs that no longer matched between the app registry, the API manifest and the storage paths.
Fix
A versioned asset manifest served by the API, a storage-provider abstraction, batch signed-URL generation, path sanitisation, and tests that compare frontend IDs against backend paths.

05At a glance

44
sounds, 10 quick mixes, 5 breathing techniques
48
blog articles in English and French
2
stores, zero rejections
1
person
React Native 0.81Expo SDK 54TypeScriptZustandexpo-audioexpo-videoReact Navigation 7Node.jsExpressSupabasePostgreSQLJWTOpenAIFly.ioNuxt 4Vue 3Tailwind CSSVercelEAS BuildSentryPostHogRevenueCatJestMaestro

06Outcome

Shipped.
Not yet found.

StillKoi passed review on the App Store and Google Play without a rejection and has been live since January 2026. The product works. Distribution did not: Google Play lists 10+ installs and six 5-star reviews, and the subscription tier, fully wired through RevenueCat, was never switched on.

The engineering held up. The plan for reaching people was the part that was missing.

What I would do differently

  • Ship a narrower first version. The mixer alone was the product. Paths, quests, XP, badges, community and integrations multiplied the testing surface before anyone was using the core.
  • Start distribution on day one. An audience, a waitlist and a content plan should have grown alongside the code, not after launch.
  • Cut what is not ready. The Spirit Hub's "breath analysis" estimates from session length rather than analysing audio. It should have waited until it was real.
  • One source of truth per kind of state. Playback, cache and progress each grew several owners before their boundaries were drawn. Defining them up front would have prevented the hardest bugs above.
  • Test on a device matrix early. The worst issues only appeared on specific Android phones. Automated end-to-end coverage of audio handoff and account migration should have come first, not last.

Credits

Product, design and engineering by Manal Gharrabou.

Website design and build by torsn.