ActsControl room

Send a tester a link — it opens on their phone like an app. Everything they do shows up here and grows the content.

Build doc · STATUS.md

Build log — STATUS

The running session log: everything shipped, newest first.

ACTS — build status / resume doc

Last updated 2026-09-05 (NEW MACHINE; wall + comments + faked push live on TestFlight via OTA). Read this first to resume.

2026-09-05 (night) — the design review: four levels of clickables

  • Rule: the vermillion 2-pt underline means SELECTED (L4 nav state) and nothing else; every decorative rule is a hairline in line. The INTENTIONS rule was the same style as the active tab — fixed.
  • Levels (DESIGN.md §6, tokens in expo/lib/theme.ts): L1 primary filled button (BTN) · L2 secondary outline/pill/act-colored bar text · L3 tertiary Marian-blue link + chevron (LINK_TEXT) · L4 nav state (TAB_UNDERLINE).
  • Feed card: face beside one text column (title · place-if-going · 💬 n) with 8-pt steps; hairline; then ✓ badge left, three act icons with counts right (hands · candle · pin) in equal cells. Server sends going and per-kind counts per need. Last OTA: 3973855.

2026-09-05 (evening) — icons, the wall's rows, the change-location steps

  • Wall rows: one type icon per row at the right — the logo's hands (PrayerMark hands: no heart, no rays) for prayed, Bryan's own candle (CandleMark SVG, ink body, hollow orange flame T.flame), Material glyphs for the rest, a speech bubble for comments; one ink, no circles; icon box one text line tall so it sits on the name. iMessage-style inset separators. No points on rows. Action bar is ALWAYS buttons (a ✓ badge there was a misread, reverted). Same icons on the profile's acts (shared KindIcon).
  • Feed card: chevron only; "✓ Prayed / Candle lit / …" as a solid green badge when acted. Faces on cards.
  • Change location (Near tab): Change location › → Cancel ×; white inset panel; field focused on open; keyboard down on success; the list scrolls by the masthead's measured height (minus the compact bar) so the panel sits high, clear of the keyboard. Verified on the simulator end to end.
  • Profile places: one quiet line "Oakland · Chapel of Grace ›", tap to edit, neutral gray (T.gray), "Change" quiet.
  • Ship guard held all evening: three chains stopped on failed edits/types.

2026-09-05 (later) — faces, and the feed nav settled

  • Faces: 27 photographic placeholder portraits (randomuser.me — made for demos), one per persona + Sally/Steve/Steve Ortman, public/avatars/ <slug>.jpg; lib/avatar.ts avatarFor(name). On every wall row, the profile, and the feed (helper on offers, seeker on needs). Cartoons (DiceBear) were tried and rejected — "no cartoons".
  • Feed nav: Near and Congregation both sort AND carry a change line — "Change location ›" / "Change place of worship ›" (the tab already names the place). Congregation FILTERS. Need cards: meta + chevron; the three actions live on the card (wall): "Pray • Go there • Comment", square box.
  • Profile: white header → gray block flush (no gap) → "You are in X · CHANGE" / "You worship at Y · CHANGE"; CHANGE opens a white inset panel.
  • Vercel deploys main on push (git integration) — verified: server fields and avatar files were live within a minute of each push. Manual vercel --prod is not needed; a "Not authorized" from it is harmless.
  • Ship guard: every chain now stops on a failed edit or typecheck (python … || exit 1, tsc; RC=$?). Two no-op/dirty OTAs earlier today were the cost of not having that.

2026-09-05 (late) — square boxes, the parish tab filters, the feed's break is color alone

  • Wall + need cards: actions in a SQUARE gray box (no radius) — "Pray • Go there • Comment"; the composer hidden until Comment, closes after send.
  • Profile: the feed's own bar language, twice, one square gray block — "You are in X · CHANGE / You worship at Y · CHANGE". No pencil, no emoji.
  • Feed: bands above INTENTIONS removed; the white sheet against the lighter ground is the break. The parish tab FILTERS (it only reordered; 1 need in 30 carried the layer) with an "Ask at <parish>" empty state.
  • Avatars on comments: DEFERRED — one avatar image exists (john.jpg).
  • Still pending Vercel: feed joinedByMe → "✓ Intention added".
  • Lesson logged: an OTA once shipped past a failed typecheck because the guard read PIPESTATUS in zsh; the chain now uses tsc; RC=$? and exits. Last OTA of the day: ec38b3a.

2026-09-05 (afternoon) — the wall as one language, the feed pinned

  • Wall: acts and comments in ONE row style (bubble · bold name · inline verb/words · small pts · time · hairline); actions one gray line "Pray • Go in person • Comment", no points; composer hidden until Comment, closes after send. Feed: INTENTIONS + tabs pinned (cards roll under), ground T.paperLight, heavier band. No opt-out on the $5 ask. 9f2cc4d.
  • Server still pending Vercel: feed joinedByMe → "✓ Intention added".

2026-09-05 (midday) — the sloppiness pass: grid, radii, tap targets, lint

  • Apple-grid audit of the native app (Bryan: "we are sloppy again"): 176 off-grid values across 18 files. 78 padding/margin/gap values snapped to the SP grid (4/8/12/16/20/24/32); 10 card radii → T.radius / 8 / 4; 21 bare Pressables given a 44-pt reach; 8 decorative Images given alt="". eslint on expo/ for the first time: 8 errors → 0, 12 warnings → 0 (FakeNotification: Animated.Value held in state, ES import for the icon).
  • Proof (visit) page tidied from Bryan's photo: one card of hairline rows, one-line proof pill, "COMPLETE this visit". b00455e.
  • $5 ask ×3 with opt-out (DonateAsk component; "Don't ask again" remembered), slim profile bar, card opens the wall with Pray · Go in person · Comment, ✓ Intention added on offers (server field — needs Vercel). f4bc2ab.
  • Client notes system: docs/client/ + STEVE-ASKS.md + /client-notes + the CLAUDE.md rule. Steve's 09-04 meeting notes still unrecovered (Dropbox on the new Mac synced files but no folders yet).

2026-09-05 (morning) — profile YOU block, feed follows the profile, social comments

  • Feed follows a place changed on the profile (was: located once, never again). Every focus compares the chosen place to the ranked one. Verified.
  • Profile "getting sloppy" → one gray YOU block under the header: LOCATION | WORSHIP LOCATION, round pencil → in-place picker + parish link (pencil becomes ×). Connect-CTA + MY PLACES sections removed; unconnected parish reads "Connect · +500 pts" in the block. Label no longer lags the server (setChosenPlace awaits the sync; label set optimistically).
  • Wall: newest of everything on top; comments restyled like a current social app (initials bubble, bold name inline, hairlines, no box).
  • $5 ask after joining a traveler (same as after posting a need).
  • Sort tabs scroll, natural width. (A "Near Oaklan" in testing was my own typed "Oaklan" — not a clip.)
  • All on main, OTA'd: 06c7b28 · 645dac0 · 18eb075. No server changes today.
  • Steve's notes, native-only scan: NOT on his phone — share cards, novena, geo tags (hospital/airport/prison/stadium), pledges (copy only), widget content (pray-at-noon / Today in the Church), social sign-in.

2026-09-04 — NEW LAPTOP, THE GATHERING WALL, AND A DAY OF CLIENT ASKS

  • Machine migration: old MacBook → new (Thunderbolt bridge, SMB). ~/Acts copied; Claude folders staged in ~/claude-migration-2026-09-04/ (not swapped in). gh · Vercel · EAS CLIs authed; Xcode 26.6 + iOS 26.5 sim runtime (23F77) installed; iPhone 17 Pro simulator runs the app in Expo Go (npx expo start --ios). eas update MUST carry --platform ios (no react-native-web; export dies otherwise). Ship order: Vercel first, then OTA — apiUrl is baked to acts-ten.vercel.app.
  • PR #1 MERGED → main 2296d0f (2026-09-05). main = production = Steve's phone. Everything below is live: Vercel deployed, OTA'd to build 1.0.1 (7), channel production.
  • The gathering wall, native (expo/app/wall/[missionId], /api/native/ wall, /api/native/comment): ONE CARD PER ACT, taps included, each labelled — "Sally · prayed +10 pts", "Steve · lit a candle +100 pts" — comments tight below, composer at the bottom. Labels resolve from activity_event via lib/data labelActs — service role only: the log is not anon-readable (found by screenshot: every act read "acted").
  • Feed: cards link into the wall ("Leave a word ›" / "💬 N comments ›"); done-label says WHAT you did — "✓ Prayed" / "✓ Candle lit" (actedAs).
  • Faked push (components/FakeNotification.tsx): in-app banner dressed as iOS, 5 s after an act, 8 s hold, tap → wall. Pure JS (expo-notifications = new build). Two gotchas cost an hour: native stack needs FullWindowOverlay; native-driver spring must start in an effect AFTER mount. Verified on the simulator.
  • Profile: MY ACTS list (each row → wall); WHAT I ASKED list, 5 rows then "+N more"; ledger line "5 prayers" (was "You 5 prayers."); ↺ Start over — experience Acts fresh easter egg at the very bottom (confirm → forget identity + place → day one).
  • Steve's big ask: post a need → "Thank you." → "A standard donation is $5 — would you give one today?" Yes → /donate?amount=$5; No → feed.
  • pts follows every number ("Pray · +10 pts", "+500 pts"; Connect pill's amount is a smaller run, single line).
  • GPS tighten → degrade (Bryan in Oakland read "San Francisco"): cached fix only if <2 min AND <1 km, else fresh High fix; label by confidence — town / "around County · approx." / no name. Unverified on a real phone.
  • New York for the client: St. Peter's — Barclay (22 Barclay St) and St. Peter's — Jazz Church (619 Lexington) in place, significance 5 → both LEAD the nearby list… only east of the Hudson (lng > −74.025): Hoboken/Jersey City see them at distance rank. Proven on prod.
  • Persona pool: common Anglo-American names, no Brian/Steve (client); slots 0/14 (John Jackson, Paul Turner) unchanged. Live.
  • Demo data: Sally (prayer) + Steve (candle, note, NO photo — the only image was a chair; needs a real candle shot) seeded on HFC as demo-* profiles; real Steve Ortman untouched.
  • Server: DEPLOYED (verified on prod: Steve · candle without a photo; feed sends actedAs).
  • App Store blocker (deferred by decision): Guideline 1.2 UGC — report, block, post-time filter, EULA. comment already has status / moderation_reason / report_count; hideComment/deleteComment exist.

2026-09-02 — BETA APPROVED + THE DAVID SESSION (Utah field QA)

  • Apple approved the external beta ("Testing") — invites live. Steve's email sent, not yet opened (watch the roster). An Apple-adjacent Sunnyvale device walked the full flow pre-review (585 pts, Raynor Park parish).
  • David (Bryan's brother, Eagle Mountain UT, roster persona "Paul Turner") field-tested and drove a fix sprint: 100km cap on near-you churches (Chimayo is nobody's parish from Utah) → formalized as the THREE PLACE TYPES canon (memory: place-taxonomy): pilgrimage destinations · my place of worship · go-to-a-church; same-named LDS wards disambiguated by street (compass+distance fallback, v3 cache); denomination-neutral wording ("place of worship", not "parish"); "Pray from anywhere" vs "Go in person" copy; Change/EDIT place of worship (profile header line: "Place of worship: X · EDIT"); street ADDRESSES on church rows + Apple Maps directions (nearby rows tappable, visit screen button); church SEARCH bar (modal + Offer picker, 25-church depth); WITHIN 5/10/25/50 mi radius chips (nearby API radiusKm, capped 80km); MILES everywhere (lib/units.ts); keyboard avoidance on all input screens; Offer picker gained pilgrimage section ("worth the journey" epic offers).
  • Tester ops: tester_roster + tester_activity views (admin-only) via migration; every native device gets a persisted pool persona; Steve → "Steve Ortman", Brian → "Brian Kelly" on arrival (manual rename — MISSED on 2026-09-08: he opened as "Samuel Wright" and told us; renamed after the fact with scripts/name-tester.mjs. Rename BEFORE the invite). Christian-only filter = OSM religion=christian at the query (denomination knob available if the client wants finer).

2026-08-31 (night) — POLISH MARATHON + EXTERNAL BETA SUBMITTED

  • TestFlight external testing submitted (group "Friends & Family", build 1.0.1 (7), sign-in-required OFF — guest app). Status: Waiting for Review. When Apple approves, Steve's invite email sends itself — he taps View in TestFlight and installs Acts of Faith. STEVE IS EAST COAST.
  • Locality seeding: locating in an area with no requests within 80km plants two demo asks at the two nearest REAL churches (Margaret's candle, the Rivera family's hospice prayer) — once per church, seeker profile guest_key demo-local-seeder. Verified at Princeton NJ (1.0km/1.1km), then test rows removed. The demo feels local anywhere on Earth.
  • The parish loop: sticky "Connect with your parish · +500" on feed + profile (Marian blue #2e6ca8 = the third color; vanishes once connected) → GPS modal: "Finding you…" → "We located you to: <city>" with EDIT (on-device geocode re-searches any town) → SELECT YOUR PARISH → tap a real church → +500 + St. Joseph card. Feed tabs: All · Near <city> (GPS-ranked, reverse-geocoded label, re-locates on focus once permission exists) · ⛪ <parish> (parish-layer first). Milestone parish points 150→500.
  • Design rounds (Bryan driving, ~30 OTA updates): masthead per Bryan's comp (bold-italic sans acts + GIVE HELP • GET HELP + mark); white header sheet + white tab bar (solid icons, tighter/lower) framing the cream feed; INTENTIONS label (not "feed"); underline text-tab sorts; Apple-quiet cards (card IS the button, tinted-text actions, vermillion left rule marks needs); doors borderless soft fills w/ chevrons; HIG spacing tokens (SP/BTN/BTN_TEXT in expo/lib/theme.ts — memory: native-spacing-canon); BackArrow component everywhere (inset-aware overlay); completion screens on one done-pattern (body centered, actions bottom, Donate on every one); native /donate screen (mockup); profile = "devotional ledger" (white-sheet identity, typographic points hero, MY ACTS "5 prayers • 3 candles • 1 visit" from actCounts, MY PARISH); BadgeSeal (scalloped seal, lit candle center); avatar john.jpg; persona John Jackson; Helper badge +50 at first open; levels renamed to badge language (Helper→Candle Bearer→Pilgrim→ Mercy Witness→Mission Champion).
  • Features: offers clickable → join screen (+10, intention rides along); feed cards show proof photos ("N acts witnessed"); offering-sent shows the candle photo itself; junk helper names anonymized; prod wiped to the crafted demo (Aug 24+ test content deleted); Bryan's device reset to day-one John Jackson for Steve's-eye review.

2026-08-31 — TESTFLIGHT: "IT WORKS" (native iOS on Bryan's iPhone)

  • Pipeline stood up end-to-end: Bryan reinstated his Apple dev account (individual, team D62K9CN2A8) → expo.dev project acts-of-faith (Hobby) → EAS credentials + ASC API key (Admin) stored → builds/submits now run non-interactively from this machine. ASC app "Acts (668412)" id 6807167318 (display name: Acts of Faith — "Acts" store name held by Chris's old build; Steve has TestFlight + Chris's dead icon already, see memory).
  • The launch-crash hunt (build 1 crashed on Bryan's iPhone, iOS 26.6.1): .ips showed SIGSEGV inside Hermes GC — SDK 54's engine predates iOS 26. FIX = SDK 57 upgrade (RN 0.86, expo-router 57). Build chain also needed: fresh package-lock (EAS strict install), reanimated 4.5.1 + worklets 0.10.1 pin (Xcode executeSync error). Build 6 opens clean — "it works".
  • OTA updates wired (expo-updates, channel production, runtime = app version 1.0.1): JS fixes ship in seconds via eas update. Build 7 (the STEVE build) adds checkAutomatically ON_LOAD + 10s wait → updates apply on FIRST launch, zero ritual for a tech-illiterate tester.
  • Review round shipped OTA: Lord's Prayer shows Bryan's Jesus watercolor (public/saints/jesus.jpg, also the card art); Saint's Prayer = FOUR default carriers (Steve: less gamified) Jude → Michael → Francis → Guadalupe, each portrait rides the prayer through (Jude's classic green-cloak art at public/saints/jude.jpg); feed cards show "✓ You acted for this request" (server acted-flags per guest key, refetch on focus); sent screen reveals ONE new badge ("✦ You earned the Helper badge ✦") + "📷 Light a candle for them — photo · 2×" (camera act on ANY need); My Profile rebuilt (points hero, level progress, acts/streak, single latest badge).
  • OSM CHURCHES — nearby is real everywhere: /api/native/nearby merges OpenStreetMap (Overpass, UA-identified, 7-day grid-cell cache in activity_event, no-empty-cache, curated ✦ holy places on top). Verified: Oakland (St. Mary's 0.5km + real locals) AND Denver (unseeded city, real parishes ~2km). NearbyPlacePicker module powers the Offer stall's "where are you headed?" — GPS → tappable church list.
  • get_app milestone card now grants St. Jude (was Jesus).

2026-08-31 — CORRECTION: STEVE HAS AN IPHONE (Bryan)

  • Nothing rebuilt — one codebase, iOS bundle already compiles; the safe-area work helps the iPhone notch/home-indicator too; iOS back = swipe gesture + the in-app "‹ back" links already on every pushed screen.
  • Priorities flipped: Apple $99 FIRST (TestFlight = the real path to Steve's iPhone); Play $25 waits for actual Android testers. docs/STORE-SIGNUPS.md updated. Free interim: expo.dev + eas update → Steve opens Acts inside Expo Go from a QR over the internet (preview, not a delivery). Bryan can dev-test on his own iPhone via Expo Go on Wi-Fi.
  • Added iOS camera/photo permission strings (expo-image-picker plugin) — required for App Store review of the photo-proof loop.

2026-08-29 (night) — NATIVE ANDROID BUTTONS & BEHAVIORS (Bryan: the build

that reaches Steve must respect them all)

  • Tested in CLASSIC 3-BUTTON navigation mode (◁ ⃝ ▢ — the mode older Android users typically run): tab bar clears the system bar ✓; system BACK pops routes correctly (pray path → feed, verified) ✓; Home/recents untouched (standard OS behavior) ✓.
  • FIXED a real one: the sticky footers ("Enter Prayer Mode", visit COMPLETE) and pinned headers used hardcoded padding and sat UNDER the 3-button bar — now all use useSafeAreaInsets (gesture nav unaffected). Emulator left in 3-button mode for future testing (Settings → System → Navigation mode to flip; adb shell cmd overlay enable/disable com.android.internal.systemui.navbar.threebutton).

2026-08-29 (night) — ANDROID HARDWARE VERIFIED ON-DEVICE (Pixel 10 emu)

  • CAMERA PROOF AT A PLACE, end-to-end: visit loop check-in (+25, GPS witnessed) → "Light a candle · 📷 Photo proof · 2×" → emulator camera → shutter → confirm → base64 → /api/native/place-act → Supabase storage → ✓ +1,100 (2×50 + 1,000 photo bonus — the web's exact math) → COMPLETE → +1,145 total + the St. Anthony card revealed.
  • GPS NEARBY: /nearby lists acts-at-places + churches by TRUE distance (haversine over real place coords; Oakland churches seeded/coord-fixed: St. Mary's 0.5km · Cathedral of Christ the Light 0.8km · St. Columba 3.6km from downtown). getPosition() helper never hangs (last-known first, 8s race — emulator quirk fixed). Home: "📍 Find acts & churches near you →"; place-anchored needs get "Do it at the place · up to +250".
  • New API: /api/native/{nearby,checkin,place-act,complete}; complete grants the place's saint card (same deterministic pick as web).
  • Landing fixed: NO Astro anywhere — Expo native replaced that idea (Astro was only ever the builder of Steve's original prototype).

2026-08-29 — THE CONSOLIDATION (Steve's meeting, Otter transcript in Dropbox)

  • ONE VERSION: the marketplace. Steve picked the market design + colors. /app IS the market home now (doors + zipped needs/helpers feed); stalls live at /app/ask · /app/offer · /app/profile. Old routes redirect: /market/* → /app/*, /hybrid → /app, /amen/* → /app.
  • One skin. lib/skins has a single market entry; new [data-skin="market"] tokens (base + .feed-scope) so every deep flow (pray · visit · /m · /p · moments · points) wears market cream/vermillion/ green. Skin switcher removed (SkinScope kept — future reskins stay token swaps). Old skin CSS remains in globals as archive.
  • Nav per Steve: bottom = Home · Ask · Offer · My Profile (not "You"); the "U" next to the burger is gone; the hamburger STAYS (Bryan's testing argument) and holds ONLY My Points · My Profile · Donate · About (new /about page). HomeNav (flow dock) = the same four doors. /app/you is retitled "My Points".
  • Social sharing pulled from completion (kept in code for later): PrayFlow sent → straight "Return to Feed" (no invite card, no shared step); VisitFlow logged/done and MomentJoin lose their share cards. ShareRow component + /p social landing remain.
  • Celebration simplified (Bryan): small centered confetti pop, market colors, NO image (Jesus-light removed), ~2s, and fires ONCE per act — PrayFlow now bursts only at "Offering sent" (was hub + sent = twice).
  • Verified: dev walk (home, menu, ask/offer/profile, full pray flow, moment join) + production build clean.
  • GONE ALL-NATIVE (Bryan): expo/ is a real React Native app, not a wrapper — Expo Router, four tabs (Home feed w/ one-tap pray · Ask · Offer with REAL GPS place-fill · My Profile), market tokens in lib/theme.ts. Data layer = new /api/native/* routes on the existing backend (feed · me · pray · ask · offer) keyed by a device guest key (SecureStore) — same server brain (recordAct anti-farm, awardPoints, badges). Smoke-tested the full loop by curl: new device prayed +10, asked +25, ledger read 35. iOS AND Android bundles compile (983/979 modules; serif is Platform.select — Georgia is iOS-only, Android gets Noto "serif"; eas preview profile builds an installable APK). Steve + his partner are ANDROID users — the APK link is their fastest path to Acts-on-phone.
  • RUNNING ON ANDROID (verified live): Bryan installed Android Studio (Quail 3) + created a Pixel 10 AVD; drove it via adb — emulator booted, Expo Go auto-installed, the native app loaded (1103 modules), doors + vector tab icons fixed (Link-asChild had dropped Pressable styles; @expo/vector-icons added), and an on-device "Answer this need" tap hit the prod API: Prayed ✓, count 4→5, anti-farm correctly paid 0 on the repeat device. Native Android is REAL. Dev loop: emulator OR Expo Go on any phone via npx expo start.
  • Native polish round (Bryan, live on the emulator): ① the praying-hands LOGO in the home header (react-native-svg port of PrayerMark) + a generated 1024px app icon (hands on cream, wired into app.json for homescreen/splash/ adaptive icon); ② STICKY collapsing doors — scroll past the header and a pinned bar holds the mark + "I need help"/"I can help" pills (the web's collapsing-doors behavior, native); ③ THE PRAY PATH — "Answer this need" no longer insta-prays: /pray/[missionId] walks intention → choose prayer (Heartfelt · Lord's Prayer) → full-screen prayer → "Amen — Offer this Prayer" → "Offering sent." All three verified by adb walk on the Pixel 10 against prod (anti-farm correctly zeroed repeat prays). ④ A SAINT'S PRAYER — third choice on the path: 9 saints (Jesus · Carlo · Joan · Francis · Michael · Jude · Thérèse · Guadalupe · Padre Pio) as "who will carry it?" chips with patronages; prayer mode shows the saint's line + invocation. Verified: St. Jude for a live request. ⑤ SAINT PORTRAITS — /api/native/saints serves the deck's RESOLVED art (curated uploads win, same pipeline as the web /saints tab): portrait chips in "who will carry it?" (full 14-saint deck) + the ringed portrait above the prayer. Verified: Carlo's real uploaded art on the Pixel 10. ⑥ Choose-screen polish (Bryan): "Enter Prayer Mode" is a STICKY bottom bar — the saint list scrolls under it; St. Jude (hope in hard causes) carries by default, so the button is always live (grays to "Choose who will carry it" only if no carrier). No Xcode on this Mac → dev loop is Expo Go on the phone; EAS builds are cloud. While the Apple check clears: expo.dev account + eas init are free, and Android APKs need NO store account (eas build -p android --profile preview → installable link).
  • (superseded) the earlier WebView wrapper: WebView wrapper around the production app (same-origin stays in-shell, other links open the browser; Android back = web back; market cream chrome; bundle ids com.actsventures.acts). eas.json build/submit profiles + README with the exact commands. docs/STORE-SIGNUPS.md: the Apple ($99/yr, D-U-N-S notes) · Play ($25) · TestFlight signup walkthrough — the accounts are BRYAN'S (individual enrollment, transferable to an Acts Ventures org later; expense the fees), then everything else is eas build + eas submit.

Previously: 2026-08-17, pre-Steve-call (Bryan's NIGHT LIST of 9 — all shipped). Read this first to resume.

2026-08-17 — demo data + refresh lever + visit door

  • /refresh — one URL purges every cached page site-wide (revalidatePath from the root layout). Hit it after any data change.
  • Steve-demo data curated: 14 nonsense missions purged (jb;hbhjb etc., children first: proofs/comments/story cards); 10 HEARTFELT requests seeded (Rosa's surgery · Danny 28 days sober · Sister Agnes at St. Mary's · Grace's biopsy · the Alvarez family · Elena & Tom · Marcus 17 · Grandpa Joe at St. Columba · Nurse Kemi · the valley floods) with live praying counts (9–63) + a word of encouragement each. 31 missions total, all real.
  • "Acts at a place" on the hamburger → /visit door (redirects to the freshest place-anchored request's location loop).

2026-08-17 — STEVE'S LANGUAGE RULE: you ask for an ACT

  • Steve (reviewing, liked it): "Ask for an ACT — correct; ask for a prayer — incorrect." Swept en masse across every version: feed door ("Do you need an act?"), menu, cards view, /get, /p ("Someone asked for an act"), AMEN ask, MARKET door ("Ask for an act — a prayer, a candle, a visit"), Ask wizard title + posted screen ("Your ask is posted."), roles, site meta description. OFFER side (Bryan's follow-up): decision points say "Offer an Act" (feed card CTA, /m CTA, immersive rail, pray-flow entry "You are offering an Act"); once prayer IS the chosen act it says prayer ("Amen — Offer this Prayer", the /p one-tap, "Say the prayer here"). Zero residual "ask for prayer" matches; production build verified locally.

2026-08-17 — EXPERIENCE MODES (pre-meeting): you stay where you entered

  • Bryan on phone: entered HYBRID, a click later he was in the classic-brown triptych. Fix: acts_mode cookie (ModeMarker set by MarketHome/AmenShell: hybrid · market · amen) — ① shared flow pages adopt the matching skin (hybrid/market → modern, amen → acts) via the skin store; ② "/app" bounces back to the experience's OWN home (meta-refresh redirect; ?skin= links escape + reset to classic); ③ the market stalls (/market/ask · offer · you) wear .hybrid-scope when the mode is hybrid. Landing embed pinned to /app?skin=acts so the hub phone never gets hijacked by the cookie.
  • AskWizard's "Connect to your parish and earn a saint card" is now a LINK to /start/parish (Bryan).

2026-08-17 — THE NIGHT LIST (9 items for the Steve call, all live)

  1. /design opens with VERSION CARDS — 5 across: icon tile · name · 4 color swatches · identifying phrase · the metaphor at the foot (7 versions).
  2. $PRAY — THE MEME COIN OF MERCY, /app/coin (Bryan: a blockchain meme coin): gold coin + ticker + $PRAY/USD price (▲% since genesis), up-only sparkline, your bag / holders / market cap, tokenomics ("every act is a block", "no rug possible — the ledger is mercy"), testnet contract mock 0xPRAY…mercy. Price is deterministic from world acts; 100 pts mint 1 $PRAY. The real chain arrives with the wrapper. Linked from ladder + menu.
  3. CLIENT MODE — /c/steve IS the hub, filtered (v2 after Bryan: "same as my page, just fewer tabs"): the link sets an acts_client cookie and opens the real hub — identical pages, but the TabBar shows ONLY the tabs checked in Admin ("Steve's page" section, plain-form Save → activity_event latest-wins). Admin/Users/Paths etc. hidden for him (Admin is never even offerable). Header reads "Prepared for Steve" + exit; /c/exit leaves the mode. In client mode HOME leads the nav, before the Versions group. Verified in-browser both ways. New clients = new URL, zero code.
  4. CARDS ARE EARNED AT PLACES: completeAtPlace grants the place's saint (deterministic per mission) and the done screen reveals it ("This place gave you the {name} card →"). Widget milestone → Carlo; parish → Joseph.
  5. INSTALL THE APP → THE JESUS CARD: new "jesus" card leads SAINT_DECK; get_app claim grants it; MilestoneCta reveals "The Jesus Christ card is yours — pray to it →". Art: public/saints/jesus.jpg when dropped.
  6. CELEBRATION v3 — JESUS RECEIVING LIGHT: the award celebration is now an image of Jesus gaining light in waves until totally lit (~3.6s, halo bloom, then fades). Same Confetti contract → all award surfaces upgraded at once. NOTE: uses the shrine stand-in photo until public/saints/jesus.jpg exists (one drop fixes the celebration AND the card).
  7. ARTIST EDITIONS: 8 top cards carry artist credits (fictional ateliers — swap for real commissions) — ✦ chips on the deck, credit line on /card.
  8. THE EARN LADDER opens /app/you: pray +10 · app +50 · widget +50 · account +100 · share +100 · post a request +25 (NEW — createMission now pays ASK_POINTS) · do an act +25–250; done-states struck through.
  9. THE WORLD — /app/world + hub tab + menu: world events as shared intentions (world audience layer), one tap to pray with the world, and the add-a-world-event form (+25, lands in every feed). Form click-path could not be browser-tested (the preview pane's renderer died — code path is the same createMission used everywhere); SSR + feed rendering verified.
  • QA: all new + core routes 200 (/c/newclient falls back to defaults); typecheck clean throughout; each item its own commit.

2026-08-16 — demo-day batch (Bryan shows Steve tomorrow)

  • Confetti v2 = a centered EXPLOSION, longer (Bryan): pieces detonate from mid-screen on their own angle, arc out, then gravity + fade, ~3–4s (was top-rain ~2s). confetti-boom keyframes with per-piece --tx/--ty/--fall; two-phase easing (blast → gravity).
  • Confetti now fires on EVERY points-gaining activity. Newly wired: PrayButton (+10 tap) · StrippedPray (/p and /free one-tap) · CompleteProof · MilestoneCta claims · VisitFlow CHECK-IN (celebrate-per-step state so mere navigation never replays) · PrayFlow invite milestone (+100 on share) · helper-offer joins (new JoinOfferButton client component replaces the server <form action> in OfferCard + MarketHome — "Sent ✓" + burst). Funnel steps that navigate away on award (signup/parish/location) don't burst — nothing to see mid-redirect; their next page shows the ledger.
  • FREE PRAY — the app-free social page + hub tab (first in Versions): /free is the evergreen link (redirects to the freshest open request's /p one-tap page — no install, no login, works in in-app browsers). Tab explains the funnel + live embed + share row.
  • Map strip pills fixed for embeds (Bryan's screenshot): chips were mid-wrapping ("1,101⏎praying"). Now whitespace-nowrap + tighter metrics + "6 at places" copy — whole pills only, never broken text.
  • QA sweep for demo day: all 46 static routes + /p /m /pray /visit /card/[slug] with live ids → 200; bad ids → 404; typecheck + console clean; /free tap-to-pray verified end-to-end with the new explosion.

2026-08-15 — donate prompt made prominent

  • DonateLink (bottom of /m request pages) upgraded from the quiet gray strip to a tinted brand-bordered card: filled heart badge · "Give to keep Acts free" bold + "every gift keeps prayers free for everyone" · "Give →". Still quieter than the pray/app CTAs; recolors per skin. Verified on /m (TikTok skin, mobile).

2026-08-15 — "Get link" share row under every hub screen

  • components/ShareVersion.tsx sits under EVERY phone embed (baked into PhoneEmbed): Get link (clipboard + execCommand fallback) · Text (sms:) · Email (mailto:) · native Share… where supported. The body reads "Try this — {version name}: {url}" — Steve sends it, the recipient opens it on their phone and it just works (guest-first web app).
  • Gotchas fixed while building: navigator/window checks in render caused an SSR 500 + hydration mismatch → origin + canShare resolve in useEffect (SSR falls back to the prod origin for the link bodies). New mail icon in the set (the typed /design meanings record caught it).

2026-08-15 — hub embeds fixed: one PhoneEmbed, true-width viewport

  • Bryan: "versions all have scaling issues with buttons and what not." Root cause: the 375px-wide iframes lose ~15px to the desktop scrollbar gutter INSIDE the frame, so the app laid out at ~360px — chips wrapped ("1,100 praying", "the vigil"), the map info strip collided, buttons crowded.
  • components/PhoneEmbed.tsx is now THE phone frame (411×852 inner viewport = iPhone width + gutter slack, scaled 0.73 to the 300px card): replaced the five drifting copies (landing, triptych, amen, market, hybrid reviews). Verified: chips single-line, map strip fits, all three Triptych panels clean.

2026-08-15 — TRIPTYCH tab + Expo named in next steps

  • /triptych-review — "Triptych" leads the Versions nav group: the MAIN app's three skins side by side like altarpiece panels (Classic · Apple · TikTok), each a live phone embed of /app?skin=… with a one-line character note. The name = the clever word for "one scene, three panels".
  • Landing next-steps step ① now names Expo as the wrapper; step ② Astro clarified as the builder the original prototype was made in.

2026-08-15 — hub: Steve's landing page + the Design system page

  • "/" is now WHERE STEVE LANDS (Users moved to /users): left column = the product in the simplest terms (Ask/Act/See it/Keep going); right column = the latest build LIVE in a phone frame (iframe of /app) with "Open the latest build →" + the share URL under it. Below: Latest build (renders STATUS.md's newest section via lib/md.ts + "Full build log →"), Tech specs (stack, data, hosting, skins, guests-first, what's mocked), and Next steps (① add the native wrapper ② get into Astro ③ TestFlight).
  • /design — the Design system page, first section ICONS: all 37 glyphs from components/Icon.tsx, each on its own card (SVG · name · intended meaning), wrapping rows; MEANING is a typed Record<IconName,string> so a new icon without a meaning fails the build. TabBar: + Home, Users → /users, + Design system.

2026-08-15 — hub cleanup: Versions group + the Docs reader

  • TabBar regrouped (Bryan): "Versions" section (Amen · Market · Hybrid · Widgets) FIRST, a pipe divider, then Users/Paths/Lists/Images/Saints/ Gamification/Docs/Admin.
  • /docs — one readable content model for every document: left rail groups "Bryan's docs" (Phase 1.2 lists · prototype estimate · audit framework) and "the build's docs" (STATUS · DESIGN · SCHEMA · IMAGE-GUIDE); ?doc=key picks, whitelisted server-side. Markdown renders via lib/md.ts — a tiny renderer (headings/lists/bold/italics/code/links/blockquote/hr; buffers wrapped bullets so bold spanning lines still formats; .doc-prose styles in globals). All 7 docs verified rendering with zero raw markdown leaking.

2026-08-15 — ICON design pass (Bryan: same type of icons, clear to all ages)

  • components/Icon.tsx is THE icon system: ~35 hand-tuned stroke glyphs (24px grid, stroke 2, round caps, currentColor → recolors per skin). Platform emoji are GONE from UI chrome — every button, nav, chip, tile and menu row draws from this one set. Add icons there, never inline.
  • pray = Bryan's praying hands (the Noun Project logo mark, hands only, filled) — after the stroke attempt "did not read as hands". Used everywhere: Offer Prayer CTAs, praying counts, navs, map markers.
  • Data now carries icon names, not emoji: OFFER_ACTS/PLACE_ACTS/MILESTONES (scoring), MOMENTS, PRAYER_MODES/ROSARY_MYSTERIES (prayers), BADGE_ART, ACT_IMAGES, SKIN_VOCAB.rankIcon. BigCta icon="candle" resolves names (falls back to raw text). Leaflet markers = inline SVG strings (MARK_SVG in ActsMap; sized via .acts-map-emoji svg in globals).
  • Swept: feed (cards/header/map/menu/home-nav), Ask wizard, pray flow + act hub, candle/rosary/visit loops, moments, donate, parish, you/map/cards pages, AMEN (nav/rail/vigil/ask), MARKET (doors/feed/nav/stalls), badges (medal seal + editor). Kept: content emoji (iOS mock home screen), trailing ✨ in reward copy.
  • MARKET wording: "market/marketplace" was the CONCEPT name, not app copy (Bryan; he likes "Home") — tagline now "give help · receive help", nav tab "Home", "Post my offer", "Back home", "How it works". Routes + Steve tab keep the name. Hamburger added to the market chrome (MarketHome header + MarketTopBar → MenuSheet with an ink prop for non-feed scopes); HYBRID inherits it.
  • Verified in-browser (mobile, all three skins + amen + market + hybrid): feed, menu sheet, prayer choice, act hub, check-in loop. Console clean.

2026-08-14 — confetti when you win points

  • components/Confetti.tsx — zero-dep CSS confetti burst (32 pieces, token colors so it recolors per skin — violet on TikTok, gold/vermillion on Acts). Mounting it = the trigger; it rains once (~1.3–2.2s per piece) and fades. Keyframes + prefers-reduced-motion (hidden) live in globals.css (.confetti-piece, confetti-fall).
  • Fires on every points win: PrayFlow act hub (prayer offered) + Offering sent · VisitFlow act logged + COMPLETE at place · MomentJoin joined · AmenVigil joined · AmenApp card Amen. Verified in-browser on /moment/evening (mobile, TikTok skin) — burst plays over the joined screen, console clean.

2026-08-13 — the ACT LOOPS session (Bryan's spec, all verified in-browser)

  • PRAY flow rebuilt as a loop (components/PrayFlow.tsx): offer → encouragement → choose your prayer (💗 Heartfelt · ✝️ Lord's Prayer · 🕊️ Saint's Prayer — lib/prayers.ts) → full-screen prayer mode per choice → ACT HUB: add candle/rosary/fasting/novena, each pays via addDevotionAct (repeatable, running total, ✓ done-states) → Complete → share screen; first share claims the new invite milestone (+100) (MILESTONES.invite).
  • Process flows: CandleFlow.tsx (prepare → tap-to-light CSS candle .candle-* in globals.css, flicker + glow keyframes, optional dedication → comment) · RosaryFlow.tsx (mystery set by weekday from lib/prayers.ts → 5 decade screens w/ .bead progress). Both reused inside the location loop.
  • LOCATION LOOP (components/VisitFlow.tsx rebuilt): plan screen ("You've chosen an act at {place}" + 🔔 mock arrival reminder; ?here=1 skips) → map + "Arrived at {place}" strip → Check in (+25, checkInAtPlace) → Actions at {place} (PLACE_ACTS in scoring.ts: candle 50 · prayer 25 · rosary 150 · Mass 200 · adoration 250 · tend 100) — each act has its OWN screen (candle/rosary = full flows; pray/Mass/adoration/tend = per-act guidance) → "Make it real" log (photo = 2× base + 1,000, logPlaceAct) → share moment → next action loop → COMPLETE at {place} (completeAtPlace +20 + story card). /m entry: "I'm here" (→?here=1) + "Going later? reminder" link.
  • Gathering wall on /m (Bryan: each request is where acts+comments group up): "N acts completed • M comments" strip + all proof photos/notes + comments in chrono order (getMissionWall in lib/data.ts; Comments composeOnly keeps the input, wall shows the list).
  • Saint-card SHOP /app/deck: "You have N cards", buy any card with 500 pts or $0.99 (mock) — buySaintCard (points path checks + deducts via awardPoints negative; owned = card:<slug> counters; cash = mock, no charge). Entry: /card/[slug] "You have N — get another →" + /app/you deck link. BuyCardButtons.tsx (two-tap confirm on $0.99).
  • Scoring canon doc → docs/PHASE-1.2-LISTS.md (Bryan's full Phase 1.2 lists paste) + memory scoring-canon.md. lib/recognition.ts gained generic awardPoints(actor, pts, counters) (badges included; offer acts now use it).
  • Feed nav tightened: filter row = All/Parish/World/Live short labels, justify-between, snug .feed-scope .feed-filter — fits 375px, no side-scroll.
  • CITY_COORDS + pilgrimage cities (Vatican City, Lourdes, Fátima, Mexico City, Knock, Assisi) so seeded place missions map correctly.
  • Verified end-to-end on the dev server (mobile 375px + desktop): pray loop paid +210 (10+50+150) + 100 invite; location loop +170 (25+125+20); card bought with points deducted 500. ShareRow extracted (components/ShareRow.tsx).
  • Menu everywhere: MenuSheet added to /app/you · parish · deck · donate · role doors (active states; menu "Saint cards" now → /app/deck).
  • /saints curation at review size: each card renders LARGE (aspect 3/4, 3-up grid) with name overlay; the image itself is the upload target ("↑ Click to upload correct art") + remove-upload; Bryan/Steve drop the real card art there (or public/saints/<slug>.jpg still wins by convention).
  • Feed + visit restructure (Bryan's batch): ① visit actions list shows 3 + "See all N acts" → bottom-sheet MODAL (requests stay in view); requested cards now PROMINENT (.card, serif name, ⛪ parish tag, 🙏 praying count + "asks: {act}", gradient "Serve them →"), ordered place → parish → most popular. ② Feed: "Do you need a prayer?" small box at top → seeker; parish connect = prominent CARD below the map (connected: name + Edit + "Open your parish room" BigCta; else "Connect with your local congregation" + Find-your-parish BigCta); "Going on a pilgrimage?" card → offer wizard. FeedHeader's old parish line removed. ③ AskWizard: "Where should it happen? 🌍 Anywhere / 📍 At a location" chooser (place field only when at-location; anywhere clears place_label).
  • Location loop = a true multi-person session: the header (📍 place + accumulated points chip) is STICKY over the scrolling acts; selecting from the "Requested while you're here" feed no longer navigates — it switches the ACTIVE person in place (scrolls up under the pinned location, actions title reads "Serving {who}", per-person act done-keys, log targets their mission). One check-in, many people served, one COMPLETE.
  • Medals in ALL versions + requested-acts feed at locations: the devotional medal set now renders on /amen/you (Honors, 3-up) and /market/you (Honors, both-sides page) with real earned state + uploaded-art overrides — every app version shares one badge system. The location loop's actions screen now carries "Requested while you're here 📥": up to 6 asked acts (person · asked act · text · +25 chip) so a helper picks up new needs without leaving the church; taps jump into that person's loop at the place.
  • Badges v2 — the SAINTS aesthetic (Bryan: v1 read as 90s clipart): medals are now devotional photographs in double GOLD bezels on parchment (--holy-gold/--holy-parchment), sepia + candlelight vignette, glyph seal at the foot, display-italic serif names, ✦ gold captions — the holy-card language worn as a medal. Default photo per badge from photoCategory in lib/badges.ts; the medal itself is the click-to-upload target on /gamification (hover "↑ upload art", reset to default).
  • Badge art is UPLOADABLE: each medal on /gamification has "↑ art" (upload → uploadImage → saveBadgeArt; latest-wins badge_art rows in the activity log — the saint-card pattern) + "reset" back to the glyph. Uploaded art renders clipped inside the medal disc (rosette frame stays, so the set keeps reading as a family); /app/you honors overrides via getBadgeArtOverrides. Verified upload→render→reset live.
  • Badge MEDALLIONS (components/BadgeMedal.tsx + lib/badges.ts + --medal-* palette): the 11 badges are now a cohesive SVG medallion set — scalloped rosette seal, jewel tone + glyph each, metal ring, glass shine, ribbon tails; earned = color, locked = ghosted 🔒. Front and center at the TOP of /gamification ("The badge set") and on /app/you (3-up medal grid, real earned state). Old text-row badge lists removed.
  • Logo trial: PrayerMark (Bryan's praying-hands SVG, Noun Project — license before production) swapped into the Wordmark lockup, currentColor + breathing glow; one-line revert to FlameMark.
  • 2026-08-14 sweep: ① Feed polish — FeedCard = ONE card (who + intention + CTA inside; feed-list hairlines → space-y-5 wherever FeedCard renders), crosshair locate icon (not a paper airplane), .btn-big refined (centered icon+text group, layered shadows + inset highlight, tighter metrics so "Offer Prayer" never wraps beside the 💬 tile). ② HYBRID (/hybrid + hub tab): THE MARKET's exact components re-skinned modern via .hybrid-scope (--mkt-* remap: cool paper, blue need / green help, sans voice) — proof the concept re-skins without a rewrite. ③ Art direction panels on Amen/Market/Hybrid review tabs: upload per category (hands/candle/church/shrine/pilgrimage/portrait) → app_image; uploaded pools OVERRIDE stock Unsplash on concept surfaces via getPhotoPools+pickFromPool (verified end-to-end: uploaded church art appeared on /hybrid need cards; hide = setImageHidden). Saints tab already had its own upload; /images = the general gallery.
  • THE MARKET is a WHOLE app: /market (sticky collapsing doors, one zipped need/offer feed, four-stall nav Market·Ask·Offer·You) · /market/ask (seeker stall → real createMission → share) · /market/offer (the signature stall: place pills from the formal catalog + act chips compose the sentence "I'm headed to [PLACE] — who wants [ACT]?" → real createOffer; verified live end-to-end) · /market/you (both sides of you: given=green Grace/level/cards, asked=vermillion your needs gathering, via getMyAsks). Shell = components/MarketShell.tsx. Hub tab embeds all four live.
  • THE MARKET — the two-sided 30–45 concept (/market + hub tab "Market"); replaced DAYLIGHT same-day (Bryan: "I hate it… angry fruit salad… missing the 2-sided marketplace"). Discipline: paper + ink + photography + exactly TWO colors (needs = deep vermillion --mkt-need, helpers = deep green --mkt-help). Front page IS the market: "I need help / I can help" doors, pulse chips, Helpers on the road (real helper_offer rows, one-tap joinOfferAction join — verified count 1→2) above Open needs (photo cards → pray/visit loops), how-the-market-works both sides. Directions now: Classic · AMEN (young/vigil) · THE MARKET (30–45/two-sided).
  • AMEN is now a WHOLE app + hub tab "Amen" (/amen-review, live iframe embeds): /amen stack · /amen/ask (one-screen ask → real createMission → "watch the Amens gather" + share) · /amen/vigil (moments in the dialect, real joinMoment) · /amen/you (flame/Grace/level/card shelf, real data). Shared chrome components/AmenShell.tsx (AmenHeader vigil bar + AmenNav three verbs). Everything hits the real backend.
  • AMEN — the next-gen concept (/amen, linked from /paths): Bryan's "coolest new designer" brief — younger grammar + parent-proof reverence, instantly legible to every Christian. Full-bleed vertical prayer stack, ONE word as the CTA (Amen = the universal liturgical act, +10 via the real prayForMission), side rail 🕯️📿⤴ into the real act hub/invite, vigil bar (live praying count + moment chip), three-verb nav (Pray·Ask·You), candlelit --amen-* tokens (never pure black), design-notes screen at the stack's end (brand: keep lowercase acts + "say amen."; rename candidate if ever wanted: EMBER). A new face on the same body — all real data/actions.
  • LIVE MOMENTS — the synchronous act (Bryan: group live events, full in all feeds + a join path): lib/moments.ts = code-defined daily world moments (Morning Offering 8:00 · Noon Prayer 12:00 · Evening Peace 20:00, 20-min live windows, never empty). joinMoment action = tap points ×streak; joins are activity_event "moment_join" rows, count-per-day = the live counter (getMomentJoinCount). MomentCard (ticking countdown → LIVE pulse) pinned on /app, /app/cards, /app/parish; /moment/[key] = the gathering screen (countdown/LIVE, avatar stack, join +10, "you prayed together" + share). Pastor-created + parish-scoped moments and push reminders = next phase; the widget's top ladder rung now has something true to show.
  • Hub "Widgets" tab (/widgets): 9 iOS home-widget INCENTIVE mockups for Steve — streak flame · pray-at-noon · Today in the Church (the daily feast/season content lives HERE now, not the feed) · Grace & level · next saint card · needs near you · novena companion · answer-one-ask · invite. WidgetKit sizes (158² / 338×158), candlelit via .feed-scope tokens; each with "what it pays / when it fires" notes. Real widget = native Swift later. /widgets/add = the iOS add-a-widget PROCESS in 5 phone-frame steps (touch & hold → + → gallery → Add Widget → on the home screen) + the metaphor note: widget = vigil candle, the +50 retention moment.
  • /app/donate rebuilt in the feed dialect (components/DonateFlow.tsx): serif hero, amount pills → Give BigCta, sponsor-a-pilgrim pledge cards (10¢/act · $1/pilgrimage-day), thank-you state. Still a mockup — no charge.

2026-08-12 sweep — old screens → new design (DONE)

  • Ask wizard rebuilt (components/AskWizard.tsx, feed-scoped at /app/seeker): big Prayer intention card + Candle/Church/Cross/Rosary grid (Cross/Rosary map to acts over the prayer image — DB enum unchanged), NAME field w/ floating label (.field-float in globals.css; resting label plays the placeholder), relationship pills, progress bar, BigCta nav, PrayFlow-style celebration.
  • /m/[missionId] = the feed card opened: avatar-ring who-row, serif intention card, OfferPrayerCta (new client comp — BigCta → /pray/[id] with skin vocab) + BigCta "Get the app". One-tap PrayButton replaced by the PRAY flow.
  • /app/you + /app/parish + /manage feed-scoped; parish lists now render FeedCard (not MissionCard); saint-card gold hex → var(--color-brand).
  • /paths: feed hero + Cards/Immersive as 2-up thumbs. Demo content: scripts/seed-demo-missions.mjs (idempotent; Sarah's Mother 24🙏 · parish storm candle 12 · world peace 57). capture-previews.py sets acts_city/acts_parish cookies → captures aren't zero-state. All 9 re-captured.
  • View toggle cleanup: /app/cards feed-scoped (FeedCard + missionCopy, underlined sort tabs, parish chip from cookie); ImmersivePanel now uses OfferPrayerCta into the PRAY flow + t-hero (killed a text-[2rem]). lib/mission-copy.ts = display copy for every feed-card surface; zero counts read "Be the first to offer this ✨". .feed-list = hairline between feed items (feed/cards/parish). Social share row (Messages/WhatsApp/FB/X/Copy) on the PRAY "sent" screen.
  • Hamburger menu (components/MenuSheet.tsx): the Figma ≡ on feed/cards/map/immersive; right-side sheet with all destinations; HomeViewSwitch deleted. Overlay zIndex 1200 (beats Leaflet panes).
  • Flame logo everywhere: LogoMark → FlameMark (currentColor); ⚠️ app/icon.svg SHADOWS public/icon.svg at /icon.svg (Next metadata convention) — both now carry the flame-on-brown app icon.
  • /paths funnel strip: "The path in — tap any step" — 6 numbered, clickable step thumbs per skin (capture-previews.py shoots them; mission id resolved from the live feed).
  • ⚠️ Vercel gotchas: root tsconfig must exclude native/ (react-native types absent on Vercel → 3 failed builds); when a push doesn't trigger a deploy, npx vercel --prod --force from repo root.
  • TestFlight Option A wrapper BUILT (native/): Expo SDK 57 WebView shell around the live app — native share bridge, external-link handoff, flame icon + candlelit splash (scripts/gen-assets.py), lib/theme.ts token source, com.actsventures.acts, eas.json. expo-doctor 20/20. 🔴 NEXT: Bryan runs native/RUNBOOK-testflight.md (Apple Developer enroll → eas login → build → submit → add Steve as internal tester). Web deploys need NO new build; Option B native screens replace the shell in the same TestFlight app later.
  • The loop closes both ways (gap sweep 1-4): ① Asker loop — /app/you "Your asks" (getMyAsks: counts + words back) + markAnswered action → status=completed + story card. ② Novena in OFFER_ACTS (+100, ACT-NVN ×9 → Novena Companion) + "Pray at noon" join-at-time card in parish room. ③ scripts/seed-local-parishes.mjs — 4 parishes + 5 local place rows, missions back-linked by label. ④ ~~ChurchDayCard/liturgical~~ REMOVED 2026-08-13 (Bryan: not asked for, didn't land) — restore from git if a feast-day surface ever comes back.
  • Ask = the Figma design (acts playground → "Prayer Request Designs" frames, node 18:1280 + Make v64): header w/ brand mark + ACTS·SEEKER + progress dots, left serif title, glowing PRAYER card ("Select an intention to ask for"), 2×2 devotion grid (emoji-beside-label), ✍️ inline-icon name field, short pills (Mom/Dad/BFF/Community), ⛪ parish toggle (sets audience_layer=parish; Self-only → me), sticky Next → bar.
  • Photo avatars: components/Avatar.tsx + portrait category in lib/photos.ts — every profile circle (feed header, feed cards, /m, parish stacks) is a deterministic portrait photo in the ring color; same seed (name) → same face everywhere.
  • Place-based act path: /visit/[missionId] (components/VisitFlow.tsx) — "We see you're at {place} — check off the visit?" → add photo (+1,000) + word back → completeMission scores it + story card. Entry: "📍 I'm here" on /m's Where card + /app/map rows. GPS itself is native-app phase; web starts the flow from the place's screens.
  • Final port: OfferWizard rebuilt like AskWizard (feed-scope, progress bar, BigCta, serif celebration; WizardShell now unused); /app/map, supporter + advocate doors feed-scoped (advocate cards use missionCopy; StoryCard FB hex → btn-pray). The whole /app/* surface is now the new design — no Phase-1 screens left.

2026-08-12 late session — design system · PRAY flow · real map

  • Design system LOCKED: scripts/guard.mjs precommit ratchet (no NEW text-[Npx], tracking-[…], raw hex; --audit = 107 hex baseline to burn down; escape: git commit --no-verify). Contract merged into CLAUDE.md. ONE button system: .btn 48pt / .btn-big 72pt (title+reward subline) / .btn-icon 48×48, icons 22–24px, pills 44pt. Feed geometry now matches the Figma guides (points t-title, map 220pt, 40pt item gaps, Classic = underlined text tabs via .feed-filter).
  • REAL MAP: components/ActsMap.tsx — Leaflet + free CARTO tiles, NO token ($0; swap TILE_URL + token for Mapbox later). Themed per skin (Classic = dark_all + sepia acts-map-candlelit, Apple = light_all, Night = dark_all). Emoji divIcon markers; CITY_COORDS for the 4 location-step cities; fadeAnimation: false REQUIRED (tiles stall at opacity 0 under React double-mount). Feed card 220pt static; /app/map interactive 420pt.
  • PRAY flow (Bryan's Figma guides): FeedCard CTA → /pray/[id] → components/PrayFlow.tsx state machine: offer summary + add an act (candle +50 / fasting +100 / rosary +150, OFFER_ACTS → badge counters via awardOfferAct) → encouragement note (suggestion pills → comment row) → choose saint (deck cards, lib/saint.ts now has prayer text) → prayer mode (full-screen saint card) → completeOffer action (tap points + act bonus + comment + saint in activity log) → offering sent → share → return to feed. /p/[id] invite landing stays one-tap (funnel top).
  • BigCta = THE button (cf1f497): LARGE icon left, two stacked rows right (title / reward), 64pt. Never hand-roll a big CTA — use the component.

🔴 NEXT SESSION — new-design sweep (Bryan: "all the Ask etc screens are old")

New design (feed-scope + BigCta) covers ONLY: /app feed · /pray/[id] · /p/[id] · /start/* · /get. Everything else is still the old Phase-1 look. Sweep order:

  1. Ask wizard (/app/seeker) → Bryan's Apple guide: big intention card (Prayer primary) + Candle/Church/Cross/Rosary grid, NAME field w/ floating label, relationship pills, progress bar. Feed-scope it.
  2. Act detail /m/[id] → feed-card look + BigCta.
  3. /app/you + parish room/manage → feed-scope + BigCta.
  4. /paths thumbnails (Bryan asked, unfinished): seed 2-3 rich demo missions ("Sarah's Mother — healing…", parish storms candle) via service-role script, set acts_city/acts_parish cookies in scripts/capture-previews.py so captures aren't zero-state, layout = feed hero + cards/immersive as 2-up thumbs, then re-capture.

Where it lives

  • Live: https://acts-ten.vercel.app · Deploy = git push origin main (Vercel auto-deploys ~1–2 min).
  • Repo: github.com/AutoMaticDelight/acts · Supabase project in .env.local (gitignored; also set in Vercel).
  • Local dev: npm run dev (or preview server acts-dev on port 3200). Never needs Docker.
  • Verify a deploy: poll a route/marker, e.g. curl -s -o /dev/null -w "%{http_code}" https://acts-ten.vercel.app/<route>.

Stack

Next.js 16 (App Router) · React 19 · TypeScript · Tailwind v4 · Supabase (Postgres + Storage + RLS).

The product path (Bryan's framing — important)

Web prototype = top of funnel: a shared web link opens on the phone, you pray in one tap, *then* it invites you to download the native app (built separately) and add the widget. The native app is its own phase (Expo / React Native + EAS → TestFlight → App Store). The web work is the design + backend spec the native build reuses. See Expo notes in chat / plan below.

The path (2026-08-12) — every step gamified

Invite → member funnel, in order (Bryan's spec, matched to the Figma feed): share link → /p/[id] stripped one-tap pray → /get?flow=1 app (+50) → widget (+50) → /start/signup (+100) → /start/parish (+150) → /start/location (+50) → /app feed.

  • Milestones: lib/scoring.ts MILESTONES + awardMilestone (idempotent — claimed state = recognition_score.category_counts["milestone:<key>"], no schema change). Actions: claimMilestone/setLocation/connectParish (city/parish = cookies acts_city/acts_parish). Profile /app/you shows the Journey ledger + parish card.
  • /app = the Figma feed (view one): header (avatar · points · rank chip · parish teaser · ✨Ask) + dotted map card (city chip, locate → /start/location) + filter pills (All/My Parish/World Events/Live) + FeedCards with one big btn-big btn-pray CTA showing the reward on its face. Older views kept: /app/cards (grid) + /app/immersive; switcher = feed/cards/immersive (map absorbed into feed).
  • .feed-scope (globals.css) remaps tokens per skin like .admin-scope: acts=Classic candlelit gold · modern=Apple light blue · night=TikTok black/violet. Per-skin vocab SKIN_VOCAB (Grace/Devout 🕊️ · pts/MVP 🏆 · XP/Warrior 🛡️) via useSkinVocab() from SkinScope context. Figma source: "acts playground" file.
  • Invites now share /p/[id] (ShareInvite on /m); /m "Keep it with you" → /get?flow=1.
  • Hub /paths — the three design systems side by side, all three views as large images (real captures in public/previews/, re-capture with python3 scripts/capture-previews.py against the dev server; ?shot=1 hides the skin switcher) + a Send-link per path (opens the funnel in that skin).
  • Parish manager /app/parish/manage — pastor side of the parish room: live stats, broadcast composer (postBroadcast → broadcast table, need pills not dropdowns), QR + Send-link parishioner invite. Linked from /app/parish ("Manage").

Built & live (routes)

  • App (skinnable, /app/*): /app Home (Cards) · /app/immersive (18–25 full-screen) · /app/map (stub+list) · /app/[role] (seeker=AskWizard, doer feed, supporter=story cards, advocate=QR broadcast) · /app/offer (OfferWizard → significant places) · /app/parish (room + Join) · /app/donate (mockup) · /app/you (guest→member ladder).
  • Shared: /m/[id] Act Detail (photo, geographic+Directions, Share/Invite, completion loop trust-led, comments, subtle donate, ✦ prayer-card link) · /s/[slug] Story Card · /card/[id] Saint card · /get App Store listing mockup + widget mockup.
  • Control room (/): Users · Lists · Images (gathering guide) · Saints (approve/edit/upload) · Admin (moderation).

Gamification (2026-08-11)

  • Engine: lib/scoring.ts (bonuses, levels, badge rules, SAINT_CARD_EVERY=5) + lib/recognition.ts (service-role recorder: streaks, counters, auto-badges, 10-min anti-farm). Every pray/join/complete/photo-upload is scored via recordAct.
  • Saint cards = the REWARD, not per-prayer artifacts. A collectible deck (lib/saint.ts SAINT_DECK), next card earned every 5 completions, collection on /app/you, card page /card/[slug]. Steve curates the deck on /saints.
  • Control room /gamification: levels, streaks/bonuses, saint-card rule + deck, badges, editable per-item points list (updates act.default_points live), top helpers.
  • Admin = Apple style (.admin-scope in globals.css): SF system font, #f5f5f7, white cards, Apple blue/green/red. The app keeps the Acts brand.
  • LogoMark (components/LogoMark.tsx) renders /public/icon.svg — swap that one file to change the logo everywhere. Feed is THE center — keep Home chrome slim.

Key decisions (don't relitigate)

  • Ask = ONE act (OR selector); actors add any. The seeker's chosen act is a featured suggestion, not a constraint — the PRAY act hub highlights it ("what {who} asked for ✨", sorts first via offerActForActCode) and any helper can stack any devotion. At a location the loop is open too: any act at the place, then the person-loop — "while you're at {place}" offers other people to serve from the same spot (same place → same city → any, each with the +25 check-in chip), on the logged and complete screens.
  • Brand: Cormorant Garamond (italic for feeling) + Instrument Sans + #ec612b vermillion. Logo = vermillion candle disc (app/icon.svg, favicon/PWA). Ring colors: me/vermillion · another/terracotta · parish/green · world/blue.
  • 8pt grid + named type scale (.t-hero/title/headline/body/sub/caption). Pills, not dropdowns (PillSelect/.pill).
  • Completion: ~~four candidates side-by-side on /m~~ RETIRED 2026-08-13 — the act loops (pray hub · location loop) ARE completion now; trust stays the philosophy (no tracking), CompletionMethods component kept but unmounted.
  • Photography: lib/photos.ts = curated Unsplash interim (License-free, no key), deterministic per item. Swap for owned photos later; shot list in docs/IMAGE-GUIDE.md.
  • Saint cards: patron saint matched to the intention (lib/saint.ts); Steve curates on /saints; curation persists in the activity_event log (no schema change) — public /card/[id] renders curated data.
  • Guest→member: prototype only — a name, no credentials (never collect passwords / create real accounts).
  • Design contract: DESIGN.md (rules + full screen spec). Never regress to the generic AI look.

Data

19 tables, all seeded. Seed utilities (run cd ~/acts && node --env-file=.env.local scripts/<x>.mjs): seed-all.mjs (audit + backfill all tables) · seed-offers.mjs · seed-place-needs.mjs. Migrations in supabase/. Note: proof counting is paginated past the 1000-row cap (lib/data.ts proofCounts).

Next / not done

  • Wire the /get "download the app" invites into the "Keep it with you" sections (Act Detail, /m, wizard posted states).
  • Advocate broadcast wizard (pick a need → QR + link).
  • Expo native app — scaffold create-expo-app + Expo Router + NativeWind + supabase-js; port screens reusing the Supabase backend + taxonomy + tokens. Widget = native Swift/WidgetKit (sized L). EAS build/submit needs Bryan's Apple Developer account (his step; I supply the eas commands).
  • Out of scope (mockups): real payments, real SMS, real widget install, real App Store submission.

2026-09-05 (late)

  • Profile rebuilt around three tabs — My Actions · My Requests · Sent to me — pinned above the first fold; the ground below them shifts to T.paperProfile (the feed's lighter-ground move, in vermillion). Points block compacted (48-pt number, one line for next level + streak). Section labels now SECTION_LABEL (14/800 ink) everywhere.
  • /api/native/me returns sentToMe: every proof + active comment on this person's asks by someone else, newest first, with author {name, avatar}, act, forName. Rows use the wall's language (shared components/Face.tsx).
  • Gotcha: a child under stickyHeaderIndices is laid out as a column by the sticky wrapper — put the row one View in.
  • (219007a) Profile: white top (identity · places · points · tabs), tinted ground T.paperProfile with a white card per tab, and a pinned compact points+tabs bar once the identity scrolls off (absolute over the list, top: insets.top; ground minHeight = viewport so it can always engage). Wall: white top block, rows on T.paperLight.

2026-09-05 (evening) — brutalist pass, LOCAL ONLY (not pushed, not OTA'd)

  • Icons v3 in the v1 logo's style behind ICON_SET; v1/v2 tagged. All icons black, flame-only candle, prayed hands green. Tab bar from the set.
  • Brand mark = v1 mark, ink + vermillion heart (BRAND = "brut"). Logo lab at /logo-lab (dev).
  • Doors: stacked mark · tag · body · arrow; ASK black / GIVE white-outlined with red heart. "What does this mean?" explainer: solid doors, prompts, × close, folds on leave.
  • Square buttons (radiusBtn 8). Profile tabs + pinned bar, white tops, distinct ground (shipped earlier today).
  • SVG exchange: scripts/svg.py, /svg skill, librsvg + potrace installed.
  • Gotcha: a flex row that was stacked (flexDirection change) keeps its widths — remount with a key.
  • TO SHIP: git push (Vercel) + eas update --platform ios; then tag icons-v3, tracker rows.

2026-09-06 — SHIPPED (OTA "brutalist pass", commit e17b7b0)

  • Three skins in one build via lib/skin.ts (sync read with expo-file-system File.textSync; switch reloads with expo's reloadAppAsync): brutalist (default), apple (red → #0970e6, radius 12), classic (v1 look). Switcher at the bottom of My Profile.
  • Everything from 09-05 evening + 09-06 morning: icons v3 (plain pin), split mark on doors/tabs, doors stacked with air, count lockup on one baseline, profile without the card, notification icon = BrandMark, no emoji on step screens, neutral grounds.
  • Steve note: docs/client/2026-09-06-steve-update.md.
  • Next build (8): expo-notifications for real push, new home-screen icon asset, licensed portraits, moderation.

2026-09-08 — FIND YOUR OWN CHURCH (Brian)

  • Brian Kelly (Brooklyn, tester #2) opened as the persona "Samuel Wright" — the manual rename was missed before the invite. Renamed after the fact; scripts/name-tester.mjs lists the roster with the city each phone last opened from, so the right profile is identifiable. Rename BEFORE inviting.
  • The real find: he lives in Brooklyn and travels to his parish. The app had one place_* on the profile doing two jobs — "where I live" and "where I worship" — and parish_label was free text with no coordinates.
  • Root cause of "I can't find my church": the nearby query caps at out center tags 60, so the picker never showed more than 60 churches in any city, chosen arbitrarily (the cap applies before any distance sort — the same mechanism that kept dropping St. Patrick's).
  • church_catalog (new table, own migration): every church from OpenStreetMap, imported via osmium. 13,340 rows for NY/NJ/CT, ~2 MB. Deliberately NOT place — place stays the small curated set whose significance drives "leads the list", and two tables FK to place_id so it can never be truncated. The catalog is disposable and re-importable. Data © OpenStreetMap contributors, ODbL — licence review still open.
  • /api/native/churches: word-start matching (substring-anywhere made "pa" match "EpisCOPAl" and returned 556 rows), 3-character minimum ("st" returned 1,000 meaningless rows), St. ↔ Saint expansion ("st pat" shares no substring with "Saint Patrick's"), bbox then true distance. 113–266 ms measured.
  • Ordering is pure distance (Bryan): from Brooklyn "pat" puts the parish 1.3 mi away above St. Patrick's Cathedral at 5.8 mi. A famous church never overshadows the local one.
  • Parish screen: the search field now queries the catalog instead of filtering the 60 cached rows; the free-text "name it yourself" box is demoted to appear only after a search comes back empty.
  • Still open: national import (11.6 GB, pipeline proven); parish coordinates on the profile so a connected church stops depending on where the phone is standing; ODbL.

2026-09-08 (evening) — THE SERVER HAD NOT DEPLOYED SINCE SEPT 5

  • Every production deploy since 2026-09-05 21:28 UTC failed, silently. Found only because the church endpoint 404'd after pushing it. Prod was serving three-day-old server code — the Sept 6 ship never reached it. (The native app was unaffected: Expo pushes OTA, a separate channel from Vercel.)
  • Cause: design/icons-v1/*.tsx are React Native drafts, and tsconfig.json excluded native and expo but not design — so next build type-checked them, failed on Cannot find module 'react-native', and exited 1. The folder landed 2026-09-05 15:18 PDT; deploys have failed from that point on.
  • Fix: design added to tsconfig exclude.
  • How it stayed invisible: nothing watches deploy status. gh api repos/AutoMaticDelight/acts/deployments shows state per deployment — worth a check after every push until something automated does it.
  • Machine (migrated from the old laptop, user bryan → bryanholland): osmium, supabase CLI, libpq/psql, watchman installed; psql added to PATH in ~/.zshrc; stale /Users/bryan/acts paths in six scripts corrected. gh and eas are authenticated. Vercel CLI is signed into the wrong account (bryan-3472, personal) — vercel login with the team account is still needed for CLI deploys. Pushing to main is what deploys, so this is not blocking.
  • Catalog is national: 244,930 churches. Filter+export of the 11.3 GB US extract took 37 seconds; the download was the only slow part.

2026-09-09 — THE PARISH HAS A POINT ("you are 3.4 miles from this church")

  • The gap: parish_label was a name with no position. The app knew Brian goes to St. Patrick's but not where St. Patrick's is, so every distance was measured from wherever the phone stood, and nothing could say how far the parish was. Bryan's line for it: "You are 3.4 miles from this church."
  • Migration 20260909090000_profile_parish_coords.sql: parish_lat/lng (exact — a church is a public building; only the PERSON's place is coarse), parish_city, parish_ref (origin tag: a place_id, cat-<catalog id>, osm-…, or null for a typed name — text, never a FK; the catalog is disposable). Backfill matches existing labels to the curated place set first (Brian tapped that row), then the catalog by name + proximity to the person's place, with St./Saint and curly apostrophes folded ("St. Patrick's" ↔ "Saint Patrick’s"). Roster gains parish_miles.
  • Validated locally: postgresql@16 installed via brew (this Mac had none); all eight migrations replay clean from scratch into a scratch DB with a stub auth schema; fixture test — Brooklyn + "St. Patrick's Cathedral" picks New York over El Paso, a "St." label finds the catalog's "Saint" row, a typed parish stays null, roster reads 7.6 mi.
  • API: POST /api/native/parish takes lat/lng/placeId/city from a tapped row (freehand sends none) and echoes parishAt; /api/native/me returns parishAt {lat,lng,city} | null.
  • App: connectParish(label, at); the parish picker passes the row's point; lib/units.ts distanceKm + fromChurchLine(me, church) — the one sentence, on the connect confirmation (from the phone), the profile (place → parish, under the "Oakland · St. Patrick's" line), and the feed's congregation tab.
  • Not yet applied to the hosted DB — Supabase CLI has no login on this Mac. Order matters: migration first, THEN push (the /me select names the new columns; pushed early, every profile returns empty). Then eas update.
  • Pre-existing lint error in expo/app/parish.tsx (set-state-in-effect on the search debounce, from 09-08) — untouched.
  • Shipped 2026-09-09: migration applied via supabase db push (the church_catalog migration was recorded too — it had been pasted by hand and was missing from the remote history; every statement is idempotent). Backfill on prod: Brian → curated St. Patrick's (0.5 mi from his midtown place), Steve → Glen Ridge Congregational 2.3 mi, Leo → Chapel of Grace 9.5 mi. Pushed 1564b64, Vercel deploy 6335709268 success. EAS update group f5e4f4db-350f-4e9f-913f-46d8f3e63733 (iOS, runtime 1.0.1). All three distance surfaces verified in the simulator against production.
  • Client note: docs/client/2026-09-09-steve-brian-update.md (churches + geo).
  • .claude/launch.json gains an expo entry (Metro on 8081) for the simulator loop. A Metro from Sept 4 was still running in this tree and served the edits.
  • Live distance (Bryan: "his location is X miles from the church — it must update… if he is moving around"): expo/lib/useHere.ts — the phone, WATCHED while the profile or feed is up (watchPositionAsync, 200 m), the typed/remembered town only when location is denied; never prompts. Proven in the simulator with simctl location set: 4.2 → 0.3 → 2.3 mi on one screen with no tap. Gotcha: Expo Go on the sim had no location grant — the line froze until simctl privacy grant location-always host.exp.Exponent. Client note gained the "it moves with you" paragraph.

2026-09-10 — ANDROID, ONE INSTALL LINK, PHONE FILTER IN THE TRACKER

  • Android build — APK live at https://expo.dev/artifacts/eas/o-nicgLdXhVaAeu4rmNACW-zP-vteeXvibscmMPnVdc.apk (build cc8d8fbb, ~26 min on EAS): eas.json profile android-testers (internal distribution, APK, channel production — the same OTA branch as iPhone, so one eas update per platform reaches both). Keystore generated in the EAS cloud (no keytool here). First build cc8d8fbb…. App android.versionCode = 1, permissions deduped to location only (image-picker adds its own).
  • /join — one link for every phone. Server reads the user agent: iPhone → TestFlight button, Android → APK download, laptop → QR to the same page plus both buttons, then the steps for that phone. /join?r=brian tags the person. Every open is install_view, every button goes through /join/go?to=testflight|apk which writes install_tap and redirects — anonymous rows (null profile) in activity_event, read back through the install_funnel view. Links live in lib/install-links.ts (edited by commit): ios = TestFlight public link (null until enabled in App Store Connect → the button opens the TestFlight app via itms-beta://), android = the APK artifact URL.
  • Which phone: profiles.platform (ios|android|web, migration 20260910090000_tester_platform.sql, backfilled ios for everyone so far). The app sends p=Platform.OS on /api/native/me; the route also infers it from the fetch user agent (okhttp = Android, CFNetwork/Darwin = iOS) for builds that predate p. Session events carry platform. tester_roster and tester_activity gain platform.
  • /track: chip row All · iPhone N · Android N (filter = ?os=, so a view is a URL), Phone column, parish distance beside the parish, and an Install link block — opened / iPhone taps / Android taps / from a laptop, with the last taps by ref. Verified in the browser (mobile UA → Android path; desktop → QR + both; tap → ledger → tracker).
  • Surfaced by the tracker: Brian changed his parish to Saint Vincent Ferrer (0.2 mi from his place) and has posted two offers — he has acted.
  • Question for Steve filed (remove "Start over" + the skin switcher?).
  • Gotcha: eas build:view must run inside expo/ — from the repo root it prints nothing and the JSON parse fails.
  • The list workflow (Bryan: Steve will hand over emails): scripts/invite-list.mjs <list.csv> (name, email, phone) → each person's /join?r=<slug> link, the Apple IDs to paste into TestFlight, and one email draft per person in docs/client/invites/ (gitignored). The slug is how the tracker ties an install-page open to the profile that appears minutes later. Rename still happens after first open (name-tester.mjs).
  • Decision (Bryan): NO TestFlight public link. iPhone testers are added to Apple one at a time; INSTALL_LINKS.ios stays null on purpose.
  • /invite — the window (Bryan: "automate this into a window for me"). Behind the /track cookie; Invite tab beside Track. Paste Steve's list (any of "name, email, phone" / "Name <email>" / bare email; tabs and semicolons fine) → tester_invite rows (migration 20260910120000, service-role only, upsert on slug so re-pasting is harmless). Per row, in order: 1 · Apple — with an App Store Connect key (ASC_ISSUER_ID, ASC_KEY_ID, ASC_PRIVATE_KEY; lib/asc.ts, ES256 JWT via node:crypto, no SDK; 409 → attach the existing tester to the group) it is one button and Apple sends the invite; without the key, a link to the TestFlight page and an "I added them ✓" tick. Android rows say "not needed". 2 · Email — a mailto: with the draft (lib/invite-email.ts, same text as the script) and their /join?r=slug; the click stamps emailed_at. Status — from the ledger: opened the link (on which phone), tapped install, then in the app with acts once the roster name matches the list name (rename still by name-tester.mjs). Verified locally end to end; test rows removed. Key not yet set — Bryan generates it in App Store Connect and adds the three values to Vercel.

2026-09-15 — THE FEWEST STEPS (Steve's testers, all iPhone)

  • Invite rewritten (lib/invite-email.ts, mirrored in scripts/invite-list.mjs): two steps to get in (accept Apple's invite; tap the link → TestFlight → Acts → Install) · three things once in (connect church, post one request, pray on one) · reply with anything that felt off. /join says the same words. Doc: docs/client/2026-09-15-tester-instructions.md. Verified on the simulator's Safari. Steve's list still to be pasted into /invite.

2026-09-16 — THE APPLE QUEUE on /invite

  • Bryan: "an invite list in a page so it's easy to open Apple Connect and add each one separately." App Store Connect's add-tester form wants first name, last name, email, typed one person at a time. /invite now opens with an Apple queue (AppleQueue.tsx): ONE person at a time, tap a field to copy it (First · Last · Email), "Open App Store Connect" (lands on the group when ASC_BETA_GROUP_ID is set), "Added, next →" ticks the row (existing addToApple manual path) and shows the next; "Skip for now" cycles. Queue = iPhone/unknown rows with an email and no apple_added_at; hidden once the ASC key makes the step automatic. Not yet verified in the browser here (no service env in this container); lint + guard clean.
  • The email, visible (Bryan: "the invite page is missing the copyable script for the email I will write and send"): Copy-draft copied blind. EmailScripts.tsx lists every person with the whole draft in the open — To, Subject, body with their /join link — "Copy for Zoho" marks the row. First unsent person opens by default.
  • Two-pass tester script (Bryan): pass 1 with location on (church, post, pray, candle photo, comment, profile) · pass 2 as a stranger who says no (Start over → Settings → location Never → type the church → change location). Steps live once in lib/invite-email.ts (passOne/passTwo, phone-aware step 8), read by the email drafts on /invite and by /join; mirrored in scripts/invite-list.mjs. Caveat filed in the tester note: Start over makes a second profile that needs renaming.
  • The appended name reaches the app (Bryan: "I asked to append their names in the link so you would know"): iOS hands an installed app nothing about the link, so /api/native/me now names a FIRST-TOUCH profile from the most recent unclaimed install_tap ref (same phone kind, 45-min window, invite name not already on a profile) — claimFromTap, logged as tester_matched. Persona only when no tap matches. Wrong guess = two clicks on /track ("Who is this?" → pick the name; track/actions.ts). Tim + one John landed as personas before this; rename them on /track. Deep link (acts://…?r=) would be deterministic but the acts scheme entered app.json Sep 5, after the Sep 1 TestFlight build — needs a new native build, not an OTA. Parked.
  • Mac readiness check, one command (scripts/mac-check.sh, --fix walks the logins): node · gh · eas · vercel (team scope + repo link, NOT whoami, which always prints the personal username) · supabase (login + linked to qhkwamzmvlhexsuyrnqq, ref now in .env.local.example + SCHEMA.md) · iPhone 17 Pro sim · ~/Dropbox/ActsVentures · git. Ran it on the new laptop: 7/8 green — Vercel re-scoped to the team and vercel linked, Supabase logged in + linked. Only red: ActsVentures/ was absent from the Dropbox account entirely (not a sync gap — Otter has never exported there); folder created, Otter → Settings → Apps → Dropbox still needs pointing at it.

2026-09-18 — THE WELCOME ("I'm not sure what to do with this app")

  • First three testers in. Two acted; Sam opened it and asked what it was for — filed in docs/client/2026-09-18-tester-feedback.md with the note Bryan sent him.
  • The welcome (expo/app/welcome.tsx): four screens on the first open — the mark ("Someone near you needs a prayer"), ASK (prayer · candle · visit), DO (pray +10 · candle +50 · kind word · visit +25), COUNT (My Profile, your church). Paged, dots, Skip (L3) and one primary button; the brand marks as the art, white sheet on paper, ink outline — no photography, no dark screens. Flag acts-welcome-v1 in SecureStore (lib/welcome.ts): Home pushes it once when unseen; "Start over" clears it; My Profile → "How Acts works" reopens it. Full-screen modal over the tabs (_layout.tsx).
  • Verified: tsc --noEmit clean in expo/; browser render of all four screens caught screen 3 overflowing (fixed: compact art + per-page scroll). Shipped 2026-09-18: main 19e336c → OTA group ee0229cb, runtime 1.0.1, channel production, "Welcome screens on first open". Not yet eyeballed on a phone — Bryan to Start over on his own and look.
  • Trap removed: a tracked root app.json stub ({"expo": {}}) made eas update from ~/Acts create a stray project @automaticds-team/acts (950d6a3c). Stub deleted; the app is expo/ — run eas from there. The stray project is still on expo.dev, harmless, delete when convenient. The real project is acts-of-faith.
  • Seen, not touched: Home's focus effect ((tabs)/index.tsx) returns setExplain(false) BEFORE the "follow a changed location" block, which therefore never runs — since d9403db ("trial: give door white with an ink outline"). Bryan's 09-09 ask ("changed my location on the profile, the feed didn't move") is back. One-line move; left for a deliberate fix.

2026-09-18 (later) — THE NAME, SAID NOT GUESSED

  • Bryan: "the way you sold it to me was easy tracking and we would auto populate their name from the invite appended name but that is a mess now." True: iOS hands an installed app nothing about the link, so the tap-match is a guess that works one person at a time and failed when Steve's five installed the same afternoon.
  • Ask, don't guess. The welcome's last screen: WHAT SHOULD WE CALL YOU? — empty for a persona, prefilled for a guess or a said name, so most people tap "Let's go". POST /api/native/name ({name} typed → tester_named; {ref} from a deep link → tester_claimed, name from tester_invite). /api/native/me now returns nameSource: persona | guessed | said (persona = display_name equals personaFor(guestKey); said = a named/claimed/renamed event exists). My Profile shows "That's not my name" / "Not you?" under the name while it is not said → /welcome?page=name.
  • Deep link, for the next build: acts://?r=slug claims the name deterministically (DeepLinkClaim in expo/app/_layout.tsx; the join page offers "Already installed? Open Acts" on iPhone). Needs a native build — commands in native/RUNBOOK-testflight.md. Harmless until then.
  • The welcome flag is unseen for every existing tester, so Tim, John and Sam each meet the welcome once and say their name — no /track renames needed.
  • Verified: expo tsc + eslint clean; web eslint clean; web tsc has one pre-existing LayoutProps error (Next's generated types, needs a build). Not yet on a phone.