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.
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
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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 β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| 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+ |
| 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 |
| Property | Value |
|---|---|
| Project | hussh-ai (Production) |
| URL | https://ibsisfnjxeowvdtvgzff.supabase.co |
| Anon Key | (stored in Config.plist, not hardcoded) |
| Auth Providers | Apple β , Google β |
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
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 (
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 (
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 (
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
)
β οΈ Important:lead_requests.agent_idreferencesagent_profiles(id)(uuid), NOTkirkland_agents(id)(text). Claims only work for agents that have a corresponding row inagent_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 (
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
)| 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 |
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)orunavailable LeadServiceonly createslead_requestsagainst resolvedagent_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.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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) β β
β βββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
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.
Bottom sheet / full screen detail view. Fully browseable without auth.
Sections:
- Photo Carousel β Horizontal scroll of
photos[] - Header β Name, rating stars, review count, categories as pills
- Quick Info β π Address Β· π Phone Β· π Website
- About / Bio β Agent bio text
- Services β List of services offered
- Contact Person β Name of representative
- Map β Lat/long on a small MapKit view
- CTA Button β "Claim This Profile" if
claimable, disabled/info ifunavailable
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:
- Apple/Google returns token β Supabase
signInWithIdToken() - Upsert
usersrow (create if not exists) - Check
onboarding_stepβ if not'complete', show onboarding - If
'complete', automatically resume pending gated action (no manual re-navigation)
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_profilesrow - Updates
users.onboarding_step = 'complete' - Resumes pending protected action (e.g., claim, settings) instead of user navigating manually
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.
| 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 |
When a protected action triggers auth:
AppStatestores the pending gated action (e.g.,.claim(agentId),.openSettings)- Auth screen shown β user signs in
- If first-time β onboarding shown β user completes
- After auth+onboarding β pending action automatically resumes
- User does NOT need to manually re-navigate to the action they intended
- Guest swipes stored in
UserDefaultsas[(agentId: String, status: String)] - On successful auth β bulk insert into
user_agent_selectionswith dedupe on existing user-agent pairs - Clear local cache after successful sync
- Already-swiped agents filtered from deck
- Sync retry on app foreground / session refresh; failed writes stay in queue until success
- Returning user: remote selections fetched β selected/rejected cards hidden from deck
| 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 |
- System default (SF Pro) β No custom fonts
- Title:
.title/.title2 - Body:
.body - Caption:
.caption - Card name:
.title3.bold() - Card rating:
.subheadline
- 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
- Card corner radius:
20pt - Card shadow:
radius: 10, y: 5 - Card stack offset: each card offset
8ptdown and0.95scale - Bottom buttons:
60ptcircular - Standard padding:
16pt
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
}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
}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
}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]
}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]
}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
}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.
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.
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.
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.
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.
| # | 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")
| # | 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 |
- 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)
- Clone the repository
- Open
HushhAgents.xcodeprojin Xcode - Add
Supabase.plisttoConfig/with:<dict> <key>SUPABASE_URL</key> <string>https://ibsisfnjxeowvdtvgzff.supabase.co</string> <key>SUPABASE_ANON_KEY</key> <string>YOUR_ANON_KEY</string> </dict>
- Configure Apple Sign In capability in Xcode
- Configure Google Sign In
GIDClientIDin Info.plist - Build and run on iOS 17+ simulator/device
- iOS 17+, SwiftUI, MVVM, SPM-only integration
- No new database tables or schema changes β all tables pre-exist
- Default:
Mapped Onlyclaim behavior forlead_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