Skip to content

hushh-labs/hushh-agents

Repository files navigation

πŸ“± Hushh Agents β€” iOS App

Browse-first SwiftUI iOS app where guest users can discover and swipe through a deck of Registered Investment Advisors. Login unlocks onboarding, persistence, claims, and settings.


🎯 Goal

Ship a browse-first SwiftUI iOS app where:

  • Guest users can see the agent deck, swipe, and view details β€” no auth required
  • Login unlocks onboarding, persistence (swipe sync), claims, and settings
  • Delivery in vertical slices: App Shell β†’ Deck UX β†’ Auth/Onboarding β†’ Sync/Settings β†’ Claims + Polish
  • Existing Supabase schema is used as-is β€” no migrations, no new tables

πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Hushh Agents iOS                       β”‚
β”‚                   SwiftUI Β· MVVM Β· iOS 17+                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                           β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚   β”‚   Views    β”‚ ←→ β”‚ ViewModels  β”‚ ←→ β”‚   Services    β”‚  β”‚
β”‚   β”‚ (SwiftUI)  β”‚    β”‚   (MVVM)    β”‚    β”‚  (Supabase)   β”‚  β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                β”‚          β”‚
β”‚                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
β”‚                                    β”‚   Supabase Cloud    β”‚β”‚
β”‚                                    β”‚  hussh-ai project   β”‚β”‚
β”‚                                    β”‚  Auth + PostgreSQL   β”‚β”‚
β”‚                                    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜β”‚
β”‚                                                           β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚   β”‚  Bundled: kirkland_agents (21 pre-populated)      β”‚   β”‚
β”‚   β”‚  Fallback JSON if network unavailable              β”‚   β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ”§ Tech Stack

Component Technology
UI Framework SwiftUI (iOS 17+)
Architecture MVVM
Backend Supabase (PostgreSQL + Auth + Realtime)
Auth Providers Apple Sign In (native) + Google Sign In
Package Manager Swift Package Manager (SPM)
Min iOS Version iOS 17.0
Language Swift 5.9+

πŸ“¦ Dependencies (SPM)

Package Version Purpose URL
Supabase Swift Latest Auth, Database, Realtime https://github.com/supabase/supabase-swift
GoogleSignIn-iOS Latest Google Sign In https://github.com/google/GoogleSignIn-iOS
AuthenticationServices Built-in Apple Sign In Native framework

πŸ”‘ Supabase Configuration

Property Value
Project hussh-ai (Production)
URL https://ibsisfnjxeowvdtvgzff.supabase.co
Anon Key (stored in Config.plist, not hardcoded)
Auth Providers Apple βœ…, Google βœ…

πŸ“‚ Project Structure

hushh-agents-ios/
β”œβ”€β”€ HushhAgents/
β”‚   β”‚
β”‚   β”œβ”€β”€ App/
β”‚   β”‚   β”œβ”€β”€ HushhAgentsApp.swift              # @main entry, Supabase init
β”‚   β”‚   β”œβ”€β”€ AppState.swift                     # Root source of truth: session, onboarding,
β”‚   β”‚   β”‚                                      # pending gated action, current user snapshot
β”‚   β”‚   └── ContentView.swift                  # Root view: navigation router
β”‚   β”‚
β”‚   β”œβ”€β”€ Config/
β”‚   β”‚   └── Supabase.plist                     # URL + anon key (gitignored)
β”‚   β”‚
β”‚   β”œβ”€β”€ Models/
β”‚   β”‚   β”œβ”€β”€ KirklandAgent.swift                # Maps to kirkland_agents table
β”‚   β”‚   β”œβ”€β”€ AppUser.swift                      # Maps to users table
β”‚   β”‚   β”œβ”€β”€ ConsumerProfile.swift              # Maps to consumer_profiles table
β”‚   β”‚   β”œβ”€β”€ UserAgentSelection.swift           # Maps to user_agent_selections table
β”‚   β”‚   β”œβ”€β”€ LeadRequest.swift                  # Maps to lead_requests table
β”‚   β”‚   β”œβ”€β”€ SwipeDirection.swift               # Enum: .pass, .interested
β”‚   β”‚   β”œβ”€β”€ ClaimAvailability.swift            # Enum: .claimable(agentProfileId), .unavailable
β”‚   β”‚   └── GatedAction.swift                  # Enum: pending protected actions for resumption
β”‚   β”‚
β”‚   β”œβ”€β”€ Services/
β”‚   β”‚   β”œβ”€β”€ SupabaseService.swift              # Supabase client singleton
β”‚   β”‚   β”œβ”€β”€ AuthService.swift                  # Apple + Google Sign In, session restore,
β”‚   β”‚   β”‚                                      # sign out, auth callback handling
β”‚   β”‚   β”œβ”€β”€ AgentService.swift                 # Fetch kirkland_agents β†’ fallback bundled JSON
β”‚   β”‚   β”œβ”€β”€ SwipeService.swift                 # Queue guest swipe, load pending, sync pending,
β”‚   β”‚   β”‚                                      # fetch remote selections, dedupe
β”‚   β”‚   β”œβ”€β”€ LeadService.swift                  # Resolve claim target, create lead_request,
β”‚   β”‚   β”‚                                      # fetch my claims (mapped agents only)
β”‚   β”‚   └── UserService.swift                  # Upsert users, read/write consumer_profiles,
β”‚   β”‚                                          # fetch profile/settings data
β”‚   β”‚
β”‚   β”œβ”€β”€ ViewModels/
β”‚   β”‚   β”œβ”€β”€ DeckViewModel.swift                # Cards, swipe handlers, deck filtering,
β”‚   β”‚   β”‚                                      # detail selection, claim availability
β”‚   β”‚   β”œβ”€β”€ AuthViewModel.swift                # Auth state, sign in/out, session check
β”‚   β”‚   β”œβ”€β”€ OnboardingViewModel.swift          # Onboarding flow, save consumer_profile
β”‚   β”‚   β”œβ”€β”€ AgentDetailViewModel.swift         # Agent detail data
β”‚   β”‚   β”œβ”€β”€ ProfileViewModel.swift             # User profile + settings data
β”‚   β”‚   └── ClaimViewModel.swift               # Claim/lead request flow
β”‚   β”‚
β”‚   β”œβ”€β”€ Views/
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ Deck/
β”‚   β”‚   β”‚   β”œβ”€β”€ DeckView.swift                 # Main screen β€” card stack + action buttons
β”‚   β”‚   β”‚   β”œβ”€β”€ AgentCardView.swift            # Individual swipeable card
β”‚   β”‚   β”‚   β”œβ”€β”€ CardStackView.swift            # ZStack of cards with gestures
β”‚   β”‚   β”‚   β”œβ”€β”€ SwipeActionButtons.swift       # Bottom βœ— ⭐ βœ“ buttons
β”‚   β”‚   β”‚   └── EmptyDeckView.swift            # "No more agents" state
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ AgentDetail/
β”‚   β”‚   β”‚   β”œβ”€β”€ AgentDetailView.swift          # Full agent info sheet
β”‚   β”‚   β”‚   β”œβ”€β”€ AgentPhotoCarousel.swift       # Photo gallery
β”‚   β”‚   β”‚   β”œβ”€β”€ AgentInfoSection.swift         # Contact, hours, services
β”‚   β”‚   β”‚   β”œβ”€β”€ AgentMapView.swift             # Location map thumbnail
β”‚   β”‚   β”‚   └── ClaimProfileButton.swift       # CTA: active if claimable, disabled if unmapped
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ Auth/
β”‚   β”‚   β”‚   β”œβ”€β”€ AuthView.swift                 # Sign in screen
β”‚   β”‚   β”‚   β”œβ”€β”€ AppleSignInButton.swift        # Native Apple Sign In
β”‚   β”‚   β”‚   └── GoogleSignInButton.swift       # Google Sign In
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ Onboarding/
β”‚   β”‚   β”‚   └── OnboardingView.swift           # Single page onboarding
β”‚   β”‚   β”‚
β”‚   β”‚   β”œβ”€β”€ Profile/
β”‚   β”‚   β”‚   β”œβ”€β”€ SettingsView.swift             # iOS-native settings (List + Sections)
β”‚   β”‚   β”‚   β”œβ”€β”€ ProfileHeaderView.swift        # Avatar + name + email
β”‚   β”‚   β”‚   β”œβ”€β”€ SelectedAgentsView.swift       # Agents user liked
β”‚   β”‚   β”‚   β”œβ”€β”€ PassedAgentsView.swift         # Agents user passed
β”‚   β”‚   β”‚   β”œβ”€β”€ MyClaimsView.swift             # Lead requests status
β”‚   β”‚   β”‚   └── EditProfileView.swift          # Edit user info
β”‚   β”‚   β”‚
β”‚   β”‚   └── Components/
β”‚   β”‚       β”œβ”€β”€ RatingStarsView.swift          # β­β­β­β­β˜† display
β”‚   β”‚       β”œβ”€β”€ CategoryBadge.swift            # Pill-shaped category tag
β”‚   β”‚       β”œβ”€β”€ CachedAsyncImage.swift         # Image loading + cache
β”‚   β”‚       β”œβ”€β”€ ShimmerView.swift              # Loading placeholder
β”‚   β”‚       └── AuthGateModifier.swift         # ViewModifier: show auth if needed,
β”‚   β”‚                                          # resume pending action after auth
β”‚   β”‚
β”‚   β”œβ”€β”€ Extensions/
β”‚   β”‚   β”œβ”€β”€ Color+Hushh.swift                  # Brand colors
β”‚   β”‚   β”œβ”€β”€ View+Haptics.swift                 # Haptic feedback helpers
β”‚   β”‚   └── Date+Formatting.swift              # Date formatters
β”‚   β”‚
β”‚   β”œβ”€β”€ Resources/
β”‚   β”‚   β”œβ”€β”€ Assets.xcassets/                   # App icon, colors, images
β”‚   β”‚   β”œβ”€β”€ kirkland_agents_seed.json          # Bundled 21 agents (offline fallback)
β”‚   β”‚   └── Info.plist
β”‚   β”‚
β”‚   └── Preview Content/
β”‚       └── PreviewData.swift                  # Sample data for SwiftUI previews
β”‚
β”œβ”€β”€ mitm-setup/                                # Existing MITM proxy tools
β”œβ”€β”€ complete_schema.sql                        # Supabase schema reference
└── README.md                                  # This file

πŸ—„οΈ Database Tables Used (All Pre-existing β€” No Migrations)

Core Tables

kirkland_agents β€” Pre-populated Agent Deck (21 agents)

kirkland_agents (
    id text PK,                    -- Yelp business ID (text, NOT uuid)
    name text NOT NULL,
    alias text,
    phone text,
    localized_phone text,
    address1 text,
    address2 text,
    city text,
    state text,
    zip text,
    country text DEFAULT 'US',
    latitude double precision,
    longitude double precision,
    avg_rating double precision,
    review_count integer DEFAULT 0,
    categories text[],
    is_closed boolean DEFAULT false,
    photo_url text,
    email text,
    website text,
    bio text,
    services text[],
    license_number text,
    years_in_business integer,
    contact_person text,
    status text DEFAULT 'active',
    photos text[]
)

users β€” App Users

users (
    id uuid PK β†’ auth.users(id),
    email text,
    phone text,
    full_name text,
    avatar_url text,
    zip_code text,
    onboarding_step text DEFAULT 'landing',  -- 'landing' | 'complete'
    house_rules_accepted_at timestamptz,
    metadata jsonb DEFAULT '{}'
)

consumer_profiles β€” Onboarding Preferences

consumer_profiles (
    id uuid PK,
    user_id uuid β†’ users(id) UNIQUE,
    first_name text,
    last_name text,
    insurance_goals text[],
    goal_timeline text DEFAULT 'exploring',
    preferred_zip text,
    service_mode text DEFAULT 'any',
    primary_goal text,
    -- ... more fields available
)

user_agent_selections β€” Swipe Actions (Deck)

user_agent_selections (
    id uuid PK,
    user_id uuid β†’ auth.users(id),
    agent_id text β†’ kirkland_agents(id),     -- text FK, NOT uuid
    status text CHECK ('selected' | 'rejected'),
    created_at timestamptz
)

lead_requests β€” Profile Claims

⚠️ Important: lead_requests.agent_id references agent_profiles(id) (uuid), NOT kirkland_agents(id) (text). Claims only work for agents that have a corresponding row in agent_profiles. Unmapped kirkland_agents show disabled/info CTA.

lead_requests (
    id uuid PK,
    user_id uuid β†’ users(id),
    agent_id uuid β†’ agent_profiles(id),      -- uuid FK to agent_profiles, NOT kirkland_agents
    message text NOT NULL,
    preferred_channel text DEFAULT 'chat',
    urgency text DEFAULT 'normal',
    status text CHECK ('requested' | 'viewed' | 'need_info' | 'in_review' |
                       'quote_sent' | 'closed_won' | 'closed_lost' | 'archived')
)

agent_profiles β€” Internal Verified Agent Profiles

agent_profiles (
    id uuid PK,
    name text NOT NULL,
    agency text NOT NULL,
    photo_url text,
    verified boolean DEFAULT false,
    years_experience integer,
    rating numeric,
    review_count integer,
    specialties text[],
    lines_of_authority text[],
    licenses jsonb,
    states_served text[],
    office_address text,
    response_time text,
    about text,
    profile_status text CHECK ('active' | 'inactive' | 'unavailable' | 'under_review'),
    is_online boolean DEFAULT false
)

Supporting Tables

Table Purpose
swipe_actions Swipe tracking for agent_profiles (pass / interested / priority)
conversations + messages Chat after lead/claim
agent_reviews Reviews on agents
blocked_agents User blocks agent
devices Push notification tokens (ios/android/web)
notifications Notification delivery (push/sms/email)
analytics_events Event tracking

⚠️ Claim Mapping: kirkland_agents β†’ agent_profiles

This is a critical architectural constraint:

kirkland_agents (text id)     agent_profiles (uuid id)     lead_requests
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ id: "abc123"     │──?──────▢│ id: uuid         │◀────────│ agent_id: uuidβ”‚
β”‚ (Yelp biz ID)    β”‚          β”‚ (internal agent)  β”‚         β”‚ (FK to       β”‚
β”‚                  β”‚          β”‚                  β”‚         β”‚ agent_profilesβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Behavior:

  • Each deck card resolves to either claimable(agentProfileId: UUID) or unavailable
  • LeadService only creates lead_requests against resolved agent_profiles.id
  • Unresolved cards show disabled/info CTA β€” no write attempt
  • Browse/auth/profile features are NOT blocked by claim mapping
  • Claim feature ships with progressive enablement β€” works only for mapped agents

Default: Mapped Only claim behavior. If no backend mapping source exists to resolve kirkland_agents β†’ agent_profiles, the app still ships with claim CTA disabled for those cards while the rest of v1 remains fully usable.


πŸ”„ User Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        APP LAUNCH                            β”‚
β”‚                                                              β”‚
β”‚  Check Supabase session β†’ Logged in? β†’ Check onboarding      β”‚
β”‚                            β”‚                                  β”‚
β”‚                     β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”                          β”‚
β”‚                     β”‚             β”‚                           β”‚
β”‚                  No Auth    Has Session                       β”‚
β”‚                     β”‚             β”‚                           β”‚
β”‚                     β–Ό             β–Ό                           β”‚
β”‚              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                  β”‚
β”‚              β”‚  DECK     β”‚  β”‚ onboarding    β”‚                 β”‚
β”‚              β”‚ (Guest)   β”‚  β”‚ complete?     β”‚                 β”‚
β”‚              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜                  β”‚
β”‚                     β”‚         Yes β”‚ No                        β”‚
β”‚                     β”‚          β”‚  β”‚                           β”‚
β”‚                     β”‚          β”‚  β–Ό                           β”‚
β”‚                     β”‚          β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
β”‚                     β”‚          β”‚ β”‚ ONBOARDING   β”‚             β”‚
β”‚                     β”‚          β”‚ β”‚ (1 page)     β”‚             β”‚
β”‚                     β”‚          β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β”‚                     β”‚          β”‚        β”‚                     β”‚
β”‚                     β–Ό          β–Ό        β–Ό                     β”‚
β”‚              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                β”‚
β”‚              β”‚         DECK VIEW            β”‚                β”‚
β”‚              β”‚   Card Stack of Agents       β”‚                β”‚
β”‚              β”‚                              β”‚                β”‚
β”‚              β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚                β”‚
β”‚              β”‚  β”‚    Agent Card          β”‚  β”‚                β”‚
β”‚              β”‚  β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚  β”‚                β”‚
β”‚              β”‚  β”‚    β”‚  Photo       β”‚    β”‚  β”‚                β”‚
β”‚              β”‚  β”‚    β”‚  Name        β”‚    β”‚  β”‚                β”‚
β”‚              β”‚  β”‚    β”‚  ⭐ 4.8 (22) β”‚    β”‚  β”‚                β”‚
β”‚              β”‚  β”‚    β”‚  πŸ“ Kirkland β”‚    β”‚  β”‚                β”‚
β”‚              β”‚  β”‚    β”‚  🏷️ Finance  β”‚    β”‚  β”‚                β”‚
β”‚              β”‚  β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚  β”‚                β”‚
β”‚              β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚                β”‚
β”‚              β”‚                              β”‚                β”‚
β”‚              β”‚   [βœ—]     [⭐ Claim]   [βœ“]   β”‚                β”‚
β”‚              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                β”‚
β”‚                         β”‚                                     β”‚
β”‚         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                    β”‚
β”‚         β–Ό               β–Ό               β–Ό                    β”‚
β”‚    Swipe Left     Claim Profile    Swipe Right               β”‚
β”‚    (pass)         (auth-gated)     (interested)              β”‚
β”‚         β”‚               β”‚               β”‚                    β”‚
β”‚         β”‚          β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”          β”‚                    β”‚
β”‚         β”‚          β”‚ Logged? β”‚          β”‚                    β”‚
β”‚         β”‚          β””β”¬β”€β”€β”€β”€β”€β”€β”¬β”˜          β”‚                    β”‚
β”‚         β”‚         Noβ”‚    Yesβ”‚           β”‚                    β”‚
β”‚         β”‚           β–Ό      β”‚           β”‚                    β”‚
β”‚         β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚           β”‚                    β”‚
β”‚         β”‚     β”‚  AUTH     β”‚ β”‚           β”‚                    β”‚
β”‚         β”‚     β”‚  SCREEN   β”‚ β”‚           β”‚                    β”‚
β”‚         β”‚     β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚           β”‚                    β”‚
β”‚         β”‚          β”‚       β”‚           β”‚                    β”‚
β”‚         β”‚     Onboarding?  β”‚           β”‚                    β”‚
β”‚         β”‚     β”Œβ”€β”€β”€β”€β”΄β”€β”€β”€β”€β”  β”‚           β”‚                    β”‚
β”‚         β”‚     β”‚1st time? β”‚  β”‚           β”‚                    β”‚
β”‚         β”‚     β””β”¬β”€β”€β”€β”€β”€β”€β”¬β”˜  β”‚           β”‚                    β”‚
β”‚         β”‚    Yesβ”‚    Noβ”‚   β”‚           β”‚                    β”‚
β”‚         β”‚      β–Ό      β”‚   β”‚           β”‚                    β”‚
β”‚         β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”‚   β”‚           β”‚                    β”‚
β”‚         β”‚  β”‚ONBOARD β”‚ β”‚   β”‚           β”‚                    β”‚
β”‚         β”‚  β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β”‚   β”‚           β”‚                    β”‚
β”‚         β”‚      β”‚      β”‚   β”‚           β”‚                    β”‚
β”‚         β”‚      β–Ό      β–Ό   β–Ό           β”‚                    β”‚
β”‚         β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚                    β”‚
β”‚         β”‚   β”‚ RESUME PENDING  β”‚       β”‚                    β”‚
β”‚         β”‚   β”‚ GATED ACTION    β”‚       β”‚                    β”‚
β”‚         β”‚   β”‚ (auto-navigate  β”‚       β”‚                    β”‚
β”‚         β”‚   β”‚  back to claim) β”‚       β”‚                    β”‚
β”‚         β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚                    β”‚
β”‚         β”‚                             β”‚                    β”‚
β”‚         β–Ό                             β–Ό                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”‚
β”‚  β”‚ LOCAL QUEUE     β”‚        β”‚ LOCAL QUEUE     β”‚             β”‚
β”‚  β”‚ (guest swipe)   β”‚        β”‚ (guest swipe)   β”‚             β”‚
β”‚  β”‚ UserDefaults    β”‚        β”‚ UserDefaults    β”‚             β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β”‚
β”‚          β”‚ on auth                  β”‚ on auth               β”‚
β”‚          β–Ό                          β–Ό                       β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”               β”‚
β”‚  β”‚ BULK SYNC β†’ user_agent_selections       β”‚               β”‚
β”‚  β”‚ (dedupe on existing user-agent pairs)   β”‚               β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ“± Screen Designs

1️⃣ Deck Screen (Home) β€” No Auth Required

The main screen. Tinder-style card stack showing agents from kirkland_agents.

Components:

  • Top Bar: Hushh logo (left) Β· Profile/Settings icon (right)
  • Card Stack: ZStack of 3 visible cards, top card interactive
  • Card Content: Agent photo, name, rating stars, review count, city/state, category badges
  • Bottom Actions: βœ— Pass (red) Β· ⭐ Claim (gold, disabled if unmapped) Β· βœ“ Interested (green)
  • Tap on card β†’ Opens Agent Detail Sheet
  • Already-seen filtering: Selected/rejected agents hidden from deck

Swipe Gestures:

  • Drag left > 100pt β†’ Pass (red overlay "PASS" appears)
  • Drag right > 100pt β†’ Interested (green overlay "INTERESTED" appears)
  • Spring animation on release
  • Haptic feedback on threshold cross

Guest swipes stored in local queue (UserDefaults). Remote write only happens after auth.

2️⃣ Agent Detail Sheet β€” No Auth Required

Bottom sheet / full screen detail view. Fully browseable without auth.

Sections:

  1. Photo Carousel β€” Horizontal scroll of photos[]
  2. Header β€” Name, rating stars, review count, categories as pills
  3. Quick Info β€” πŸ“ Address Β· πŸ“ž Phone Β· 🌐 Website
  4. About / Bio β€” Agent bio text
  5. Services β€” List of services offered
  6. Contact Person β€” Name of representative
  7. Map β€” Lat/long on a small MapKit view
  8. CTA Button β€” "Claim This Profile" if claimable, disabled/info if unavailable

3️⃣ Auth Screen β€” On Demand

Clean, minimal sign-in screen shown only when user triggers a protected action (claim, settings, saved lists).

Components:

  • Hushh logo + tagline "Find Your Perfect Financial Advisor"
  • "Sign in with Apple" β€” Native ASAuthorizationAppleIDButton
  • "Sign in with Google" β€” Google branded button
  • "Maybe Later" β€” Dismiss and return to deck
  • Privacy note: "By signing in, you agree to our Terms & Privacy Policy"

Auth Flow:

  1. Apple/Google returns token β†’ Supabase signInWithIdToken()
  2. Upsert users row (create if not exists)
  3. Check onboarding_step β†’ if not 'complete', show onboarding
  4. If 'complete', automatically resume pending gated action (no manual re-navigation)

4️⃣ Onboarding Screen β€” After First Sign Up Only

Single page, shown only when users.onboarding_step != 'complete'.

Fields:

  • Name β€” First name + Last name (pre-filled from Apple/Google if available)
  • What are you looking for? β€” Multi-select chips:
    • πŸ’° Wealth Management
    • πŸ“Š Financial Planning
    • 🏠 Insurance
    • πŸ“ˆ Investment Advisory
    • 🧾 Tax Planning
    • 🏦 Retirement Planning
  • Your ZIP Code β€” Text field (for location matching)
  • How soon? β€” Single select:
    • πŸ”₯ ASAP
    • πŸ“… This month
    • πŸ” Just exploring

On Submit:

  • Creates consumer_profiles row
  • Updates users.onboarding_step = 'complete'
  • Resumes pending protected action (e.g., claim, settings) instead of user navigating manually

5️⃣ Settings / Profile Screen β€” Auth Required

iOS-native List with grouped sections, exactly like iOS Settings app.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                                          β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”                                β”‚
β”‚  β”‚ πŸ‘€   β”‚  John Doe                      β”‚
β”‚  β”‚avatarβ”‚  john@example.com              β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”˜  Edit Profile β†’                β”‚
β”‚                                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  MY ACTIVITY                             β”‚
β”‚  ───────────────────────────────────     β”‚
β”‚  βœ“  Selected Agents              12  β†’  β”‚
β”‚  βœ—  Passed Agents                 8  β†’  β”‚
β”‚  🏒  My Claims                    2  β†’  β”‚
β”‚                                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  PREFERENCES                             β”‚
β”‚  ───────────────────────────────────     β”‚
β”‚  🎯  Investment Goals           Edit β†’  β”‚
β”‚  πŸ“  Location          Kirkland, WA β†’   β”‚
β”‚  πŸ””  Notifications              On  β†’   β”‚
β”‚                                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  SUPPORT                                 β”‚
β”‚  ───────────────────────────────────     β”‚
β”‚  πŸ“‹  Privacy Policy                  β†’  β”‚
β”‚  πŸ“‹  Terms of Service                β†’  β”‚
β”‚  πŸ“§  Contact Support                 β†’  β”‚
β”‚  ℹ️   App Version              1.0.0     β”‚
β”‚                                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                          β”‚
β”‚  πŸ”΄  Sign Out                            β”‚
β”‚                                          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Sign Out: Clears protected state but does NOT break guest browsing. User returns to deck as guest.


πŸ” Auth Gate Logic

User Action Auth Required? Behavior
Browse deck (view cards) ❌ No Anyone can see agents
Tap card β†’ View detail ❌ No Full agent info visible
Swipe Left (pass) ⚑ Deferred Stored in local queue β†’ synced after auth
Swipe Right (interested) ⚑ Deferred Stored in local queue β†’ synced after auth
Claim Profile βœ… Yes Auth gate β†’ onboarding if needed β†’ resume claim
Open Settings/Profile βœ… Yes Auth gate β†’ onboarding if needed β†’ resume settings
View Selected/Passed agents βœ… Yes Auth gate β†’ show lists

Gated Action Resumption

When a protected action triggers auth:

  1. AppState stores the pending gated action (e.g., .claim(agentId), .openSettings)
  2. Auth screen shown β†’ user signs in
  3. If first-time β†’ onboarding shown β†’ user completes
  4. After auth+onboarding β†’ pending action automatically resumes
  5. User does NOT need to manually re-navigate to the action they intended

Deferred Sync Strategy

  1. Guest swipes stored in UserDefaults as [(agentId: String, status: String)]
  2. On successful auth β†’ bulk insert into user_agent_selections with dedupe on existing user-agent pairs
  3. Clear local cache after successful sync
  4. Already-swiped agents filtered from deck
  5. Sync retry on app foreground / session refresh; failed writes stay in queue until success
  6. Returning user: remote selections fetched β†’ selected/rejected cards hidden from deck

🎨 Design System

Colors

Name Light Mode Dark Mode Usage
hushhPrimary #6C5CE7 #A29BFE Buttons, accents
hushhBackground System System Main background
hushhCard #FFFFFF #1C1C1E Card background
hushhPass #FF6B6B #FF6B6B Pass/reject action
hushhLike #51CF66 #51CF66 Like/interested action
hushhClaim #FFD43B #FFD43B Claim/star action
hushhText System primary System primary Main text
hushhSubtext System secondary System secondary Secondary text

Typography

  • System default (SF Pro) β€” No custom fonts
  • Title: .title / .title2
  • Body: .body
  • Caption: .caption
  • Card name: .title3.bold()
  • Card rating: .subheadline

Animations

  • Card swipe: spring(response: 0.4, dampingFraction: 0.7)
  • Card enter: scale(0.95) β†’ scale(1.0) with spring
  • Overlay labels: "PASS" / "INTERESTED" with opacity based on drag distance
  • Haptics: .impact(style: .medium) on threshold, .notification(.success) on action

Spacing & Sizing

  • Card corner radius: 20pt
  • Card shadow: radius: 10, y: 5
  • Card stack offset: each card offset 8pt down and 0.95 scale
  • Bottom buttons: 60pt circular
  • Standard padding: 16pt

πŸ“‹ Public Interfaces / Key Contracts

AppState (ObservableObject β€” Root Source of Truth)

class AppState: ObservableObject {
    @Published var sessionStatus: SessionStatus        // .anonymous | .authenticated(user)
    @Published var onboardingStatus: OnboardingStatus  // .incomplete | .complete
    @Published var pendingGatedAction: GatedAction?    // .claim(agentId) | .openSettings | nil
    @Published var currentUser: AppUser?               // Snapshot of users table row
    
    func resolveGatedAction()     // Clears and executes pending action
    func clearProtectedState()    // Sign out: clear user but keep guest browsing
}

DeckViewModel (ObservableObject)

class DeckViewModel: ObservableObject {
    @Published var cards: [KirklandAgent]
    @Published var selectedAgent: KirklandAgent?        // For detail sheet
    
    func swipe(_ agent: KirklandAgent, direction: SwipeDirection)
    func claimAvailability(for agent: KirklandAgent) -> ClaimAvailability
    func filterAlreadySeen()                            // Remove selected/rejected from deck
    func loadAgents()                                   // Fetch from DB β†’ fallback bundled JSON
}

AuthService

class AuthService {
    func restoreSession() async throws -> Session?
    func signInWithApple(credential: ASAuthorizationCredential) async throws
    func signInWithGoogle(presenting: UIViewController) async throws
    func signOut() async throws
    func onAuthCallback(session: Session) async         // Post-auth: upsert user, sync swipes
}

SwipeService

class SwipeService {
    func queueGuestSwipe(agentId: String, status: String)      // Local UserDefaults
    func loadPendingSwipes() -> [(agentId: String, status: String)]
    func syncPendingSwipes(userId: UUID) async throws          // Bulk insert with dedupe
    func fetchRemoteSelections(userId: UUID) async throws -> [UserAgentSelection]
}

LeadService

class LeadService {
    func resolveClaimTarget(kirklandAgentId: String) async -> ClaimAvailability
    func createLeadRequest(userId: UUID, agentProfileId: UUID, message: String) async throws
    func fetchMyClaims(userId: UUID) async throws -> [LeadRequest]
}

UserService

class UserService {
    func upsertUser(from session: Session) async throws -> AppUser
    func fetchUser(id: UUID) async throws -> AppUser?
    func updateOnboardingStep(userId: UUID, step: String) async throws
    func createConsumerProfile(_ profile: ConsumerProfile) async throws
    func fetchConsumerProfile(userId: UUID) async throws -> ConsumerProfile?
    func updateConsumerProfile(_ profile: ConsumerProfile) async throws
}

πŸƒ Implementation Plan (Vertical Slices)

Slice 1: Foundation & App Shell

App boots, shows loading, data loads from DB or bundled JSON.

# Task Files
1.1 Create Xcode project + folder structure Project setup
1.2 Add SPM dependencies (Supabase, GoogleSignIn) Package dependencies
1.3 Supabase client singleton SupabaseService.swift, Supabase.plist
1.4 Core data models KirklandAgent.swift, AppUser.swift, ConsumerProfile.swift, UserAgentSelection.swift, LeadRequest.swift, SwipeDirection.swift, ClaimAvailability.swift, GatedAction.swift
1.5 Bundle seed JSON kirkland_agents_seed.json
1.6 App state bootstrap AppState.swift, HushhAgentsApp.swift, ContentView.swift
1.7 Agent loading service AgentService.swift (fetch β†’ fallback)

Exit Criteria: App launches, loads 21 agents (remote or bundled), shows basic list/placeholder.

Slice 2: Deck UX (Browse-First Experience)

Guest users can swipe cards and view details. No auth required.

# Task Files
2.1 Deck ViewModel DeckViewModel.swift
2.2 Agent Card UI AgentCardView.swift
2.3 Card Stack with swipe gestures CardStackView.swift
2.4 Main deck screen DeckView.swift
2.5 Action buttons (pass/claim/interested) SwipeActionButtons.swift
2.6 Empty deck state EmptyDeckView.swift
2.7 Agent Detail sheet AgentDetailView.swift, AgentPhotoCarousel.swift, AgentInfoSection.swift, AgentMapView.swift, ClaimProfileButton.swift
2.8 Shared components RatingStarsView.swift, CategoryBadge.swift, CachedAsyncImage.swift, ShimmerView.swift
2.9 Theme & extensions Color+Hushh.swift, View+Haptics.swift
2.10 Local swipe queue Guest swipes β†’ UserDefaults

Exit Criteria: Full deck experience works as guest. Swipe left/right/tap detail. Cards animate. Guest swipes stored locally.

Slice 3: Auth & Onboarding

Protected actions trigger auth. New users see onboarding. Pending actions resume.

# Task Files
3.1 Auth Service (Apple + Google + Supabase) AuthService.swift
3.2 Auth ViewModel AuthViewModel.swift
3.3 Auth Screen UI AuthView.swift, AppleSignInButton.swift, GoogleSignInButton.swift
3.4 Auth gate modifier AuthGateModifier.swift
3.5 User Service (upsert, profile) UserService.swift
3.6 Onboarding ViewModel OnboardingViewModel.swift
3.7 Onboarding UI OnboardingView.swift
3.8 Gated action resumption logic In AppState.swift

Exit Criteria: Claim/settings trigger auth. New user sees onboarding. Returning user skips. Pending action resumes after auth+onboarding.

Slice 4: Persistence, Profile & Sync

Swipes sync to Supabase. Settings screen shows user data. Returning users see filtered deck.

# Task Files
4.1 Swipe Service (local queue + remote sync + dedupe) SwipeService.swift
4.2 Bulk sync on auth In AuthService.swift callback
4.3 Fetch remote selections β†’ filter deck In DeckViewModel.swift
4.4 Profile ViewModel ProfileViewModel.swift
4.5 Settings screen SettingsView.swift, ProfileHeaderView.swift
4.6 Selected/Passed agent lists SelectedAgentsView.swift, PassedAgentsView.swift
4.7 Edit profile EditProfileView.swift
4.8 Sync retry on foreground/session refresh In SwipeService.swift

Exit Criteria: Guest swipes sync after login. Returning user sees filtered deck. Settings/profile work. Failed syncs retry.

Slice 5: Claims + Polish

Claim flow for mapped agents. UI polish, animations, dark mode.

# Task Files
5.1 Lead Service (resolve mapping, create lead) LeadService.swift
5.2 Claim ViewModel ClaimViewModel.swift
5.3 Claim CTA (active/disabled based on mapping) ClaimProfileButton.swift update
5.4 My Claims list MyClaimsView.swift
5.5 Preview data PreviewData.swift
5.6 Dark mode audit All views
5.7 Animation & haptics polish All interactive views
5.8 Loading states & error handling ShimmerView.swift, error alerts

Exit Criteria: Claim works for mapped agents. Disabled for unmapped. Full polish pass. Dark mode works. Preview data available.


πŸ“Š Data: 21 Pre-populated Agents

# Name City Rating Reviews Source
1 Sound Planning Group Kirkland, WA ⭐ 5.0 8 organic
2 Snider Financial Group Bellevue, WA ⭐ 5.0 1 organic
3 Elite Wealth Management Kirkland, WA ⭐ 4.3 14 organic
4 WaterRock Global Asset Mgmt Bellevue, WA ⭐ 4.8 22 organic
5 Edward Jones - Calen H Johnson Kirkland, WA ⭐ 5.0 8 organic
6 Joanna Maliva Lee Kirkland, WA ⭐ 1.0 1 organic
7 Jeff LaDue NMLS Kirkland, WA ⭐ 5.0 22 organic
8 PCM Encore Bellevue, WA ⭐ 0.0 0 organic
9 Capital Planning Bellevue, WA ⭐ 5.0 4 organic
10 Brein Wealth Management Bellevue, WA ⭐ 5.0 1 organic
11 Charles Schwab Redmond, WA ⭐ 4.5 2 organic
12 KE & Associates Kirkland, WA ⭐ 3.9 25 organic
13 M3 Tax and Accounting Kirkland, WA ⭐ 5.0 1 organic
14 Huddleston Tax CPAs Bellevue, WA ⭐ 3.9 19 organic
15 Omega Financial & Insurance Kirkland, WA ⭐ 5.0 2 organic
16 Edward Jones - Loren P Winter Kirkland, WA ⭐ 5.0 1 organic
17 Edward Jones - Kagan C. Wolfe Kirkland, WA ⭐ 0.0 0 organic
18 ICON Consulting Bellevue, WA ⭐ 0.0 0 organic
19 Green Financial Kirkland, WA ⭐ 5.0 1 organic
20 HighTower Bellevue Bellevue, WA ⭐ 5.0 1 organic
21 Blue Mountain Wealth Mgmt Monroe, WA ⭐ 5.0 2 sponsored

Data Source: Yelp MITM Capture (Kirkland, WA 98033 β€” "Registered Investment Advisor")


πŸ§ͺ Test Plan

# Scenario Expected Result
1 Fresh install, guest launch, network available 21 agents load from kirkland_agents table, deck shows
2 Fresh install, network unavailable 21 agents load from bundled kirkland_agents_seed.json
3 Guest swipes left/right, app restart, then login Deferred swipes sync to user_agent_selections successfully
4 New user signs in Onboarding shown β†’ completion returns to deck
5 Returning user signs in Onboarding skipped β†’ previous selections filtered from deck
6 Settings access when logged out Auth gate triggers β†’ auth screen β†’ resumes settings after login
7 Claim CTA for unmapped agent CTA disabled/info state, no write attempt
8 Claim CTA for mapped agent Lead request created successfully in lead_requests
9 Sign out Clears protected state, guest browsing continues
10 Duplicate sync attempts No duplicate user_agent_selections rows (dedupe)
11 Sync failure Failed writes stay in queue, retry on foreground/session refresh
12 Dark mode All screens render correctly in light and dark mode

πŸš€ Getting Started

Prerequisites

  • Xcode 15.0+
  • iOS 17.0+ device or simulator
  • Apple Developer account (for Sign in with Apple)
  • Google Cloud Console project (for Google Sign In)

Setup

  1. Clone the repository
  2. Open HushhAgents.xcodeproj in Xcode
  3. Add Supabase.plist to Config/ with:
    <dict>
        <key>SUPABASE_URL</key>
        <string>https://ibsisfnjxeowvdtvgzff.supabase.co</string>
        <key>SUPABASE_ANON_KEY</key>
        <string>YOUR_ANON_KEY</string>
    </dict>
  4. Configure Apple Sign In capability in Xcode
  5. Configure Google Sign In GIDClientID in Info.plist
  6. Build and run on iOS 17+ simulator/device

βœ… Assumptions & Defaults

  • iOS 17+, SwiftUI, MVVM, SPM-only integration
  • No new database tables or schema changes β€” all tables pre-exist
  • Default: Mapped Only claim behavior for lead_requests
  • If no backend mapping source resolves kirkland_agents β†’ agent_profiles, app still ships with claim CTA disabled for those cards. Rest of v1 remains fully usable
  • Auth is on-demand only β€” never forced at launch
  • Sign out clears authenticated state but preserves guest browsing

About

SwiftUI Hushh Agents app for RIA discovery, onboarding, profile flows, and release/TestFlight work.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors