Work / Gleam
Gleam
Cross-platform AI recording and sharing app
Mobile app · spiritual journaling
Gallery

Cross-platform AI recording and sharing app for iOS, Android, and web — offline-first audio capture with searchable transcripts and AI-assisted reflection.
Product
Gleam helps people capture, transcribe, and revisit audio recordings in one place. Recordings stay searchable and organized so insights surface again at the right time, with AI-assisted reflection and daily recaps that connect back to a personal history instead of starting from zero each day.
Tech stack
expo, next, storybook, storybook-rn) and 5 shared packages@powersync/attachments, TUS resumable uploads to Supabase Storageexpo-av for recording (migration path to expo-audio), react-native-track-player for background-capable playbackplatform, flow, stage, error_category, retry_count, network_state)Unique challenges solved
Shareable content without a download wall
Built a sharing + claim loop that lets anyone open a shared recording in a browser without the app, then converts the share into a signup. Shared gems use a sharing_token (bypassing RLS for reads), and a tRPC claim route ports the gem into the new user's account on signup. The full funnel (share_modal_opened → shared_gem_viewed → gem_claim_succeeded) is measured in PostHog, turning every share into an attributable growth loop.
Offline-first audio capture that doesn't lose recordings
Recordings persist in PowerSync SQLite and upload through a TUS resumable queue to Supabase Storage. They survive force-quit, app restart, and network loss, then reconcile on reconnect. Retries are non-destructive with a 30s → 2m → 10m → hourly ladder capped at 10 attempts. Per-file and global retry actions are both exposed in UI with copy that matches behavior.
Dual-ID reconciliation (client UUID ↔ server bigint)
Every gem has both a client-generated UUID (client_id, stable idempotency key) and a Postgres bigint primary key. PowerSync upserts outbound with .eq('client_id', op.id) so offline-created gems don't collide on sync. Web resolves /gem/:id by bigint; native can resolve by either. This is what lets offline creation and stable shareable URLs coexist.
Silent production bug in the upload pipeline
A PowerSync watch query used NEW.audio_url / OLD.audio_url — valid only inside SQLite triggers, not plain selects — so the watch silently returned nothing and uploads never started for affected devices. Diagnosed from the "gem exists in DB but no file in storage" symptom, fixed with SELECT audio_url FROM gems WHERE audio_url IS NOT NULL, and wrote a support script so CX could triage existing affected users.
Eventual consistency between DB trigger and storage object
The Supabase INSERT trigger fires before the audio file finishes uploading to Storage, so the downstream Pipedream workflow can arrive before the object exists. Downstream treats "object missing" as retryable (backoff) rather than terminal, and uses client_id as the idempotency key so retries and replays don't double-process.
Deep-link + Meta Ads attribution for new installs
Meta ad → Branch link (with $deeplink_path) → install → Branch SDK tracks attribution on first launch, even for unauthenticated users. Expo Router's +native-intent.tsx intercepts Branch tracking IDs before the router tries to match them as routes (prevents Unmatched Route errors). Purchase events flow server-side via RevenueCat → Branch → Meta and RevenueCat → Meta CAPI — no client-side pixel, no duplicate events.
Consent- and ATT-compliant tracking
iOS ATT prompt gates Meta CAPI device-identifier collection. A marketing-consent atom gates all tracking SDKs. Shared telemetry helpers scrub PII by default (no emails, tokens, cookies, or session payloads) so analytics don't leak sensitive fields.
Observability as a product surface
A shared event taxonomy (audio_upload_failed, auth_session_restore_failed, gem_claim_failed, etc.) plus Sentry tag routing (flow=audio → mobile reliability owner, flow=share → growth, flow=api → backend) means product, growth, and reliability each get their own alerts from the same event bus.
Risk-managed SDK migration planning
Two-track plan for expo-av → expo-audio (deprecated in SDK 54, removed in SDK 55): Track A hardens the current stack, Track B swaps the dependency behind parity tests covering start/pause/resume/stop, duration/metering, queue persistence across app restarts, and the downstream readiness contract. Explicit data-continuity constraint: migration must not silently drop local-only queued attachments from existing users.