It’s been a whirlwind two weeks on my favorite project: my wife’s app. Life threw a curveball when my trusty mini-split air conditioner decided to retire after seven years of faithful service, right as development was heating up. It’s a fitting metaphor for what I’ve been learning: sometimes the most basic features—like an upload button—hide surprising complexity underneath.

The Upload Button That Ate Two Weeks

Aiming merely to let my wife upload new-show photos triggered a full architectural overhaul. The initial client-side approach was fragile and limited. I realized I needed to shift more responsibility to the server and, hardest of all, learn to let go of controlling everything from the iOS app.

The new system is now a fully-automated, server-managed client-involved pipeline:

  • The iOS app captures user intent and sends raw photos to Firebase
  • Cloud functions automatically process everything
  • AI analyzes photos and generates item details
  • Export-ready spreadsheets are created for platforms like Poshmark

The performance gains have been remarkable:

  • 300GB of photos process in ~3 minutes - from 15 minutes
  • 50 items get full AI-generated details in ~4 minutes - with absolutely no actions needed from the user
  • Complete spreadsheets ready for upload immediately after

The photo slinging is -by far- the most intensive part of the process, but we now guarantee reliable, fast uploads from the phone. Previously, we had a ~15 minute process including literally jiggling the Photos app to get iCloud updates to sync so we could get the JPG version of the photos.

The Technical Architecture

graph LR
  %% High-level upload → AI → export flow
  webApp["🖥️ Web App"] 
  iOSApp["📱 iOS App"] 
    -->|1\. Upload Photos| UploadBatch["📦 Upload Batch Service"]
  UploadBatch 
    -->|2\. Firestore Trigger| AIService["🤖 AI Processing Service"]
  AIService 
    -->|3\. Firestore Trigger| ExportService["📁 Export Generation Service"]
  ExportService 
    -->|4\. Download Links| iOSApp
  ExportService 
    -->|4\. Download Links| webApp

The refactor introduced three core server-driven workflows:

  1. Photo Ingestion: Batch uploads with progress tracking
  2. AI Detail Generation: Automatic triggers when uploads complete, with custom retry logic for failed requests
  3. Data Export: On-demand generation of platform-specific formats

Key improvements include:

  • Resilient AI processing with automatic retries beyond standard cloud function limits
  • Data denormalization for faster access (AI results stored directly in Item documents)
  • Atomic operations through Firestore triggers ensuring reliability

The $20 Nap: A Lesson in CI/CD

Here’s my confession: I introduced a recursive trigger in GitHub Actions and went for a Saturday nap. That one-hour rest cost me $20 in runaway builds. It was the wake-up call I needed (pun intended) to completely rethink our CI/CD approach.

Changes made:

  • All iOS builds now run locally (grounded from the cloud!)
  • Concurrency controls prevent duplicate workflow runs
  • Lefthook integration for pre-commit and pre-push validations
  • Enhanced secret scanning with both Trufflehog and Gitleaks
  • Dependabot reduced to monthly to minimize automated PR noise

The philosophy shift: do more locally for “free” (plus the cost of a water break) before anything hits the cloud.

Progress Through Deletion

Many recent PRs have deleted more code than they’ve added. This isn’t regression—it’s refinement. Moving from “technically working” to “actually usable” often means removing clever complications in favor of simple, reliable patterns.

The app is becoming something my wife can actually use in her workflow, not just a technical demonstration. That distinction matters more than any architecture diagram.

A Small Identity Victory

Amid all this technical progress, Apple Support finally resolved my 8-year identity crisis. My developer account is no longer under my client’s name—though Apple’s interpretation of my business name (IN ALL CAPS, ignoring my D&B profile) wasn’t quite what I expected. Still, progress is progress, even when it comes in unexpected forms.

Looking Ahead

These two weeks taught me that the simplest features often require the most sophisticated solutions. An upload button isn’t just an upload button—it’s a promise to handle someone’s business data reliably, efficiently, and intelligently. And sometimes it’s debounced based on client managed status documents triggering automatic AI processing so she doesn’t have to waste clicks.

The app is transitioning from a technical exercise to a real tool. My wife’s workflow remains the north star, and every architectural decision serves that goal.


Building this app has been an incredible journey of technical growth and practical problem-solving. If you’re interested in following along or have questions about the architecture, feel free to reach out. The best software is built with real users in mind.