Overview
MyCirclee is a native iOS application built entirely with SwiftUI and backed by Supabase. Zero third-party SDKs beyond Supabase itself, zero custom servers, and zero tracking frameworks.
System Architecture
A fully serverless architecture. The iOS app communicates exclusively through the Supabase client SDK, which manages auth, database queries, real-time WebSocket subscriptions, and edge function invocations. No custom backend server exists.
100% serverless. There is no Express server, no Lambda, no Docker container. Every backend capability is provided by Supabase's managed platform, including auth, database, real-time pub/sub, and edge functions for push notifications.
Tech Stack Layers
Five distinct layers from the user interface down to the backend infrastructure. Each layer has clearly bounded responsibilities.
AppState: The Brain
A single @Observable class (~2,800 lines) acts as the centralized state manager. Injected via @Environment into every view. Handles all data fetching, caching, subscriptions, and mutations in one place.
SwiftData: Local Persistence
On-device persistence layer backed by SwiftData. Stores cached data, draft posts, user preferences, and widget configuration. Enables offline reads and fast cold launches.
RichHaptics: Tactile Feedback
Custom haptic engine combining CoreHaptics patterns with UIKit feedback generators. Every interaction (reactions, status changes, polls) has purpose-designed haptic feedback.
CoreSpotlight: System Search
Posts and contacts are indexed via CoreSpotlight for iOS system-wide search. All indexing happens on-device. Nothing is sent to any server for search purposes.
Database Schema
14 PostgreSQL tables organized into four logical groups. Every table is protected by Row Level Security policies that enforce circle-level data isolation.
circles
circle_members
profiles
posts
post_reactions
poll_options
poll_votes
event_rsvps
dm_threads
dm_messages
dm_reactions
presence_states
push_tokens
kept_posts
add_requests
Key relationships: All foreign keys enforce referential integrity. circle_members is the join table that gates every RLS policy. A user can only read rows where their user_id appears in circle_members for the relevant circle_id.
Data Flow: Post Creation
The lifecycle of a post from composition to automatic expiry. Optimistic updates provide instant feedback while the server sync happens in the background.
posts table. RLS validates the author is a circle member. expires_at set to now() + 24h
push_tokens for circle members and dispatches APNs notifications
expires_at < now(). Client also filters locally for instant UI consistency
Data Flow: Realtime Subscriptions
Three persistent WebSocket channels keep the UI synchronized across all devices in a circle. Supabase Realtime handles the pub/sub layer; RLS enforces security at the database level.
@Observable property changes trigger automatic SwiftUI view updates. No manual refresh needed
Why unfiltered subscriptions? Supabase Realtime applies RLS to prevent unauthorized access at the database layer. The client then applies a secondary circle-level filter for DMs to show only the active conversation. This two-layer approach ensures security without sacrificing flexibility.
Data Flow: Widget Architecture
Three WidgetKit extensions (small, medium, lock screen) plus ActivityKit Live Activities. Widgets and the host app communicate through a shared App Group container.
presence_states row updated. Realtime broadcasts the change to all circle members
Live Activities
ActivityKit powers lock-screen Live Activities for upcoming events. Shows countdown, location, and RSVP count. Updates via push token delivery from the edge function.
Timeline Strategy
Widgets use an atEnd reload policy with a 15-minute cadence. Presence data updates immediately via intent-triggered reloads; background refreshes fill gaps.
Authentication Flow
Two authentication methods: traditional email + password and passwordless magic link. Both managed entirely by Supabase Auth with JWT tokens stored in the iOS Keychain.
auth.uid() to enforce access
WHERE circle_id IN (SELECT circle_id FROM circle_members WHERE user_id = auth.uid())
Security Architecture
Defense in depth: every layer from the database up to the UI enforces data isolation. No single point of failure can expose cross-circle data.
Row Level Security
Every table has RLS policies enabled. Queries are automatically filtered by circle membership. Even direct SQL access to the database respects these boundaries.
Auth Token Management
JWTs are stored in the iOS Keychain, never in UserDefaults or plain files. The Supabase SDK handles transparent token refresh without exposing credentials to app code.
On-Device Content Safety
Content safety analysis runs entirely on-device using Apple's frameworks. No post content is ever sent to an external service for scanning or moderation.
Ephemeral by Design
Posts auto-delete after 24 hours at the database level. This is not a soft archive or hidden flag. The data is permanently removed and unrecoverable.
Invite Security
Invite codes are single-use and expire after 48 hours. Once claimed, the code is invalidated. No link can be shared publicly to grant ongoing access.
DM Privacy Levels
Users control who can DM them with three levels: anyone in the circle, reciprocal only (both must opt in), or nobody. Enforced at the database query layer.
Design System
Centralized in Theme.swift, the design system defines every visual token: colors, typography, spacing, shadows, and interaction patterns. Consistency is enforced through SwiftUI view modifiers rather than ad-hoc styling.
Color Palette
Typography Pairing
Component Patterns
Haptic Design Language: RichHaptics.swift maps every user action to a specific haptic pattern using CoreHaptics and UIKit generators. Reactions use a soft impact, status changes use a double-tap pattern, and errors use a notch feedback. Haptics are never random or decorative.
Data Flow Patterns
Five core patterns govern how data moves through the application. Each pattern is purpose-built for its use case.
Optimistic Updates
Reactions, RSVPs, and poll votes update the UI immediately before the server confirms the mutation. If the server rejects the change, the UI rolls back. This makes interactions feel instant.
Realtime Subscriptions
Unfiltered WebSocket subscriptions with client-side circle filtering. Supabase RLS prevents unauthorized access at the database layer; the client narrows to the active context.
Ephemeral Expiry
Server-side scheduled deletion of posts where expires_at < now(). The client also filters expired posts locally for instant consistency, even before the server cron runs.
Draft Persistence
The compose view auto-saves drafts to UserDefaults as the user types. Drafts survive app kills, crashes, and accidental dismissals. Restored silently on next compose.
Widget Communication
App Groups shared container bridges the host app and widget extensions. The app writes presence data and event info; widgets read it. WidgetCenter.reloadTimelines() signals refresh. Interactive widgets fire App Intents that flow back through the host app to Supabase.