For the human reviewer. Sections are independent — work in any order. Mark each with PASS / FAIL / N/A and note the file:line where you confirmed it.
-
pnpm installsucceeds on a clean clone with the published lockfile -
pnpm run typecheckis green (note:useColors.tsTS2352 is a known pre-existing item) -
pnpm --filter @workspace/api-server run buildproducesartifacts/api-server/dist/index.mjs -
pnpm --filter @workspace/api-spec run codegenruns cleanly and does not produce uncommitted diffs -
pnpm --filter @workspace/db run pushsucceeds against a fresh Postgres - API serves
/api/healthz→200 - Mobile boots in Expo Go and reaches the welcome screen without console errors
- Production build args in
artifacts/api-server/.replit-artifact/artifact.tomlaresh -c "pnpm --filter @workspace/db run push && pnpm --filter @workspace/api-server run build" -
app.jsonhasios.infoPlistkeys forNSLocationWhenInUseUsageDescription,NSCameraUsageDescription,NSPhotoLibraryUsageDescription -
app.jsonhasandroid.permissions: [ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION]
-
Every state-changing route uses
requireAuth. The codebase mixes single-line (router.post("/x", requireAuth, handler)) and multiline (router.post(\n "/x",\n requireAuth,\n ...\n)) declarations, so any line-oriented pipeline (e.g.grep ... | grep -v requireAuth) leaks false positives on the multiline form. Use a single-regex PCRE pass that excludesrequireAuthinside the matched call:rg -UP -n 'router\.(post|patch|put|delete)\((?:(?!requireAuth)[\s\S])*?\)' \ artifacts/api-server/src/routes/Expected output (verified against the current code): exactly one match —
routes/storage.ts:57forPOST /storage/uploads/request-url. That route is intentionally gated by an inlinerequireAuthmiddleware inroutes/index.tsbeforestorageRoutermounts, so it is safe. Any additional matches are real authz gaps and must be investigated. (Note:/storage/objects/*is a GET route; it streams private user content and is gated by the same inline middleware inroutes/index.ts— verify by reading that file directly.) -
Public routes are limited to:
/api/healthz,/api/storage/public-objects/*filePath, and the production Clerk proxy at/api/__clerk/*. Everything else (including/api/storage/objects/*andPOST /api/storage/uploads/*) requires Clerk auth. -
app.tsrefuses to start in production with noALLOWED_ORIGINSand noREPLIT_DOMAINS -
CORS allowlist is enforced both at the CORS layer AND at Clerk's
authorizedParties -
Auth is bearer-token via
@clerk/expressclerkMiddleware; there is no Express session cookie store, so noSESSION_SECRETto manage despite the placeholder inartifacts/api-server/.env.example(unused at runtime — flag for cleanup) -
Tokens never appear in error responses or
req.logoutput
- Foreign keys cascade-delete from
usersto:profile_photos,presence_buckets,glimpses,glimpse_responses, blocks, reports, chat messages, connections (verify each table inlib/db/src/schema/index.ts) - Unique constraints exist on:
users.email, one response per(glimpseId, responderId), one photo perpublicUrl - Indexes support the production read paths: presence by
(bucket, time), glimpses by(bucket, eventTime)and(visibleAfter, status), expiry indexes for the 72-hour purge - No production migration depends on data backfill that isn't explicitly run
-
DELETE /api/meactually removes the row and all dependents — confirm in psql after a deletion
- DTOs returned to other users (Discover, Glimpse detail, Responses, Connections, Chat) NEVER include
lat/lngfor someone else - Times shown to other users are fuzzy labels, not exact
eventTime - Coordinates are bucketed (
floor(lat*100),floor(lng*100)) before any cross-user comparison - Presence rows are purged after 72h (verify the
expiresAtindex is acted on by a job or by query-time filter) - Glimpses become visible only after
visibleAfter(current code: 1-hour delay viaVISIBLE_DELAY_MSinroutes/glimpses.ts; reconcile with any product copy that still says 4 hours before App Review) -
lib/location-validationis the primary source of shared constants and helpers used by both client pre-flight and server enforcement. Known duplication:artifacts/api-server/src/routes/presence.tsre-declares its ownBUCKET_FACTOR = 100instead of importing from the shared package — flag as a P1 cleanup, not a blocker - Server enforcement of presence gate is authoritative — client pre-flight is advisory
- Create rejects payloads that fail Zod validation
- Create rejects when the author has no qualifying presence row
-
/discoveronly returns Glimpses where viewer has presence in the 3×3 neighborhood (NEIGHBOR_BUCKET_RADIUS = 1) of the Glimpse's bucket and within ±2 hours (PRESENCE_WINDOW_SECONDS = 7200) of the Glimpse'seventTime— seeroutes/glimpses.ts~line 309 -
/discoverexcludes Glimpses authored by the viewer -
/discoverexcludes Glimpses involving blocked users in either direction -
/discoverdoes not return precise coordinates or precise event times of other users' Glimpses -
/glimpses/meonly returns the caller's own Glimpses -
DELETE /glimpses/:idenforces author ownership - Cascade on delete clears responses, connections, chat messages tied to that Glimpse
- Responder cannot respond to their own Glimpse
- Responder cannot respond twice to the same Glimpse (unique constraint)
- Only the Glimpse author can confirm or decline a response
- Chat is locked until response status is
confirmedfor both sides - Chat list endpoint excludes connections involving blocked users
- Sending a message verifies the sender is a participant in that connection
- Block is two-way enforced — blocked user disappears from both users' Discover, Responses, Connections, Chat
- Existing chat messages from a blocked user are hidden in subsequent fetches
- Unblock restores visibility
-
POST /reportsrunsisInappropriateon thedetailtext before persisting - Reports persist with
reporterId, target, reason, optional detail, timestamp - Rate limits on block POST/DELETE prevent abuse
- Photo upload uses signed URL flow — server never proxies the bytes
- User cannot bind an
objectPaththey did not upload —POST /me/photosparses the user id out of the path and rejects mismatches (seeroutes/photos.ts~line 100) -
PUT /me/photosis reorder-only and does not accept new bind payloads - 6-photo cap is enforced server-side
- Reordering preserves the
positioninteger cleanly -
DELETE /me/photos/:idremoves the row and compacts positions. NOTE: current code does NOT delete the underlying storage object — orphan objects accumulate. Flag as a P1 cleanup, not a blocker - Public profile DTO does not leak email or other private fields
- Client calls
DELETE /api/mebeforesignOut - Server deletes Clerk user (404 treated as success)
- DB transaction cascades through every dependent table
- DB failure returns 500 (so client can retry) rather than silently succeeding
- Re-signing up with the same email is possible afterward
-
app.jsonhasios.bundleIdentifierandios.buildNumberset -
eas.jsonexists withproductionprofile reading from EAS Secrets -
EXPO_PUBLIC_TERMS_URLandEXPO_PUBLIC_PRIVACY_URLare set in EAS production secrets and resolve to live, public 200 pages -
EXPO_PUBLIC_DOMAINin EAS production points to the deployed*.replit.app, NOT the dev workspace domain -
EXPO_PUBLIC_CLERK_PUBLISHABLE_KEYin EAS production is apk_live_…from a separate Clerk production instance -
CLERK_SECRET_KEYin deployment secrets issk_live_…from the same prod Clerk instance - Production
DATABASE_URLis a separate database from dev - App Store Connect privacy questionnaire is complete and accurate
- Demo reviewer credentials documented in App Review notes
- App Review notes explain location use (presence-gate, anti-spam, coarse coords, never shared)
- Welcome screen shows tappable Terms + Privacy under the "by creating an account…" line
Walk through these on at least one physical iPhone. Note iOS version.
- Cold launch → welcome screen renders, fonts loaded, no flash of black
- Sign up → email verification → 18+ gate → mandatory photo → tabs
- Location permission denial path is graceful (no crash, clear message)
- Drop with valid presence succeeds; Drop without presence is blocked with a clear hint
- Place picker autocomplete returns results within ~1s
- "Use current location" works when permission is granted
- Discover renders cards; tapping opens detail
- Respond → switch account → confirm → both sides see chat unlocked
- Photo upload from camera and from library both succeed
- Reorder and "set main" persist after app restart
- Block → blocked user no longer appears anywhere
- Report → server returns 200, no PII echoed back
- Delete account → app returns to welcome → sign-up with same email works
- Tap Terms and Privacy on welcome and in Settings — both open in-browser, fallback Alert if URL is invalid
- Background the app for 5+ minutes, foreground — session restores
- Production
/api/healthzreturns 200 from the deployed domain - Production logs (Pino) do not show any tokens, secrets, or stack traces leaking to clients
- Trying CORS from an unlisted origin is rejected (
curl -H "Origin: https://evil.example" -v) - Trying any protected route without a Clerk token returns 401
- Trying to read another user's photo by guessed URL is denied
- Trying to confirm someone else's response returns 403/404