After building our first production iOS application—the Crown & Coil Booking app for a premium natural hair studio in Tacoma—we faced a question every engineering team eventually confronts: How do we make the second app faster, cheaper, and more reliable than the first?
Most teams write a retrospective, file it in Confluence, and forget it. We built a database.
A 17-table, 856-record SQLite database that captures every activity, task, requirement, tool, build run, error, dependency, design decision, and lesson learned from the entire iOS development lifecycle. The result isn't documentation—it's an automation blueprint.
Why a Database and Not a Document
Retrospective documents are write-once, read-never. They capture narrative but not structure. When you need to answer "What dependencies broke the build last time?" or "Which views are stubs that Apple will reject?", you need SQL—not prose.
Our ios_development_history.db is queryable. Every fact is a row. Every question is a SELECT.
-- What files do I need for a new booking app?
SELECT file_path, category, purpose
FROM source_files
WHERE project_id = 'crown_coil' AND is_stub = 0
ORDER BY category, file_path;
-- Returns 79 production-ready Swift files with exact purposes
-- What errors will I hit and how to prevent them?
SELECT error_message, root_cause, fix_description
FROM build_errors;
-- Returns 6 unique errors with root cause analysis
The Schema: 17 Tables That Capture Everything
The database isn't a flat list. It's a relational graph of the entire iOS development lifecycle.
| Table | Records | What It Captures |
|---|---|---|
projects | 4 | All iOS apps: Crown & Coil, SBB Companion, Aura, CEO Avatar |
requirements | 579 | Every requirement across all projects, with status and traceability |
source_files | 105 | Every Swift file: purpose, types, imports, stub status, generation method |
build_runs | 11 | Every compilation attempt: status, error count, duration, simulator |
build_errors | 6 | Every unique error with root cause analysis and fix description |
dependencies | 5 | SPM packages: versions, products, known issues |
automation_tools | 16 | Every script and tool: Ruby, Python, XcodeGen, Fastlane |
design_decisions | 10 | Architecture choices with rationale and alternatives considered |
api_endpoints | 10 | Backend routes from server.js mapped to iOS service files |
data_models | 7 | Swift↔SQLite parity tracking for every model |
timeline_events | 50 | Chronological history of every file creation, build, fix, milestone |
appstore_compliance | 15 | Apple Review Guidelines checklist with evidence |
brand_design_system | 13 | Color tokens, fonts, effects with CSS↔Swift mapping |
testing_coverage | 4 | Test files, pass/fail status, known issues |
performance_checks | 6 | TODO scans, accessibility label coverage over time |
lessons_learned | 15 | What worked, what failed, and what to change |
Anatomy of a Build Failure Timeline
One of the most valuable tables is build_runs. It tells the story of 11 consecutive failed builds—each one teaching us something different.
Build # 1 | FAILED | 0 errors | 61.7s ← Dependency resolution timeout
Build # 2 | FAILED | 0 errors | 60.8s ← Same timeout
Build # 3 | FAILED | 0 errors | 60.8s ← Same timeout
Build # 4 | FAILED | 0 errors | 60.7s ← Same timeout
Build # 5 | FAILED | 0 errors | 60.7s ← Same timeout
Build # 6 | FAILED | 6 errors | 10.2s ← First real compile errors!
Build # 7 | FAILED | 6 errors | 2.4s ← Same 6 errors
Build # 8 | FAILED | 4 errors | 3.8s ← Fixed 2, discovered 4 remaining
Build # 9 | FAILED | 3 errors | 3.3s ← Fixed ApplePay delegate
Build #10 | FAILED | 3 errors | 2.6s ← Persistent redeclaration
Build #11 | FAILED | 5 errors | 2.1s ← Regression in ProfileView
Builds 1–5 all timed out at ~61 seconds during SPM dependency resolution. Firebase and GoogleSignIn are large SDKs. The lesson: always pre-resolve dependencies with swift package resolve before compiling.
Build 6 was the breakthrough—the first time we got past resolution into actual compilation. Six errors surfaced immediately. Each one is now cataloged with root cause analysis:
Error: invalid redeclaration of 'Appointment'
Root Cause: fix_all.py generated a stub Appointment.swift that conflicted with the real model in Models/Appointment.swift.
Prevention: Before generating stub files, always check if a real implementation already exists. Maintain a file registry.
The Design Decision Record
Every architectural choice is stored with its rationale and the alternatives we evaluated. This isn't about being right—it's about being reproducible.
SELECT title, rationale, alternatives_considered
FROM design_decisions WHERE project_id = 'crown_coil';
-- "XcodeGen for Project File Management"
-- Rationale: .pbxproj is notoriously difficult to merge. YAML is
-- human-readable and git-friendly. Enables regenerating project.
-- Alternatives: ["Manual Xcode", "Tuist", "Bazel"]
-- "Firebase for Backend"
-- Rationale: Rapid development, no server management, real-time
-- sync, built-in auth providers.
-- Alternatives: ["Custom Node.js", "Supabase", "AWS Amplify"]
-- "Glassmorphism Visual Effects"
-- Rationale: Matches web CSS (rgba(255,255,255,0.03) + blur(20px)).
-- Alternatives: ["Flat design", "Neumorphism", "Material Design"]
Web→iOS Feature Parity Tracking
The requirements table holds 579 items across 26 categories. For the Crown & Coil app alone, we decomposed the entire web platform—book.html, server.js, db.js, styles.css—into 543 specific, queryable requirements.
| Category | Count | What They Cover |
|---|---|---|
| UI Components | 110 | Every SwiftUI view mapped to its web HTML counterpart |
| App Store Compliance | 105 | Apple Review Guidelines 1.x–5.x + metadata |
| Data Layer | 105 | CoreData schema, sync, migration, caching |
| Developer Practices | 100 | Testing, CI/CD, documentation, code quality |
| Feature Modules | 48 | Booking, referral, waitlist, loyalty, live stream, etc. |
| Booking Flow | 10 | 5-step wizard: tier→pricing→intake→calendar→confirm |
| Models | 6 | Designer, Customer, CrownPass, Appointment, Wallet, PromoCode |
| + 19 more categories | 95 | Services, security, analytics, localization, accessibility… |
Brand Design System as Data
We didn't just screenshot the CSS—we stored every design token as a row with its web value and its Swift equivalent. This means the next app can programmatically inherit the entire visual identity.
SELECT token_name, css_value, swift_value, usage_context
FROM brand_design_system WHERE token_type = 'color';
-- crownGold | #C9973A | Color(hex:"C9973A") | Buttons, highlights
-- crownDark | #0A0D14 | Color(hex:"0A0D14") | Backgrounds
-- crownCream | #FAF5EC | Color(hex:"FAF5EC") | Text on dark
-- glassBg | rgba(…,0.03) | .ultraThinMaterial | Card backgrounds
Fonts, animations, spacing, effects—all stored the same way. When spinning up Aura Bookings (our white-label booking app), we query this table, swap the color palette, and the design system is bootstrapped in seconds.
Lessons Learned: The Highest-Impact Table
The lessons_learned table is arguably the most valuable. Each row has a lesson_type (success, failure, recommendation), an impact rating, and a concrete action_item for next time.
Stripe SDK Missing from SPM
StripeService.swift was generated with import Stripe but the SDK was never added to Package.swift. This blocked every single build.
Duplicate Type Declarations
fix_all.py generated stub files that conflicted with real implementations. The "invalid redeclaration" error persisted across 6+ builds.
36 Feature Stubs
36 feature modules were generated as single-line Text("Feature") views. Apple Guideline 2.1 rejects apps with inactive screens.
XcodeGen for Project Files
Using project.yml + xcodegen eliminated .pbxproj merge conflicts entirely. Clean regeneration from YAML is invaluable.
SQLite Requirements Database
Tracking 543 requirements in SQLite enabled automated gap analysis and in-app compliance views. Far superior to markdown checklists.
Build Early, Build Often
First build attempt was late in development. All 11 failed. Run xcodebuild after every batch of file generation.
How This Powers the Next App
When we start Aura Bookings—our white-label booking platform—the database becomes the factory specification:
- Query
source_filesto generate the exact directory structure and file manifest - Query
dependenciesto writePackage.swiftwith all required packages on day one - Query
data_modelsto scaffold Swift models with correct Codable conformance - Query
build_errorsto pre-check generated code against known failure patterns - Query
brand_design_systemto bootstrap the theme with swapped color tokens - Query
automation_toolsto know which Ruby scripts to run and in what order - Query
appstore_complianceto build the pre-submission checklist from the start - Query
lessons_learnedto avoid every mistake that cost us 11 failed builds
The database is the institutional memory of the engineering team. It doesn't forget, it doesn't paraphrase, and it answers in milliseconds. Every future app inherits the full weight of every decision, every failure, and every fix that came before it.
Technical Implementation
The system is three files:
ios_development_history_db.py— Creates all 17 tables and seeds 277 records of project data, source files, tools, decisions, timeline events, and lessonsmigrate_requirements.py— Imports 579 requirements from three existing project databases into the unified masterios_development_history.db— The 320 KB SQLite artifact: portable, versionable, queryable from any language
The entire database can be regenerated from scratch in under 2 seconds. It's idempotent, deterministic, and version-controlled alongside the source code.
cd databases/
python3 ios_development_history_db.py # Create + seed 17 tables
python3 migrate_requirements.py # Import 579 requirements
# Done. 856 records. 320 KB. Ready to query.
We don't build apps from memory. We build them from data. And now, every app we ship makes the next one better—automatically.
← Back to Engineering Narratives