Brainstormed 2026-07-06. One new design move: **transactions become
a first-class read surface**, split into two views that match how
members and admins already think about money — right now vs
that session. The v9 single-app/role-aware architecture
stays unchanged; this v10 widens the surface area on top.
Two surfaces, one source of truth (transactions table), one service.
┌─ Top-level: Live ledger card ──────────────────────────────────┐
│ (lives in BriefingView; one card, two role flavours) │
│ │
│ UPCOMING session: "The House hasn't opened yet" + RSVP CTA │
│ + starting_chip_allocation preview │
│ LIVE session: member rows OR per-member rollup (admin) │
│ with running totals, loan distance, live │
│ updates (3s poll or Supabase Realtime) │
│ NO active session: most recent settled session summary card │
│ + "Open in Archive" link │
│ │
│ Tap → SessionDetailView (live session, scrolled to ledger) │
└───────────────────────────────────────────────────────────────┘
┌─ Archive tab ──────────────────────────────────────────────────┐
│ PastSession row ── tap ──→ SessionDetailView (NEW) │
│ ├─ Header: date, theme, location, status, season │
│ ├─ Members table (admin: all; member: self only) │
│ ├─ Transaction ledger (per-session, all 8 categories, │
│ │ admin: filter by member + category; member: own rows) │
│ ├─ Loans from this session │
│ ├─ Notes / highlights (from SessionRecap.highlights) │
│ └─ Admin: CSV export of this session's ledger │
└───────────────────────────────────────────────────────────────┘
not Standing. Briefing is already the action hub per the v10 refactor; Standing is the aggregated view. Different jobs.
(names + amounts) — transparency over privacy-by-default. Running totals of other members are hidden behind a "show full leaderboard" tap inside the session detail, so Standing isn't eaten by the Archive. (User verbatim: "yes, this sounds good for transparent UX.")
tap to flip. Same query, different member_id filter. Useful for the admin watching the floor.
active or historical loan** — show all loans (open + paid off), not just open ones. Members want to see "I paid off the loan at session X" as part of their story. (User lean: show all.)
per session detail. Exports that session's ledger (member, date, event, category, amount). Numbers opens it. (User lean: ship in v1.)
(mirrors the existing live settlement board pattern); snapshot for everything else. No Supabase Realtime in v1 — keep free-tier compatible. Admin's live view polls; member's live view polls; both cost the same.
The v9 sketch (mentally) treated the ledger as one screen. The correction splits it into two surfaces that already have natural homes:
come to Briefing to act; the live ledger is the read-side of those actions. Same tab, same mental model.
past sessions. Members come to Archive to look back; the per- session ledger extends that look-back with row-level money history. No new tab needed.
The Archive is already filterable (season + my-attendance + search per the v12 batch); the per-session detail inherits those filters for free. The Briefing already loads the active session via appState.activeSession; the live card reuses that data path.
One service, parameterised:
// LedgerService (NEW) — replaces the existing
// `loadWithdrawalLedger(eventId:)` narrow AppState method
func loadTransactions(
eventId: UUID?, // nil = across all events
memberId: UUID?, // nil = all members
categories: [Transaction.Category]? // nil = all categories
) async -> [LedgerEntry]
Callers compose:
SessionDetailView (admin): eventId=session.id, memberId=nilSessionDetailView (member): eventId=session.id, memberId=me.id (RLS enforces too)MyLiveLedgerCard (admin): eventId=activeSession.id, memberId=nilMyLiveLedgerCard (member): eventId=activeSession.id, memberId=me.idThree new files, one absorb:
MyLiveLedgerCard.swift — NEW. Lives in Features/Briefing/.Briefing already exists; this slots in as a card at the bottom of the existing BriefingView body. Polls every 3s when session is live.
SessionDetailView.swift — NEW. Lives in Features/Archive/. Reached via NavigationLink from ArchiveView's existing session row. Snapshot (no polling) — past sessions don't change.
LedgerService.swift — NEW. Lives in Core/Ledger/. The parameterised loader above. Mirrors AdminService patterns.
WithdrawalLedgerView.swift — ABSORBED into MyLiveLedgerCard.swift.Rename the file, broaden the query from buy_in+rebuy to all categories. Delete the file after merge.
No new Edge Functions. All reads via anon-key Supabase. The migration surface is small.
transactions SELECT for authenticated (members): confirm that a member reading with the anon key can already see their own rows. If not, add a policy: FOR SELECT USING (member_id = auth.uid()).
cashout_adjustment reversal rows: the admin-driven adjustment flow inserts a row with the affected member's member_id. That row must be visible to the affected member (so they see the reversal). Confirm the migration 20260630_0013_cashout_settle_to_net_worth.sql respects this scope. If reversal rows are inserted with member_id = admin, that's a bug to fix first — it hides adjustments from members.
admin RLS. Verify with the 5-query pre-migration recipe (information_schema + pg_constraint + pg_policies + pg_proc) before drafting the new migration.
(member_id, created_at) — confirm the transactions table has it. If not, add in the same migration. Otherwise the per-session ledger query is a sequential scan on a table that grows every session.
Reversal rows (cashout_adjustment) show inline next to the original, with a strikethrough on the original amount. Members need to see "the House changed the number" — that's trust-critical. Don't bury the reversal in a separate "adjustments" tab; show it adjacent to the row it reverses. The math already lives in cashout_settle_to_net_worth migration; the UI just needs to render it honestly.
Subtle, not loud. Reserve Felty for:
Plain copy for the ledger rows themselves. Felty narrating every ledger entry is noise, not voice.
the live card + session detail are the row-level views).
recap already cover the touch points).
go through existing Edge Functions).
a separate project).
transactions per member; add a cursor when one member crosses 1k rows).
missing. One migration file. Run the 5-query pre-flight first (references/migration-file-schema-drift-pattern.md).
LedgerService — the parameterised loader. Tests first.MyLiveLedgerCard — extract from WithdrawalLedgerView,broaden query, drop into Briefing.
SessionDetailView — new screen in Archive, NavigationLinkfrom the existing session row.
"Save to Files" / Numbers.
toggle (admin only), Felty empty states.
WithdrawalLedgerView.swift (existing admin narrow ledger — absorbed)AdminService.swift (admin read patterns)Member.swift (role enum, isAdmin semantics)PastSession.swift (id, date, theme, location, status, archivedAt — no new model)
ArchiveView.swift (the existing container — gains aNavigationLink, no other change)
MainTabView.swift (tab structure locked: Briefing / Archive /Standing / Settings)
migrations/0001_init.sql (transactions categories)migrations/20260630_0013_cashout_settle_to_net_worth.sql(reversal pattern + idempotency)
originals/felt-faction-ios-app-v9-invite-in-app-no-telegram(predecessor; role-aware single-app architecture)
originals/felt-faction-ios-app-v5-felty-as-the-bank (Feltyvoice canon)
originals/felt-faction-ios-app-v4-bank-and-phoenix-arc (loan+ Phoenix distance pattern)
CLAUDE.md (role-aware nav conventions)
Published and managed by TARS, an AI co-author built on Nathan's gbrain.