Insights — internal
Enter the password to continue.
Insights — Source of truth

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.

Uman C AB · iOS (SwiftUI) + FastAPI + Supabase · repo v3.2.1 (54) · verified against live DB, code and API 17 Aug 2026

🔒 No secrets live in this document. Passwords, API keys, tokens and the Supabase service key are deliberately absent — this page is shareable, and secrets must not travel in it. Every row below tells you where the real value is stored (the backend env on Railway, ~/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.
Overview

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 Railwayatlas-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).

Access

Accounts & logins

Every service and the account it sits under. Team is Marie (owner) + Julien (CTO, GitHub jgaillard, write access to the backend repo).

ServiceAccount / loginKey identifiersWhere the secret lives
Apple Developer / App Store Connectm@umancstudio.comUman C AB · Team 77A25Z388N · App ID 6761016582ASC API key KG6P2943B6 (.p8) + Xcode signing on the build Mac
GitHubMaelwi (Marie); collaborator jgaillard (Julien)iOS + backend reposgh CLI auth / SSH keys on the Mac
Supabasem@umancstudio.comRef bkiodocaqdgtkaixoisn · eu-west-1 (Ireland) · Postgres 17.6SUPABASE_URL / SUPABASE_SECRET_KEY in ~/Documents/atlas-api/.env and the Railway env
Railwaym@umancstudio.comService atlas-api-production-8c22 · US-eastAll backend env vars in the Railway project settings
Segmentm@umancstudio.comSource: Insights iOSWrite key in the iOS build config
Amplitudem@umancstudio.comOrg join-insights-466344 · project 658118API key held in the Segment destination, not in the app
Customer.iom@umancstudio.comLifecycle + pushTrack API key in the Segment destination; APNs key uploaded to CIO
RevenueCatm@umancstudio.comPublic 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
Anthropicm@umancstudio.comPowers go-deeper chat, Haiku cluster naming, the 03:20 jobAPI key in the Railway backend env. single point of failure — see gaps below
Cloudflarem@umancstudio.comWorker insights-proxy · s.join-insights.comwrangler auth on the Mac
Meta / Facebookm@umancstudio.comApp Events + SKAdNetworkClient token in the iOS build config
The logic

How Insights works today

The core loop and the recommendation model behind it — verified against the current code, not the docs.

The daily loop

  1. Deck — a swipeable set of cards for today; keep / pass / "this is me".
  2. Read — the card opens into paged story slides.
  3. Reflect — the last slide is a Quest; tapping reveals a reflection prompt.
  4. 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.

signalweight lookupintent_embeddingpgvector matchfilter + rankprepared_cards/v2/daily
Infrastructure

Architecture

iOS app

Structure

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.

Dependencies

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.

Auth

SupabaseAuthProvider talks to Supabase Ireland directly, not through the backend. Do not repoint this for the mirror.

Backend / FastAPI

Routers

cards, recommend, paths, user, favorites, self_model, deeper, notes, referral, admin.

Services

recommender (the Lens Shift engine), recommendation_agent, self_model, embeddings, suppression, pinecone_client.

Scheduled

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.

Deploy

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

Data model

26 public tables in Supabase Ireland. Row counts verified live on 17 Aug 2026.

TableHoldsRowsNotes
cardsThe idea cards — title, hook, topic, author, cover, embedding, metadata697metadata empty
storiesThe paged slides inside each card; is_quote marks a quote slide5,048910 are quotes
key_insightsPull-out insights per card3,282
questsThe reflection prompt on a card's last slide701
authorsThe thinkers — name, tagline, description, image511no gender field
paths / path_cards / path_axesJourneys and their ordered card lists15 / 29314 paths now complete at 20
card_interactionsEvery engagement event — opened, story_view, reflect, save, pass, deeper…4,492131 users
served_cardsWhat has been shown, for 14-day anti-repetition1,483
prepared_cardsPre-assembled decks written by every decision; /v2/daily reads only this334
user_profilesThe intent embedding that drives recommendation139139 of 241 users
user_path_progressPosition within each journey308
user_clusters / user_card_links / user_self_model"My Insights" self-portrait, rebuilt nightly113
favoritesSaved cards92
user_signalsFree-text check-ins + their embeddings40
referral_codesGift/referral codes500no redemption surface
card_metadata_proposalsIntended output of a card classifier0never ran
auth.usersAccounts2419 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.

Growth

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.

iOS — analytics-swiftSegmentAmplitude 658118+Customer.io+Meta App Events

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.

Content

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.

Risk

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.

GapWhat is actually trueRisk
Card metadata never writtencards.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 failureThe 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 unused1 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 missing139 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 go500 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 schedulerBoth 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.
Operating

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.

Reference

Docs & wikis

📌
Insights wiki — the priority board
THE working document: open items, decisions awaiting Marie, Marie-feedback log, and the session log.
insights-wiki.html
🛠
Infrastructure wiki (earlier, longer)
The deeper technical reference this page summarises.
insights-infrastructure.html
📊
Readiness report
What happens at a thousand users, by subsystem.
insights-readiness.html
📚
Content audit
Cards, journeys and pathways reviewed end to end.
insights-content-audit.html
🔗
HQ link tree
Every wiki, dashboard and live surface in one place.
index.html
📝
Session log
Append-only, newest first — what changed, what was verified, what is still open.
log.md
Your notes on this page
Saves as you type. Copy or save, then paste back to Claude.

Your feedback on this page