Grand Diomande Research · Full HTML Reader

Stage 4: FORGE — FirstDate Architecture

**Core principle:** The deal IS the episode. A restaurant sponsorship deal produces one episode. An episode fulfills one deal. They share a lifecycle.

Embodied Trajectory Systems architecture technical paper candidate score 34 .md

Full Public Reader

Stage 4: FORGE — FirstDate Architecture

The Formula: Content Business OS

One app. Two experiences. One host, many applicants, episodic content, real money tracking.

Core principle: The deal IS the episode. A restaurant sponsorship deal produces one episode. An episode fulfills one deal. They share a lifecycle.

Role-Based Navigation

swift
// FirstDateApp.swift — role switching via isAdmin
if profile?.isAdmin == true {
    HostTabView()      // Mohamed's experience
} else {
    PublicTabView()     // Women's / audience experience
}

### Host Tabs (Mohamed)
1. Inbox — Application review (approve/pass/shortlist)
2. Pipeline — Deal CRM (Lead → Pitched → Confirmed → Filmed → Published → Invoiced)
3. Schedule — 10-week calendar grid (deal + talent + budget per week)
4. Ledger — P&L with expense/revenue categorization
5. Studio — Consent manager + pre-shoot checklist + episode content links

### Public Tabs (Women / Audience)
1. Series — Published episodes (poster cards, content links, restaurant featured)
2. Apply — Application form (photo, bio, Instagram, why-me, consent acknowledgment)
3. Spots — Restaurant catalog with "Featured in Episode X" badges
4. Status — Application status tracker (pending → reviewing → selected/not selected)
5. Chat — DMs with Mohamed (unlocked after selection)

Data Models (New + Modified)

New Models

swift
// Models/Application.swift
struct Application: Codable, Identifiable {
    let id: String
    let profileId: String          // FK → fd_profiles
    var status: ApplicationStatus  // pending, reviewing, shortlisted, selected, passed
    var whyMe: String              // Free text: "Why should Mohamed take you on a date?"
    var instagramHandle: String    // Required
    var videoIntroUrl: String?     // Optional hosted video link
    var consentAcknowledged: Bool  // Acknowledged filming consent info
    let episodeId: String?         // Linked when selected for an episode
    var reviewedAt: String?
    let createdAt: String
}

enum ApplicationStatus: String, Codable {
    case pending, reviewing, shortlisted, selected, passed
}

// Models/Episode.swift — unified with Deal
struct Episode: Codable, Identifiable {
    let id: String
    var weekNumber: Int            // 1-10
    var title: String              // "Week 3: Coconut Grove"
    var status: EpisodeStatus      // planning, casting, scheduled, filmed, editing, published

    // Deal fields (restaurant sponsorship)
    var restaurantId: String       // FK → fd_restaurants
    var dealTier: DealTier?        // comp_meal, logo_placement, title_sponsor
    var dealValue: Double?         // Cash value of sponsorship
    var dealContactName: String?
    var dealContactEmail: String?
    var dealNotes: String?
    var pitchedAt: String?
    var confirmedAt: String?
    var invoicedAt: String?
    var paidAt: String?

    // Talent fields
    var applicationId: String?     // FK → fd_applications (selected woman)
    var talentName: String?
    var talentInstagram: String?

    // Production fields
    var scheduledFor: String?
    var timeSlot: String?          // brunch, lunch, dinner
    var filmedAt: String?
    var consentRecordId: String?   // FK → fd_consent_records

    // Content fields
    var tiktokUrl: String?
    var instagramUrl: String?
    var youtubeUrl: String?
    var thumbnailUrl: String?
    var publishedAt: String?

    // Metrics (updated manually or via content platform APIs later)
    var viewCount: Int?
    var likeCount: Int?
    var commentCount: Int?

    let createdAt: String
    var updatedAt: String
}

enum EpisodeStatus: String, Codable, CaseIterable {
    case planning, casting, scheduled, filmed, editing, published
}

enum DealTier: String, Codable {
    case compMeal = "comp_meal"
    case logoPlacement = "logo_placement"
    case titleSponsor = "title_sponsor"
}

// Models/ConsentRecord.swift
struct ConsentRecord: Codable, Identifiable {
    let id: String
    let episodeId: String
    let participantProfileId: String
    var consentType: String        // "filming_and_distribution"
    var signatureImageUrl: String  // Supabase Storage URL
    var signedAt: String
    var deviceInfo: String         // iPhone model + iOS version
    var ipAddress: String?
    let createdAt: String
}

// Models/LedgerEntry.swift
struct LedgerEntry: Codable, Identifiable {
    let id: String
    var type: LedgerType           // income or expense
    var category: LedgerCategory
    var amount: Double
    var description: String
    var episodeId: String?         // Which episode this relates to
    var receiptUrl: String?        // Photo of receipt (Supabase Storage)
    var date: String
    var taxDeductible: Bool
    let createdAt: String
}

enum LedgerType: String, Codable {
    case income, expense
}

enum LedgerCategory: String, Codable, CaseIterable {
    // Income
    case sponsorshipCash = "sponsorship_cash"
    case sponsorshipComp = "sponsorship_comp"
    case affiliateRevenue = "affiliate_revenue"
    case contentMonetization = "content_monetization"
    case brandDeal = "brand_deal"
    // Expense
    case food, transport, wardrobe, production, appInfra = "app_infra", misc
}

// Models/WardrobeItem.swift
struct WardrobeItem: Codable, Identifiable {
    let id: String
    var name: String               // "Navy Slim Chinos"
    var brand: String              // "Zara"
    var category: String           // top, bottom, shoes, accessories, fragrance
    var cost: Double
    var isSponsored: Bool
    var sponsorName: String?
    var photoUrl: String?
    var episodesWorn: [String]     // Episode IDs
    let createdAt: String
}

// Models/Outfit.swift
struct Outfit: Codable, Identifiable {
    let id: String
    var episodeId: String
    var wardrobeItemIds: [String]
    var photoUrl: String?          // Full outfit photo
    var notes: String?
    let createdAt: String
}

Modified Models

swift
// Restaurant — add sponsorship fields
struct Restaurant: Codable, Identifiable {
    // ... existing fields ...
    var sponsorshipTier: DealTier?     // NEW
    var featuredInEpisode: Int?         // NEW — episode week number
    var partnerContactName: String?    // NEW
    var partnerContactEmail: String?   // NEW
    var filmingPermission: Bool?       // NEW
    var lightingNotes: String?         // NEW
    var bestTableDescription: String?  // NEW
}

New Supabase Tables (7)

sql
-- fd_applications
CREATE TABLE fd_applications (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    profile_id UUID REFERENCES fd_profiles(id),
    status TEXT DEFAULT 'pending',
    why_me TEXT NOT NULL,
    instagram_handle TEXT NOT NULL,
    video_intro_url TEXT,
    consent_acknowledged BOOLEAN DEFAULT false,
    episode_id UUID,
    reviewed_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ DEFAULT now()
);

-- fd_episodes (unified deal + episode)
CREATE TABLE fd_episodes (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    week_number INTEGER NOT NULL CHECK (week_number BETWEEN 1 AND 20),
    title TEXT,
    status TEXT DEFAULT 'planning',
    restaurant_id UUID REFERENCES fd_restaurants(id),
    deal_tier TEXT,
    deal_value NUMERIC(10,2),
    deal_contact_name TEXT,
    deal_contact_email TEXT,
    deal_notes TEXT,
    pitched_at TIMESTAMPTZ,
    confirmed_at TIMESTAMPTZ,
    invoiced_at TIMESTAMPTZ,
    paid_at TIMESTAMPTZ,
    application_id UUID REFERENCES fd_applications(id),
    talent_name TEXT,
    talent_instagram TEXT,
    scheduled_for TIMESTAMPTZ,
    time_slot TEXT,
    filmed_at TIMESTAMPTZ,
    consent_record_id UUID,
    tiktok_url TEXT,
    instagram_url TEXT,
    youtube_url TEXT,
    thumbnail_url TEXT,
    published_at TIMESTAMPTZ,
    view_count INTEGER DEFAULT 0,
    like_count INTEGER DEFAULT 0,
    comment_count INTEGER DEFAULT 0,
    created_at TIMESTAMPTZ DEFAULT now(),
    updated_at TIMESTAMPTZ DEFAULT now()
);

-- fd_consent_records
CREATE TABLE fd_consent_records (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    episode_id UUID REFERENCES fd_episodes(id),
    participant_profile_id UUID REFERENCES fd_profiles(id),
    consent_type TEXT DEFAULT 'filming_and_distribution',
    signature_image_url TEXT NOT NULL,
    signed_at TIMESTAMPTZ NOT NULL,
    device_info TEXT,
    ip_address TEXT,
    created_at TIMESTAMPTZ DEFAULT now()
);

-- fd_ledger_entries
CREATE TABLE fd_ledger_entries (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    type TEXT NOT NULL CHECK (type IN ('income', 'expense')),
    category TEXT NOT NULL,
    amount NUMERIC(10,2) NOT NULL,
    description TEXT,
    episode_id UUID REFERENCES fd_episodes(id),
    receipt_url TEXT,
    date DATE NOT NULL,
    tax_deductible BOOLEAN DEFAULT false,
    created_at TIMESTAMPTZ DEFAULT now()
);

-- fd_wardrobe_items
CREATE TABLE fd_wardrobe_items (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name TEXT NOT NULL,
    brand TEXT,
    category TEXT,
    cost NUMERIC(10,2) DEFAULT 0,
    is_sponsored BOOLEAN DEFAULT false,
    sponsor_name TEXT,
    photo_url TEXT,
    episodes_worn UUID[] DEFAULT '{}',
    created_at TIMESTAMPTZ DEFAULT now()
);

-- fd_outfits
CREATE TABLE fd_outfits (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    episode_id UUID REFERENCES fd_episodes(id),
    wardrobe_item_ids UUID[] NOT NULL,
    photo_url TEXT,
    notes TEXT,
    created_at TIMESTAMPTZ DEFAULT now()
);

-- Add columns to fd_restaurants
ALTER TABLE fd_restaurants ADD COLUMN IF NOT EXISTS sponsorship_tier TEXT;
ALTER TABLE fd_restaurants ADD COLUMN IF NOT EXISTS featured_in_episode INTEGER;
ALTER TABLE fd_restaurants ADD COLUMN IF NOT EXISTS partner_contact_name TEXT;
ALTER TABLE fd_restaurants ADD COLUMN IF NOT EXISTS partner_contact_email TEXT;
ALTER TABLE fd_restaurants ADD COLUMN IF NOT EXISTS filming_permission BOOLEAN;
ALTER TABLE fd_restaurants ADD COLUMN IF NOT EXISTS lighting_notes TEXT;
ALTER TABLE fd_restaurants ADD COLUMN IF NOT EXISTS best_table_description TEXT;

New Services (7)

ServiceResponsibility
ApplicationManagerFetch/filter/approve/reject/shortlist applications
EpisodeManagerFull episode lifecycle: plan → cast → schedule → film → edit → publish. Also handles deal pipeline stages.
ConsentManagerGenerate consent form, capture signature (PencilKit canvas), upload to Storage, create record
LedgerManagerIncome/expense CRUD, per-episode P&L, series P&L, category breakdown
WardrobeManagerWardrobe item CRUD, outfit assembly per episode
ScheduleManager10-week plan computation from episodes, budget allocation per week
ChecklistManagerPre-shoot validation: consent signed? Reservation confirmed? Phone charged? Outfit logged?

New Views (~25)

### Host Views (Mohamed-only)
- `Views/Host/HostTabView.swift` — 5-tab layout
- `Views/Host/Inbox/ApplicationInboxView.swift` — card list with approve/pass/shortlist
- `Views/Host/Inbox/ApplicationDetailView.swift` — full applicant profile
- `Views/Host/Pipeline/EpisodePipelineView.swift` — horizontal Kanban (planning → casting → ... → published)
- `Views/Host/Pipeline/EpisodeDetailView.swift` — all episode fields + deal info + talent + content links
- `Views/Host/Schedule/ScheduleView.swift` — 10-week grid
- `Views/Host/Schedule/WeekDetailView.swift` — expanded week: budget, logistics, deal status
- `Views/Host/Ledger/LedgerView.swift` — transaction list with category filters
- `Views/Host/Ledger/PLSummaryView.swift` — series P&L with visual breakdown
- `Views/Host/Ledger/AddExpenseView.swift` — quick expense entry with receipt photo
- `Views/Host/Studio/StudioView.swift` — consent records + pre-shoot checklist
- `Views/Host/Studio/ConsentCaptureView.swift` — legal text + PencilKit signature + timestamp
- `Views/Host/Studio/SignatureCanvasView.swift` — PencilKit drawing canvas
- `Views/Host/Studio/PreShootChecklistView.swift` — gated validation before filming
- `Views/Host/Studio/WardrobeView.swift` — wardrobe items + outfit assembly
- `Views/Host/Studio/OutfitBuilderView.swift` — select items for an episode outfit

### Public Views (Women / Audience)
- `Views/Public/PublicTabView.swift` — 5-tab layout
- `Views/Public/Series/SeriesView.swift` — episode poster cards
- `Views/Public/Series/PublicEpisodeDetailView.swift` — episode info + content links + restaurant card
- `Views/Public/Apply/ApplicationFormView.swift` — replaces OnboardingView for applicants
- `Views/Public/Apply/ApplicationStatusView.swift` — pending/reviewing/selected/passed
- `Views/Public/Spots/PublicSpotsView.swift` — restaurants with "Featured in Episode X" badges
- `Views/Public/Status/StatusView.swift` — application tracker

File Inventory (Post-Rebuild)

### Preserved (12 files)
- FirstDateApp.swift (modified for role switching)
- AuthManager, AuthView (unchanged)
- SupabaseConfig (unchanged)
- Theme.swift (add custom fonts)
- Constants.swift (add new constants)
- ChatManager, ChatView, MessageBubbleView (kept for Mohamed-applicant DMs)
- PhotoManager (finally wired to UI)
- GradientButton, LoadingView, ProfilePhotoView, ChipSelector, SingleChipSelector

### Deleted (12 files)
- DiscoverView, SwipeCardView, MatchPopupView, DiscoveryManager
- MatchListView, MatchRowView, MatchManager
- DatesView, DateCardView, ProposeDateView, DateManager
- DashboardView (replaced by role-specific dashboards)

### New (~32 files)
- 7 models
- 7 services
- 16 host views
- 7 public views
- StatCardView kept (reused in Ledger)

Net: ~42 files, ~6,200 LOC (from 41 files, ~4,500 LOC)

Design Evolution

### Typography Upgrade
Add custom fonts for the Calvin Klein-esque aesthetic:
- Headlines: DM Serif Display (Google Fonts) or Playfair Display
- Body: DM Sans (clean, modern, pairs with DM Serif)
- Add font files to Xcode project, register in Info.plist

### Color Palette Refinement
Keep the existing dark theme but add:

swift
static let fdIvory = Color(hex: "f5f0e8")    // Warm text on dark (replaces pure white)
static let fdChampagne = Color(hex: "e8d5b7") // Luxury accent
static let fdOnyx = Color(hex: "0d0d14")      // Deepest dark

### Motion
- Episode cards: parallax scroll effect
- Pipeline: horizontal drag with snap-to-stage
- Consent signature: ink trail animation
- P&L numbers: counting animation on appear

## Anti-Patterns to Avoid
1. No WKWebView embeds — TikTok embeds break. Just use URL links that open in Safari/app.
2. No camera integration — iPhone Camera app is better. Use PhotosPicker for receipt photos.
3. No audience voting — Dehumanizes applicants. Killed in Review.
4. No leaderboards of women — Ranking by follower count is creepy.
5. No "talent" or "deal" language in public UI — Internal only. Public says "application" and "episode."

Promotion Decision

Promote into a technical note or architecture paper with implementation anchors.

Source Anchor

omega-output/firstdate-rebuild-20260320/04-architecture.md

Detected Structure

Method · References · Code Anchors · Architecture · is Stage Research