Insights — infrastructure & how it all works
The single reference for every account, service, and piece of logic behind Insights. It points to where each credential lives; it never contains the credentials themselves.
~/Documents/atlas-api/.env, Xcode signing, or the password manager). Public client identifiers (project refs, app IDs, team IDs) are included because they are not secret and you need them constantly.
What Insights is & the stack at a glance
Insights is an iOS app for self-knowledge. A daily deck of swipeable idea cards drawn from real thinkers, a reader with reflection quests, journey paths through a theme, and a self-portrait ("My Insights") built from what you actually engage with.
Client
iOS, SwiftUI, modular SPM. Local repo ~/Documents/insights-ios-2026, live branch paths-v1 (not main). App Store ID 6761016582, team 77A25Z388N. Repo at v3.2.1 (build 54). confirmed 17 Aug the live App Store version is 1.9, Ready for Sale since 10 Aug — queried from the App Store Connect API. The public version number is decoupled from MARKETING_VERSION, so the repo number and the store number will not match.
Backend
FastAPI on Railway — atlas-api-production-8c22.up.railway.app, health at /health (verified 200 in 0.10s, 17 Aug). Repo ~/Documents/insights-backend-2026; push to main auto-deploys. 11 routers + 7 services. In-process APScheduler runs the two nightly jobs.
Data
Supabase Ireland bkiodocaqdgtkaixoisn — Postgres 17.6, 56 MB, 26 public tables. pgvector 0.8.0 (HNSW + IVFFlat on card embeddings), pg_trgm, pgcrypto. Ireland is the source of truth; the US mirror is read-only and never a write target.
Growth & lifecycle
Segment SDK → Amplitude + Customer.io + Meta, as cloud-mode destinations. RevenueCat/StoreKit for subscriptions. Cloudflare Worker insights-proxy terminates SSL for s.join-insights.com (share links, deep links, AASA).
Accounts & logins
Every service and the account it sits under. Team is Marie (owner) + Julien (CTO, GitHub jgaillard, write access to the backend repo).
| Service | Account / login | Key identifiers | Where the secret lives |
|---|---|---|---|
| Apple Developer / App Store Connect | m@umancstudio.com | Uman C AB · Team 77A25Z388N · App ID 6761016582 | ASC API key KG6P2943B6 (.p8) + Xcode signing on the build Mac |
| GitHub | Maelwi (Marie); collaborator jgaillard (Julien) | iOS + backend repos | gh CLI auth / SSH keys on the Mac |
| Supabase | m@umancstudio.com | Ref bkiodocaqdgtkaixoisn · eu-west-1 (Ireland) · Postgres 17.6 | SUPABASE_URL / SUPABASE_SECRET_KEY in ~/Documents/atlas-api/.env and the Railway env |
| Railway | m@umancstudio.com | Service atlas-api-production-8c22 · US-east | All backend env vars in the Railway project settings |
| Segment | m@umancstudio.com | Source: Insights iOS | Write key in the iOS build config |
| Amplitude | m@umancstudio.com | Org join-insights-466344 · project 658118 | API key held in the Segment destination, not in the app |
| Customer.io | m@umancstudio.com | Lifecycle + push | Track API key in the Segment destination; APNs key uploaded to CIO |
| RevenueCat | m@umancstudio.com | Public SDK key appl_ABNW… (the HeVr… key is dead — do not reuse) | Public key ships in the iOS build; secret key in the RC dashboard |
| Anthropic | m@umancstudio.com | Powers go-deeper chat, Haiku cluster naming, the 03:20 job | API key in the Railway backend env. single point of failure — see gaps below |
| Cloudflare | m@umancstudio.com | Worker insights-proxy · s.join-insights.com | wrangler auth on the Mac |
| Meta / Facebook | m@umancstudio.com | App Events + SKAdNetwork | Client token in the iOS build config |
How Insights works today
The core loop and the recommendation model behind it — verified against the current code, not the docs.
The daily loop
- Deck — a swipeable set of cards for today; keep / pass / "this is me".
- Read — the card opens into paged story slides.
- Reflect — the last slide is a Quest; tapping reveals a reflection prompt.
- Return — the tree grows, "My Insights" builds a self-portrait from what you engaged with.
Recommendation — the Lens Shift model
Three slots per recommendation. Mirror — closest cosine match to the user's intent embedding. Reframe — the same human truth from a different discipline. Stretch — a topic they've barely explored. Anti-repetition: nothing served in 14 days, no repeated author or topic in a session.
What actually ranks it thinner than designed
Live ranking is cosine similarity + an active-path bias (+0.10), and that is all. The level band, tone bias and "level+1" stretch bias are all inert because the data they read was never written. See Known gaps.
/v2/daily — a dumb read
Deliberately not assembled live. Every decision writes into prepared_cards; the endpoint is a single read of that table. The nightly job at 03:00 UTC prepares day-2+ decks; 03:20 rebuilds self-models.
Architecture
iOS app
Modular SPM with a protocol/impl split wired by Swinject. Local modules include AuthAPI/AuthImpl, ServiceAPI/ServiceResolver, LoggingAPI/LoggingImpl, Paywall/PaywallImpl, CardsUI, SharedUI, Assets, HomeScreen, RootScreen, TrialTracker — each with a Preview variant for SwiftUI previews.
supabase-swift 2.42.0, analytics-swift 1.9.3 (Segment), purchases-ios 4.44.2 (RevenueCat), facebook-ios-sdk, swinject 2.8.3, lottie-spm 4.6.0, alamofire 5.7.1.
SupabaseAuthProvider talks to Supabase Ireland directly, not through the backend. Do not repoint this for the mirror.
Backend / FastAPI
cards, recommend, paths, user, favorites, self_model, deeper, notes, referral, admin.
recommender (the Lens Shift engine), recommendation_agent, self_model, embeddings, suppression, pinecone_client.
APScheduler in-process: nightly_prepare 03:00 UTC, nightly_self_models 03:20 UTC. In-process means they die with the web dyno — no separate worker.
Push to main → Railway auto-deploys. There is no staging environment.
Caching
The path list is cached in-process for 30 minutes, stale-while-revalidate with a background refresh, warmed on startup so the first onboarding user never hits a cold rebuild. Popular cards cached similarly. note the docstring says "5 minutes"; the constant is 1800s.
Deep links & share
Cloudflare Worker insights-proxy terminates SSL for s.join-insights.com and serves the AASA. Break it and every share link, deep link and universal link dies at once. The AEO site is live and teaser-gated — the full breakdown stays in the app, never on the web.
Data model
26 public tables in Supabase Ireland. Row counts verified live on 17 Aug 2026.
| Table | Holds | Rows | Notes |
|---|---|---|---|
| cards | The idea cards — title, hook, topic, author, cover, embedding, metadata | 697 | metadata empty |
| stories | The paged slides inside each card; is_quote marks a quote slide | 5,048 | 910 are quotes |
| key_insights | Pull-out insights per card | 3,282 | |
| quests | The reflection prompt on a card's last slide | 701 | |
| authors | The thinkers — name, tagline, description, image | 511 | no gender field |
| paths / path_cards / path_axes | Journeys and their ordered card lists | 15 / 293 | 14 paths now complete at 20 |
| card_interactions | Every engagement event — opened, story_view, reflect, save, pass, deeper… | 4,492 | 131 users |
| served_cards | What has been shown, for 14-day anti-repetition | 1,483 | |
| prepared_cards | Pre-assembled decks written by every decision; /v2/daily reads only this | 334 | |
| user_profiles | The intent embedding that drives recommendation | 139 | 139 of 241 users |
| user_path_progress | Position within each journey | 308 | |
| user_clusters / user_card_links / user_self_model | "My Insights" self-portrait, rebuilt nightly | 113 | |
| favorites | Saved cards | 92 | |
| user_signals | Free-text check-ins + their embeddings | 40 | |
| referral_codes | Gift/referral codes | 500 | no redemption surface |
| card_metadata_proposals | Intended output of a card classifier | 0 | never ran |
| auth.users | Accounts | 241 | 9 in the last 7 days |
Vector indexes on cards.embedding: cards_embedding_hnsw_idx and cards_embedding_idx. Three card_id indexes were added to stories, key_insights and quests in Aug 2026 — measured 124.6 ms → 2.3 ms, 54× on the same query, and /v2/daily does 30 of those per load.
Analytics & lifecycle pipe
This is correct and worth stating plainly, because it has been misread before. Insights has Segment, Amplitude and Customer.io, and has had them all along.
Why there is no backend → Customer.io call
Amplitude and Customer.io are cloud-mode destinations configured inside Segment. The app sends once, Segment fans out server-side. The absence of a direct Customer.io call in the backend is the correct architecture, not a missing integration.
Identify traits
13 traits are sent on identify, including ideas_read (pushed on every completion so milestone campaigns see it near-real-time) and my_insights_ready (fires once, at 12 ideas read).
Attribution
Meta App Events + SKAdNetwork + ATT. Paid conversion is keyed on StoreKit Transaction.originalID so a renewal never re-fires a purchase conversion.
Blind spots measure before spend
The whole go-deeper menu is uninstrumented — the button and its three options fire nothing. Share surfaces were unmeasured until Aug 2026 and the fix is committed but not yet built.
The content library
The product is the library. These are the numbers as of 17 Aug 2026.
Scale
697 cards from 511 authors, 5,048 story slides, 3,282 key insights, 701 quests, across 15 journeys.
Journeys
14 of 15 are complete 20-card journeys. women-who-rewrote-the-rules sits at 13 of 20 and is deliberately left for Marie's curation — embedding similarity kept proposing men for a path curated around women.
Quotes editorial
910 quote slides. Only 50 (5.5%) are slogan-length under 45 characters; the median is 100 characters / 17 words and the longest is 399. They read as sentences rather than memorable lines.
No difficulty model
Nothing ranks how hard or how obvious a card is. The user-facing "Level 1–5" is a count of cards completed per topic, not difficulty. cards.ranking (1–20) is a legacy import artifact, not an ordering.
Known gaps & landmines
Everything here is verified against live code or live data, not inferred. Ordered by what would hurt most with a thousand new users.
| Gap | What is actually true | Risk |
|---|---|---|
| Card metadata never written | cards.metadata is empty on 695 of 697 cards; paths.metadata empty on all 15; difficulty_arc and duration_estimate null on all 15. The classifier table exists with 0 rows. | degraded level band, tone bias and level+1 stretch are all inert. Nothing errors. |
notes table does not exist | /v1/notes GET/POST/DELETE all target a table that is absent from the database. iOS never calls it — notes are stored in local UserDefaults only. | landmine 500 for every user the moment anyone wires it up. Notes are also lost on reinstall. |
| Retry coverage ~19% | 39 runtime DB statements are wrapped in safe_execute; 167 are bare. get_full_cards_batch fires two queries in a thread pool with no wrapper. | fragile a transient Supabase blip surfaces as several unrelated-looking bugs. |
| Anthropic key is one point of failure | The same key powers go-deeper chat, Haiku cluster naming and the 03:20 job. | watch when credits run out, all three fail at once and look like three separate bugs. |
| Go-deeper is unused | 1 conversation from 1 user in the 13 days since logging deployed (4 Aug) — and that one is Marie's own test. | unknown the funnel is uninstrumented, so we cannot tell disuse from undiscoverability. |
| Profile rows missing | 139 user_profiles against 241 accounts. Creation is lazy and scattered across a few endpoints. | improving the signal-loss half was fixed and deployed in Aug 2026. |
| Referral codes with nowhere to go | 500 codes exist; there is no surface in the app where a user can redeem one. | gate first confirm in ASC whether these are subscription Offer Codes before any spend. |
| In-process scheduler | Both nightly jobs run inside the web process via APScheduler. | scale they die with the dyno and do not scale horizontally — running two instances would double-run them. |
Rules that exist because something broke
Never deploy to live users without Marie's yes
Deploying, shipping, prod-data changes, anything user-facing today. Investigation, branches, commits and local analysis need no permission.
iOS: Claude writes, Marie builds
Never run xcodebuild, simctl or devicectl. Never archive or upload without an explicit yes in the immediately preceding message.
Never trust the migrations folder
Migrations that exist in the repo but were never applied to prod cause a 500 for every user. Check the live schema. The missing notes table above is exactly this.
Never bump min_ios_version
Not until the new build is actually live on the App Store — otherwise every user is locked out.
Ireland is the source of truth
The US mirror is a read replica and never a write target. No new tables, columns or RPCs without approval.
Monetization code is deliberate
Paywall, RevenueCat, StoreKit and entitlement logic are designed, not buggy. Read the surrounding code and git history before touching them.