commit 8fec909c45881a7e3debde7879754c951316158b Author: 2RE Date: Wed Sep 9 21:07:22 2026 +0800 Initial commit: repo hygiene (README, .gitignore) + memory-bank planning docs diff --git a/.clinerules/memory-bank.md b/.clinerules/memory-bank.md new file mode 100644 index 0000000..12b8bd8 --- /dev/null +++ b/.clinerules/memory-bank.md @@ -0,0 +1,67 @@ +# Cline's Memory Bank + +I am Cline, an expert software engineer with a unique characteristic: my memory resets completely between sessions. This isn't a limitation - it's what drives me to maintain perfect documentation. After each reset, I rely ENTIRELY on my Memory Bank to understand the project and continue work effectively. I MUST read ALL memory bank files at the start of EVERY task - this is not optional. + +## Memory Bank Structure + +The Memory Bank consists of core files and optional context files, all in Markdown format. Files build upon each other in a clear hierarchy: + +### Core Files (Required) +1. `projectbrief.md` + - Foundation document that shapes all other files + - Created at project start if it doesn't exist + - Defines core requirements and goals + - Source of truth for project scope + +2. `productContext.md` + - Why this project exists + - Problems it solves + - How it should work + - User experience goals + +3. `activeContext.md` + - Current work focus + - Recent changes + - Next steps + - Active decisions and considerations + - Important patterns and preferences + - Learnings and project insights + +4. `systemPatterns.md` + - System architecture + - Key technical decisions + - Design patterns in use + - Component relationships + - Critical implementation paths + +5. `techContext.md` + - Technologies used + - Development setup + - Technical constraints + - Dependencies + - Tool usage patterns + +6. `progress.md` + - What works + - What's left to build + - Current status + - Known issues + - Evolution of project decisions + +### Additional Context +Create additional files/folders within memory-bank/ when they help organize: +- Complex feature documentation +- Integration specifications +- API documentation +- Testing strategies +- Deployment procedures + +## Documentation Updates + +Memory Bank updates occur when: +1. Discovering new project patterns +2. After implementing significant changes +3. When user requests with **update memory bank** (MUST review ALL files) +4. When context needs clarification + +REMEMBER: After every memory reset, I begin completely fresh. The Memory Bank is my only link to previous work. It must be maintained with precision and clarity, as my effectiveness depends entirely on its accuracy. \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bdfa8ba --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +# Dependencies +node_modules/ + +# Expo / React Native (generated by `expo prebuild` — do not commit) +.expo/ +.expo-shared/ +dist/ +web-build/ +ios/ +android/ + +# Builds / cache +*.tsbuildinfo +*.js.map +.expo/types/ + +# Env / secrets +.env +.env.* +!.env.example +*.pem +google-services.json +GoogleService-Info.plist +*.jks +*.keystore +credentials.json +auth.json + +# Logs +*.log +npm-debug.log* +yarn-error.log* +pnpm-debug.log* + +# OS / editor +.DS_Store +Thumbs.db +.idea/ +*.swp diff --git a/README.md b/README.md new file mode 100644 index 0000000..e00ca91 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# iFarted + +> Send a friend exactly one thing: **"I farted."** + +A dead-simple cross-platform mobile app: pick a person, optionally attach your +location, and send them a push notification that says exactly one thing — +**"I farted."** They can one-tap fart right back. No typing, no inbox, no feed. + +Modeled on the 2014 **Yo!** app (context-based messaging) — with monetization in +from day one. + +## What it is +- **iOS + Android** — React Native + Expo + TypeScript, one codebase +- The fixed message is *"I farted."* — meaning comes from context (who, when, where) +- Optional **location** attached per message → recipient sees a map pin +- Custom notification sound; notifications are ephemeral by design (no history) + +## Find & add friends (all three ways) +- Unique **@username** + search +- Opt-in **phone contacts** matching +- **Invite code + deep link** + +## Architecture +| Piece | Stack | +|---|---| +| `apps/mobile` | Expo (React Native + TypeScript) client | +| `apps/server` | Lightweight **Bun** relay → **Expo Push API** → APNs/FCM; SQLite storage (Node-runnable) | +| `packages/contracts` | Shared API types | + +## Monetization +- Free tier with ads (AdMob) +- One-time **Remove Ads** IAP (non-consumable, restorable) + +## Status +Design locked, zero code. Planning notes live in [`memory-bank/`](memory-bank/). diff --git a/memory-bank/activeContext.md b/memory-bank/activeContext.md new file mode 100644 index 0000000..89d0998 --- /dev/null +++ b/memory-bank/activeContext.md @@ -0,0 +1,38 @@ +# Active Context — iFarted + +*Last updated: 2026-09-09 (session 2)* + +## Current State +- **Greenfield.** No app code, no git repo yet. Workspace: `.clinerules/`, `memory-bank/` (6 core files + `research/yo-app.md`). +- **Stack (locked):** React Native + Expo + TypeScript (accepted earlier). +- **This session's decisions (locked by user):** + 1. **Identity & discovery = all three:** unique @username + search · phone/contacts (opt-in) · invite code/deep link. + 2. **Backend = lightweight Bun + Expo Push API** (user asked "is Bun possible?" → **yes**, answered + recorded in techContext; Bun not yet installed on this box). + 3. **Product design = Yo! pattern** (user: "research how they handled it and do that") → see `memory-bank/research/yo-app.md`. Key adoptions: fixed phrase w/ zero typing, notification text+audio, contact-list home with tap-to-send + one-tap fart back, **ephemeral (no inbox/history)**, context-based messaging framing (also our App Review explanation), username addressing. + +## Recent Changes (this session) +- 2026-09-09: Researched Yo! (Wikipedia + CNET via Wayback). Created `memory-bank/research/yo-app.md`. Answered the Bun feasibility question (yes). Locked identity (all 3), backend (Bun + Expo Push API), product model (Yo-style) and rewrote the memory-bank core files to match. + +## Remaining Open Decisions (small, non-blocking) +1. **Audio asset:** the fart notification sound file (must be <30s for iOS; on-brand, not too loud/gross for reviewers). +2. **Remove Ads:** price point (~$1.99 suggestion) + library (expo-iap vs RevenueCat). +3. **Ad placement:** default = AdMob banner on home; decide whether an interstitial after send is worth the UX/review cost. +4. **Branding:** final store name (working: iFarted), icon, screenshots, store copy, in-app copy tone pass. +5. Server deployment target (cheap VPS/fly.io/Railway) + invite deep-link domain once branding is set. + +## Next Steps +1. `git init` + repo hygiene (README, `.gitignore`), commit memory-bank. +2. Scaffold monorepo: `apps/mobile` (Expo TS) + `apps/server` (Bun + Hono + SQLite) + `packages/contracts` (shared API types). +3. Install Bun on this box; stand up the relay server with the REST API from systemPatterns. +4. Prove device-to-device fart end-to-end (two dev-build devices, Expo Push API, custom sound). +5. Implement client screens per productContext (onboarding incl. 3 add-friend paths, home, fart detail + map, settings). +6. Wire ads + Remove Ads IAP + gating + restore. +7. Permissions/privacy polish (purpose strings, privacy labels). +8. Alpha → store submissions (EAS + TestFlight/Play internal). + +## Important Patterns / Preferences to Preserve +- Tiny, single-purpose product — **resist feature creep**; the Yo research (research/yo-app.md) is the reference for "does this serve the fart notification?". +- Ads first, Remove Ads IAP second; single gated ad component. +- Expo managed workflow + config plugins; `app.json` as source of truth. +- Server code stays Bun **and** Node-runnable. +- One TS codebase for both stores; platform differences only where push/permissions/sound demand it. diff --git a/memory-bank/productContext.md b/memory-bank/productContext.md new file mode 100644 index 0000000..b7d378d --- /dev/null +++ b/memory-bank/productContext.md @@ -0,0 +1,40 @@ +# Product Context — iFarted + +## Why This Project Exists +Pure-play comedic utility. The product *is* the punchline: a friend's phone lights up with a deadpan push notification reading **"I farted."** There is no feed, no inbox, no typing — the notification itself is the entire message. This is **Yo!** (2014) with flatulence: Yo's own feature summary ("send individual notifications to other users, simply containing the word 'Yo'... additionally send their location") is essentially our brief. + +The **location attachment** upgrades the joke ("I farted." *where?*) and drives opening the app to see the map pin — which is also where ads live. + +## Positioning (stolen from Yo, verbatim philosophy) +**"Context-based messaging. You understand by the context what is being said."** — Or Arbel (Yo creator), via CNET. One phrase; the sender, the timing, and the optional location carry the meaning. A fart at 8am from your partner means "good morning." A fart from a co-worker while you're in a meeting means "get me out." This framing also doubles as our App Review explanation (Apple once rejected Yo for being "too simple"). + +## How It Should Work (UX Flow — Yo pattern) +1. **Onboarding (< 60s):** + - Claim a unique **@username** (first-come-first-served, Yo/Twitter style). + - Optional: add phone number → opt-in "find friends from contacts." + - Alternative entry: open an **invite link/code** → auto-connect to inviter. + - Request notification permission with a plain-language explanation. +2. **Home screen:** list of your people (most-recently active first) + big primary send action + "attach my location" toggle. Ad banner (default placement). Empty state pushes the **three add-friend paths** (search username / contacts / invite). +3. **Send:** tap a person → instant delivery feedback ("Fart delivered 🫢"). Optionally tap a **"with location"** toggle first. +4. **Recipient experience:** push = **title: sender's name, body: "I farted.", custom fart sound** (Yo sent text + an audio alert of the word). Tap → app: + - no location → deadpan "whoever farted" screen with a **one-tap "fart back"**; + - with location → map pin at the sender's location + one-tap fart back. +5. **Settings:** Remove Ads (IAP) + Restore Purchases, notification sound on/off, phone-discovery toggle, account (username, sign out), privacy note. +6. **Ad-free:** owning the entitlement unmounts ad containers everywhere. + +## Ephemerality (Yo decision — resolved) +No message history/inbox/feed. The notification IS the message; the app only shows the *latest* fart from a person to keep the recipient list ordered. Nothing to scroll, nothing to archive. + +## Experience Goals +- Setup < 60 seconds; sending = 1 tap (2 with location); zero typing, always. +- Tone: consistently dry/wry, never gross; the name + store listing set the tone. +- Privacy feels safe: location per-message and explicit; phone only used for opt-in matching. +- Add-a-friend is the retention lever — all three connection paths must be one or two taps from the empty state. + +## Target Users +Friends, partners, roommates (teens/adults). Viral loop: receiving a fart notification is funny enough to screenshot/share → "send a fart to your friends." + +## Key UX Risks / Notes +- **Empty network kills the app** → first-run add-a-friend flow is the most important screen. +- **Harassment vector** (Yo suffered spam/spoofing) → server-side rate limits + block; keep the recipient list explicit (you only receive farts from people you've added, pending acceptance for strangers). +- Ad placement must never block the joke (banner; no interstitial before sending). diff --git a/memory-bank/progress.md b/memory-bank/progress.md new file mode 100644 index 0000000..bd47928 --- /dev/null +++ b/memory-bank/progress.md @@ -0,0 +1,42 @@ +# Progress — iFarted + +## Current Status: Design locked, zero code (greenfield) +No application code exists yet. All major product/architecture decisions resolved. Ready to scaffold. + +## What Works +- Nothing application-wise yet. +- Full requirements + architecture captured in Memory Bank (6 core files + `research/yo-app.md`). + +## Decisions Made (all user-confirmed) +- Target platforms: iOS + Android. +- Core action: send another user an individual push notification containing **"I farted."** (fixed phrase, zero typing); optional per-message location. +- **Identity & discovery: all three** — unique @username + search · phone/contacts (opt-in) · invite code/deep link. +- **Backend: Bun** (Node-compatible) **+ Expo Push API**; SQLite storage; no Firebase Functions/Firestore. (Bun feasibility confirmed — plain HTTPS/JSON; install still pending on dev box.) +- **Product design: Yo! (2014) pattern** — researched (Wikipedia + CNET); adopted context-based messaging, text+audio notification, contact-list home with tap-to-send + one-tap fart back, **ephemeral (no inbox/history)**. See `memory-bank/research/yo-app.md`. +- Monetization: ads (AdMob) + **one-time non-consumable Remove Ads IAP**. +- Mobile stack: React Native + Expo + TypeScript; EAS Build for iOS from this Linux box. + +## What's Left to Build (MVP roadmap) +1. Repo setup: `git init`, README, `.gitignore`, commit memory-bank. +2. Scaffold monorepo: `apps/mobile` (Expo TS), `apps/server` (Bun + Hono + bun:sqlite), `packages/contracts` (shared types). +3. Install Bun; implement relay server per systemPatterns REST API (register/tokens/farts/search/contacts/invites/block + rate limiting). +4. Device-to-device fart end-to-end: two dev builds, Expo Push API, custom sound, location payload. +5. Client screens per productContext (onboarding incl. 3 add-friend paths, home list, fart detail + map, settings). +6. Ads (AdMob banner) + Remove Ads IAP + gating + restore. +7. Permissions/privacy polish (purpose strings, privacy labels). +8. Alpha on real devices (both platforms). +9. Store assets + compliance review → TestFlight + Play internal testing. + +## Remaining Minor Open Items +Audio asset · Remove Ads price/lib · ad placement confirmation · final branding · deploy target (see activeContext.md). + +## Known Issues / Risks +- **iOS review:** Yo was initially rejected for being "too simple" → have the context-based messaging explanation ready; keep copy clean. +- **Harassment/spam:** Yo was hacked + spammed in 2014 → strict auth, no unauthenticated PII, rate limits + block (baked into API design). +- **No business model killed Yo** → monetization is in from day one (ads + IAP). +- iOS builds can't run on this Linux box → EAS cloud build (or a Mac) required. +- Android push requires a Firebase project for FCM client credentials even with Expo Push API (secret `google-services.json`, injected at build). + +## Evolution Log +- **2026-09-09 (s1)** — Memory Bank initialized; requirements captured; stack recommendation (RN + Expo + TS) accepted. +- **2026-09-09 (s2)** — Yo! app researched (`memory-bank/research/yo-app.md`); Bun feasibility answered (yes); decisions locked: identity = all three mechanisms, backend = Bun + Expo Push API, product = Yo-style context-based messaging with ephemeral farts. Memory-bank core files updated. diff --git a/memory-bank/projectbrief.md b/memory-bank/projectbrief.md new file mode 100644 index 0000000..c2a1fde --- /dev/null +++ b/memory-bank/projectbrief.md @@ -0,0 +1,51 @@ +# Project Brief — iFarted + +## Working Title +**iFarted** (matches the working directory). Branding style follows **Yo!** (single word = the product). Final store name/icon/copy still TBD. + +## One-Liner +A dead-simple cross-platform mobile app that lets a user send another user an **individual push notification** containing exactly the phrase **"I farted."** — optionally with the sender's current **location** attached. Modeled on the 2014 **Yo!** app (see `memory-bank/research/yo-app.md`). + +## Platform Targets +- iOS (iPhone) — Apple App Store +- Android — Google Play Store + +## Product Model (Yo! pattern — "context-based messaging") +- The user **never types anything**. The single fixed message is "I farted."; meaning comes from context (who sent it, when, and where). +- Recipient gets a push notification (text + custom audio sound) — the notification *is* the message. **No inbox/history/feed** (Yo-style ephemeral). +- Home = recipient list; **tap a person → they get the fart**. Recipient can **one-tap fart back**. + +## Core Requirements (MVP) +1. **Identity & discovery — ALL THREE mechanisms (user decision):** + a. Claim a unique **@username**; find others via **username search** + add. + b. Optional **phone number** on profile → **contacts matching** (opt-in only, privacy-safe matching server-side). + c. **Invite code + deep link** — sender generates one, friend opens it and is pre-connected. +2. **Compose & send** — choose a recipient from your list, optionally toggle "attach my location," send the fixed phrase "I farted." +3. **Delivery** — push notification to recipient: **title = sender's display name, body = "I farted."**, custom fart audio sound (mirrors Yo's text+audio alert). With location attached, tapping opens the app and shows a map pin of the sender. +4. **Ads** — AdMob ads (default: banner on home; placement/frequency TBD). +5. **Remove Ads IAP** — one-time **non-consumable** purchase that permanently removes ads, billed through StoreKit / Google Play Billing. + +## Backend (user decision) +- **Lightweight server written in Bun** (Node-compatible runtime; "yes, Bun is possible") relaying through the **Expo Push API** (no Firebase Functions/Firestore, no raw APNs/FCM management server-side). +- SQLite storage (Bun's `bun:sqlite`). Must be trivially runnable under plain Node too. + +## Non-Goals (MVP) +- Message history/inbox/feed (ephemeral by design) +- Group broadcasts, scheduled/recurring farts, reactions +- Free-text chat or any content beyond the fixed phrase +- Web/desktop clients +- Accounts heavier than needed for reliable recipient targeting + push tokens + +## Monetization Model +- **Free tier:** ads. +- **Paid tier:** one-time Remove Ads IAP (non-consumable, permanent entitlement, restorable). +- Yo died in 2016 for lack of revenue — monetization is in from day one. + +## Business/Policy Constraints to Respect +- **No P2P push.** Delivery always routes backend → **Expo Push Service** → **APNs** (iOS) / **FCM** (Android). +- Push must be **user-initiated and targeted** at a known recipient (anti-spam + store policy). +- Location requires per-platform runtime permissions, iOS purpose strings, and store privacy disclosures. +- Android still needs a **Firebase project** solely for FCM client credentials (`google-services.json`) even though the backend uses the Expo Push API. +- iOS builds require macOS/Xcode or **EAS cloud build** + Apple Developer Program ($99/yr); Android requires Google Play Console ($25 one-time). +- Remove Ads must be a genuine store-billed IAP — out-of-band payment for ad removal is grounds for rejection. +- Yo was hacked (2014) exposing phone numbers + enabling Yo-spam → our API is auth'd end-to-end, PII-protected, and rate-limited (see systemPatterns). diff --git a/memory-bank/research/yo-app.md b/memory-bank/research/yo-app.md new file mode 100644 index 0000000..630ccd6 --- /dev/null +++ b/memory-bank/research/yo-app.md @@ -0,0 +1,40 @@ +# Research Notes — "Yo!" (2014) and What We Copy for iFarted + +*Researched 2026-09-09. Sources: Wikipedia "Yo (app)" (current revision, 2026) + CNET article "The million-dollar app that exists to say 'Yo'" (June 19, 2014), retrieved via Wayback Machine.* + +## What Yo was +- iOS/Android/Windows Phone app released **April 1, 2014** by Israeli developer **Or Arbel**, built in **~8 hours** at the request of Moshe Hogeg (Mobli CEO), who wanted a **single-button app** to "call" his assistant/wife without picking up the phone. +- **Apple initially rejected it for being "too simple."** It exploded after appearing on Product Hunt. ~20k users in month one; 1M+ downloads by June 2014; 100M+ "Yos" sent by Sept 2014; ~$2.5M raised at a $5–10M valuation. +- Company **shut down in 2016** ("autopilot"); later kept alive via Patreon (2018). **It never had a real business model** — the cautionary tale for our ad + IAP plan. + +## How it worked (verified quotes) +- Wikipedia's feature summary is *almost word-for-word our user's brief*: **"The app enabled users to send individual notifications to other users, simply containing the word 'Yo'. Users could additionally send their location."** +- Sending (CNET): **"You have a list of contacts. You tap one of those contacts, and they receive a notification saying simply, 'Yo', along with an audio alert of the word being spoken."** +- Positioning (Arbel via NYT/CNET): **"We like to call it context-based messaging. You understand by the context what is being said."** The same "Yo" means good morning, "thinking about you", "meeting's over", "are you up?" depending on context. +- **Addressing was by unique username** — e.g., a "worldcup" account yo'd followers whenever a team scored (later formalized via a public Yo API). +- Evolution: Aug 2014 → profiles, links, hashtags. **Oct 2014 → send your location.** June 2015 v2 → photos or location **"within 1 swipe and a tap from the home screen"** + groups (yo several friends with one tap). +- Notifications were **text + audio** (the word spoken aloud). No inbox/feed of messages — the notification *was* the message. + +## Failures / lessons (what NOT to repeat) +1. **No monetization → died.** We monetize from day one: ads + one-time Remove Ads IAP. +2. **June 2014 security hack** (Isaiah Turner): anyone could retrieve *any user's phone number* and spam/spoof Yos → we must: auth on every endpoint, never leak PII from unauthenticated lookups, unguessable tokens, per-sender rate limits, block path, abuse monitoring. +3. **Apple review rejected "too simple"** → prepare a purpose/value explanation for App Review using the context-based messaging framing. +4. **Novelty decays fast** → single-purpose is the hook; retention levers are the friend-connection flow and monetization, not features. + +## What we adopt ("do that") +| Yo | iFarted adaptation | +|---|---| +| Single fixed word "Yo", zero typing | Single fixed phrase **"I farted."**, zero typing | +| Push = "Yo" + audio alert | Push body "I farted." (+ sender display name); **custom audio notification sound** (iOS bundle sound <30s; Android notification-channel sound) | +| Contact list, tap to send | Home = recipient list, tap → send; **one-tap "fart back"** after receiving | +| Context-based messaging | Same framing: one phrase, meaning comes from context | +| Username addressing | **All three** connection methods: username search · phone contacts (opt-in) · invite code/link | +| Location attach (Oct 2014) | Per-message location toggle → map pin when recipient opens | +| Groups (v2, 2015) | Post-MVP stretch feature | +| No revenue model | AdMob ads + non-consumable Remove Ads IAP | + +## Copy/UX defaults derived from Yo (pending final wording) +- Notification: **title = sender's display name, body = "I farted."** +- Home: recipient list (recent first), big primary send action; empty state nudges "Add friends". +- **No message history/inbox** — messages are the notifications themselves (ephemeral). +- Primary onboarding: claim a unique **@username**; optionally verify phone for contact matching; invite via code/link. diff --git a/memory-bank/systemPatterns.md b/memory-bank/systemPatterns.md new file mode 100644 index 0000000..71826bb --- /dev/null +++ b/memory-bank/systemPatterns.md @@ -0,0 +1,66 @@ +# System Patterns — iFarted + +## Target Architecture (user decisions locked: Bun backend + Expo Push API) + +``` +[Sender phone — iOS/Android, Expo/RN] + │ POST /v1/farts { recipientId, lat?, lng? } (Bearer apiKey) + ▼ +[Bun relay server] (TypeScript, Hono or plain fetch handlers, bun:sqlite) + │ auth + rate limit → insert message → call Expo Push API: + │ POST https://exp.host/--/api/v2/push/send + │ { to: , title: senderName, + │ body: "I farted.", sound: "fart.caf|mp3", data: {...} } + ▼ +[Expo Push Service] → [APNs / FCM] + ▼ +[Recipient phone] → OS notification → tap → in-app fart view (map pin if coords) +``` + +Guiding rules: +- **No P2P push; no raw APNs/FCM on the server.** Bun talks only to the Expo Push API; Expo's service handles APNs/FCM. (Android still needs a Firebase project for the *client's* FCM token.) +- Thin client, thin backend: client sends a tiny intent; backend validates, persists a stub, and relays; there is no inbox to serve. +- Server must run identically under Bun or Node (keep bun-specific APIs optional so Node is a trivial fallback). + +## REST API (draft) +| Endpoint | Purpose | +|---|---| +| `POST /v1/register` | Create user. Body: `{ username?, phoneE164?, inviteCode? }` → returns `{ userId, apiKey }`. Username claim is unique/case-insensitive; inviteCode pre-links. | +| `POST /v1/tokens` | Register/refresh device push token `{ expoPushToken, platform }` (Bearer). | +| `POST /v1/farts` | Send. Body: `{ recipientId, lat?, lng? }`. Server: check relationship+block list → rate limit → persist → Expo Push. Returns `{ ok, messageId }`. | +| `GET /v1/users/search?username=` | Public lookup by username (returns only id/username/displayName — never phone). | +| `POST /v1/contacts` | Body: `{ phoneE164: string[] }` → returns which of *my contacts* are users who enabled phone discovery. Contact numbers hashed/normalized server-side; not stored raw. | +| `POST /v1/invites` | Create invite code for my username; client renders as code + deep link (exp:// / https link). | +| `POST /v1/block` | Body `{ userId }` → stop receiving/sending. | + +## Mobile Client Patterns +- **Navigation:** expo-router; flows = auth/onboarding, home(recipients), fart-detail(maps), settings. +- **State:** React Context or Zustand. No Redux at MVP. +- **Home data:** recipient list = contacts + people who sent you a fart (latest first, Yo-style), not a server inbox. +- **Screens:** Onboarding (username/phone/invite) → Home (list + big send + location toggle + ad banner) → Send toast → Fart detail (+ map pin, one-tap **fart back**) → Settings (Remove Ads + Restore, sound toggle, phone-discovery toggle, account). +- **Permissions:** notifications at first run with purpose text; location lazily via `expo-location` only when "attach location" tapped (iOS When-In-Use). +- **Ads abstraction:** single gated `` routed through one `isAdFree` flag. +- **Deep links:** invite links and notification taps both resolve into navigation (messageId or username pre-link). +- **Push payload (data):** `{ type:"fart", messageId, senderId, senderName, lat?, lng?, sentAt }` — notification body is "I farted.", title is sender name, custom sound file. + +## Data Model (SQLite) +- `users`: id, username (unique ci), display_name?, phone_e164?, phone_discovery (bool), invite_code (unique), api_key_hash, created_at, updated_at. +- `push_tokens`: id, user_id, expo_push_token (unique), platform, last_seen_at. +- `relationships`: id, owner_id, peer_id, status (added/blocked/pending-invite), added_via (username|contacts|invite), created_at. +- `messages`: id, sender_id, recipient_id, lat?, lng?, created_at — retained only for rate limiting/abuse/receipts; **never rendered as history**. +- `invites`: code, creator_id, created_at, accepted_by_user_id?. + +## Monetization Architecture +- Product: non-consumable **`remove_ads`** in App Store Connect + Play Console. +- Entitlement source of truth = store state (expo-iap or RevenueCat — library TBD; RevenueCat favored for cross-platform entitlement mgmt + restore). +- Launch + purchase + restore resolve `isAdFree` → ad components unmount and stop loading. +- AdMob: **non-personalized** ads initially → no ATT complexity. + +## Security & Privacy (direct responses to the 2014 Yo hack) +- Every endpoint requires Bearer `apiKey` (random 256-bit, hashed at rest). No unauthenticated PII access. +- Username search returns only non-PII profile fields. Contacts matching uses **hash-normalized** numbers and only reveals matches to users who enabled discovery. +- Server-side rate limits per sender (e.g., N farts/hour) + per-recipient cap + block list → stops fart-spam/spoofing. +- Location: per-message opt-in, only to the chosen recipient; not logged in analytics. +- Expo push tokens are the only "FCM/APNs" secret-ish material on the server; no server keys ship in the app. +- No API keys/tokens in client source; issued per-install at register. +- Invite deep links carry a random unguessable code, not phone numbers. diff --git a/memory-bank/techContext.md b/memory-bank/techContext.md new file mode 100644 index 0000000..c2cf83d --- /dev/null +++ b/memory-bank/techContext.md @@ -0,0 +1,51 @@ +# Tech Context — iFarted + +## Stack (decisions locked by user) +| Concern | Choice | Notes | +|---|---|---| +| Mobile framework | **React Native via Expo (managed workflow), TypeScript** | One codebase → iOS + Android | +| Build/sign/submit | **EAS Build** (cloud) | Linux box → iOS builds via EAS cloud (or a Mac) | +| Push (client) | `expo-notifications` | Acquires **ExpoPushToken**; needs `projectId` in app.json | +| Push (server) | **Bun** calling the **Expo Push API** (`exp.host/--/api/v2/push/send`) | **YES, Bun is possible** — Expo's push API is plain HTTPS+JSON; Bun's runtime handles it with built-in `fetch`. No Firebase Functions/Firestore. | +| Backend framework | Bun + **Hono** (or plain fetch handlers) | Runtime-agnostic TS so plain Node is a trivial fallback | +| Storage | **bun:sqlite** (SQLite) | Single file DB; zero external services | +| Location | `expo-location` | Permission-gated, per-message opt-in | +| Maps | `react-native-maps` / `expo-maps` | Pin sender location on recipient device | +| Ads | AdMob via `react-native-google-mobile-ads` (+ Expo config plugin) | Non-personalized ads first | +| IAP | `expo-iap` **or** RevenueCat `react-native-purchases` | RevenueCat favored (entitlements + restore); final TBD | +| Navigation | expo-router | File-based | + +## Bun question — answer recorded 2026-09-09 +**Yes, Bun is possible and adopted.** The Expo Push API is a plain HTTPS REST endpoint; Bun (a Node-compatible JS/TS runtime) can call it with built-in `fetch` and run the whole relay with zero native-module risk. Bun also ships `bun:sqlite` for storage and starts fast. **Caveats:** Bun is *not installed on this box yet* (install via `curl -fsSL https://bun.sh/install | bash` → `~/.bun/bin/bun`); keep the server code Node-runnable so falling back to `node` is trivial; avoid bun-only APIs except optional `bun:sqlite` (swap to `better-sqlite3` if we need Node-only). Node v22 is the fallback runtime. + +## Development Environment (current box) +- OS: Linux. Node v22.22.1, npm 9.2.0, OpenJDK 25.0.4, Python 3.14.4, git 2.53.0. **Bun not yet installed.** No Flutter (not needed). +- Android SDK availability **TBD** — needed for local Android builds/emulator; EAS can cloud-build if absent. +- Workspace: `/opt/system/apps/VSCode-iFarted-app/VSCode.AppImage.home/iFarted` (only `.clinerules/`, `memory-bank/` so far). + +## Accounts & Services Required (dev → release) +- Apple Developer Program ($99/yr): signing, APNs key, App Store. +- Google Play Console ($25 one-time): signing, Play Billing, releases. +- Expo account (free): EAS builds; push `projectId`. +- **Firebase project (free)** — needed for **Android FCM client credentials** (`google-services.json` + FCM sender id), even though the backend uses Expo's Push API (expo-notifications registers an Android FCM token with the app's Firebase project). +- Google AdMob (free, approval): ad units for iOS + Android. +- App Store Connect + Play Console IAP entries: `remove_ads` non-consumable. + +## Push mechanics (specifics that bite later) +- Client: `expo-notifications` → `getExpoPushTokenAsync()` → `ExpoPushToken[...]`, send to `POST /v1/tokens`. +- Server: `POST https://exp.host/--/api/v2/push/send` with `{ to, title, body, sound, data }`; batch ≤100 tokens; optionally poll `push/getReceipts` for delivery status. +- Custom notification sound: iOS bundles a <30s audio file referenced in the payload `sound`; Android defines a notification channel with the sound. (Yo's signature was text **+ audio alert** — we mirror with a short fart sound.) +- Android FCM credentials (`google-services.json`) are a **secret** → injected at EAS build time, never committed. + +## Technical Constraints & Gotchas +- **Cannot build/submit iOS from this Linux machine** → EAS Build cloud or a Mac is mandatory. +- Privacy/permissions: iOS `NSLocationWhenInUseUsageDescription` via app.json/plugin; Android location runtime permissions; push permission rationale; store privacy "nutrition labels". +- Remove Ads must be store-billed IAP; entitlement restorable across reinstall/device. +- Expo config plugins over ejecting; `app.json` is the source of truth. +- No API keys/PII in client source; phone numbers hashed for contact matching. + +## Tooling Patterns / Conventions +- TypeScript strict; small feature folders; monorepo layout suggestion: `apps/mobile` (Expo) + `apps/server` (Bun) + shared `packages/contracts` (API types). +- `eas build` for internal + store builds; **development builds** for push testing (Expo Go has Android push limitations). +- Secrets via EAS env vars / `.env` (git-ignored). +- Test on real devices early — push, sound, maps, location are device-dependent.