Skip to content

Latest commit

 

History

History
101 lines (87 loc) · 9.88 KB

File metadata and controls

101 lines (87 loc) · 9.88 KB

Overview

This project is a pnpm workspace monorepo using TypeScript, focused on the Glimpse mobile app. Glimpse is a privacy-first "missed-connections" Expo app designed to help users connect with people they've encountered in real life, with a strong emphasis on user privacy and data security. The app provides a platform for users to "drop" a Glimpse at a specific location, which becomes visible to others who were in the same approximate area after a 4-hour delay. Key features include location-based matching, mutual opt-in for chat, and robust moderation tools.

User Preferences

  • The user wants iterative development.
  • The user prefers to be asked before making major changes.

System Architecture

Glimpse Mobile App (artifacts/glimpse)

  • Framework: Expo / React Native with Expo Router.
  • State Management: React Context (AuthContext, GlimpseContext) combined with AsyncStorage for persistent data.
  • Image Processing: expo-image-manipulator (v14) for in-app photo cropping.
  • Styling: React Native StyleSheet, utilizing semantic color tokens (constants/colors.ts).
  • Design System: Warm minimal off-white theme (#F7F6F3 background), near-black text (#1A1A1A), monochrome primary buttons with softened rectangle shape (14px radius), subtle borders (#E5E3DE). No dark gradients. Employs an editorial typographic identity.
  • Logo: A map pin with a heart cutout (SVG, components/GlimpseLogo.tsx).
  • Fonts: Inter (400/500/600/700) via @expo-google-fonts/inter, and Playfair Display (400 Italic) via @expo-google-fonts/playfair-display for branding.
  • Button Radii: Primary action buttons use borderRadius: 14; smaller action buttons use borderRadius: 12. No full pill shapes are used.
  • Keyboard Handling:
    • A global <KeyboardToolbar /> from react-native-keyboard-controller ensures consistent keyboard dismissal.
    • Major submit handlers call Keyboard.dismiss() before async operations to prevent UI obstruction.
    • ScrollView/FlatList components containing inputs use keyboardShouldPersistTaps="handled".
  • Location System:
    • Place Selection: Uses a server-proxied Google Places API (New) via the PlacePicker component. A per-search-session UUID token manages billing for autocomplete and details requests.
    • "Use current location": Leverages Expo geolocation and OSM/Nominatim for reverse geocoding, which is also a fallback if Google Places API is unavailable.
    • Privacy Safeguards: Coordinates are fuzzed (~30m), sensitive locations auto-fallback to broader labels, time is displayed as fuzzy labels, matching uses spatialBucketId from raw coordinates, and Nominatim is rate-limited.
  • Core Product Rules: No real-time location sharing, Glimpse pins become visible after a 4-hour delay, matching requires overlapping coarse location (0.01° ~1km grid) and hourly time buckets, location data is purged after 72 hours, mutual opt-in for chat, and block/report controls are available.
  • Location Posting Validation: The @workspace/location-validation library (lib/location-validation) provides a single source of truth for presence-gate constants.
    • Server: Authoritatively enforces presence gates via Drizzle SQL predicates.
    • Mobile Pre-flight: Provides UX hints in DropPanel based on checkPresencePreflight() and recent-areas response, showing soft amber hints for obvious misses.
  • Block / Report Moderation: Server-backed (blocks and reports tables). Two-way enforcement filters blocked users from various UI lists and prevents interactions. Client-side invalidation ensures immediate UI updates.
  • Descriptor System: A 3-layer progressive disclosure system defined by lib/descriptorConfig.ts.
    • Layer 1 (Required): topColor, heightRange, hairColor.
    • Layer 2 (Optional): clothingType, activityTags, accessoryTags, companionContext.
    • Layer 3 (Refinements): Context-aware refinements based on Layer 2 selections, e.g., clothingStyleTags, activityRefinements. Max 180 characters, no handles or phone numbers.
  • Profile System:
    • Reusable ProfileCard component displays UserProfileSnapshot data: photoUris, displayName, age, height, job, city, bio, religion, politicalViews.
    • Photo Management: Server-backed via Replit object storage. Uses presigned URLs for direct client uploads, with CRUD operations (GET /api/me/photos, DELETE /api/me/photos/:id, PUT /api/me/photos) and a cap of 6 photos per user.
  • Screens:
    • /auth (welcome, sign in, sign up)
    • /location-onboarding (privacy, permissions)
    • /(tabs)/index (My Glimpses)
    • /(tabs)/discover (Discovery)
    • /(tabs)/drop (Drop a Glimpse with 3-section descriptor UI)
    • /(tabs)/responses (Incoming responses)
    • /(tabs)/profile (Profile tab with Edit/Preview, photo management)
    • /glimpse-detail (Full descriptor view, respond/block/report)
    • /chat (Private chat, mutual unlock only)
    • /profile-edit (Redirects to profile tab)
    • /settings (Subscription, blocked users, privacy info)
  • Account Deletion: Implemented via DELETE /api/me, which deletes the Clerk user and cascades deletions across all related database tables. The process handles Clerk 404s as success for idempotency and returns 500 on DB failure to ensure retry. Client-side, deleteMyAccount() is called before signOut().
  • UI Tweaks:
    • Onboarding transitions: screenOptions.contentStyle.backgroundColor set to colors.background to prevent black flashes.
    • Profile-setup photo grid: Refactored photo tiles with gradient scrims, re-positioned removeBtn and mainBadge, consolidated reorder chevrons into a reorderPill. Empty required slots now have varied affordances.
    • GlimpseCard layout: descriptors.header is a bold title, descriptors.note appears in a quote block only if present.
    • Glimpse deletion: Author-only delete (DELETE /api/glimpses/:glimpseId) with schema cascading for related data. UI includes a destructive button and confirmation modal.

API Server (artifacts/api-server)

  • API Framework: Express 5.
  • Build: esbuild (CJS bundle).
  • Global Error Handler: Returns structured JSON {error,code:"internal"} for 500 errors.
  • Rate Limiters: Implemented on POST /storage/uploads/request-url, PATCH /me, POST /me/photos, PUT /me/photos, POST/DELETE /me/blocks/:userId.
  • Moderation: POST /reports runs isInappropriate on the detail field before persistence.

External Dependencies

  • Database: PostgreSQL (Drizzle ORM for schema management and queries).
  • Authentication: Clerk (via @clerk/expo).
  • API Codegen: Orval (generates code from OpenAPI spec).
  • Validation: Zod (zod/v4) and drizzle-zod.
  • Location Services:
    • Google Places API (New) for venue search (server-proxied).
    • OpenStreetMap (OSM) / Nominatim for reverse geocoding (for "use current location" and as a fallback).
    • Expo Geolocation (for current location detection).
  • Image Handling: Replit object storage (for user-uploaded photos).
  • Analytics: Custom event logging system integrated with client and server.

TestFlight / App Store Readiness

Status of pre-submission blockers (last audit: see chat history):

Resolved in code

  • Privacy/Terms links (Apple Guideline 5.1.1): artifacts/glimpse/lib/legalLinks.ts exposes TERMS_URL and PRIVACY_URL, fed by EXPO_PUBLIC_TERMS_URL / EXPO_PUBLIC_PRIVACY_URL env vars (placeholder defaults to getglimpse.app/{terms,privacy}). Surfaced in two places: app/auth.tsx welcome screen ("by creating an account you agree to…") and app/settings.tsx About section. Both call Linking.openURL with a fallback Alert on failure.
  • iOS / Android permission strings: app.json ios.infoPlist defines NSLocationWhenInUseUsageDescription, NSCameraUsageDescription, NSPhotoLibraryUsageDescription. android.permissions lists ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION.
  • Schema sync on deploy: artifacts/api-server/.replit-artifact/artifact.toml production build.args is now sh -c "pnpm --filter @workspace/db run push && pnpm --filter @workspace/api-server run build", so drizzle-kit push runs against the deployment's DATABASE_URL before the server bundle compiles. Build fails loudly if push errors (intentional — better to block deploy than ship a stale-schema server).

Manual / out-of-code (must do before TestFlight upload)

  1. Set EXPO_PUBLIC_DOMAIN in EAS production build profile to the live deployment domain (NOT the dev Replit domain).
  2. Provision Clerk production instance; set EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_live_… in EAS, CLERK_SECRET_KEY=sk_live_… in deployment secrets.
  3. Set EXPO_PUBLIC_TERMS_URL and EXPO_PUBLIC_PRIVACY_URL in EAS to live, publicly-reachable URLs. Same URLs go in App Store Connect → App Information → Privacy Policy URL.
  4. Provision a separate production PostgreSQL (do NOT reuse the dev DATABASE_URL); set the prod URL only in deployment secrets.
  5. App Store Connect privacy questionnaire: declare email, display name, age, photos, precise location, user-generated text. All under "App Functionality" / "Authentication".
  6. Demo reviewer account (appreview@…) created in Clerk production, run through onboarding once so a profile exists; paste credentials in App Review notes.
  7. App Review notes should explain location use (presence-gate, anti-spam, coarse coordinates, never shared with other users).

Design decisions

  • 18+ gate is enforced at profile-setup (app/profile-setup.tsx:107-115), not signup. Acceptable because onboarding is mandatory before any in-app action.
  • Schema sync uses drizzle-kit push rather than versioned migrations to keep parity with scripts/post-merge.sh. If/when the project moves to a true migration workflow (drizzle-kit generate + migrate), update both post-merge.sh and the production build args together.