Initial commit: repo hygiene (README, .gitignore) + memory-bank planning docs

This commit is contained in:
2RE 2026-09-09 21:07:22 +08:00
commit 8fec909c45
10 changed files with 469 additions and 0 deletions

View file

@ -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.

39
.gitignore vendored Normal file
View file

@ -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

35
README.md Normal file
View file

@ -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/).

View file

@ -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.

View file

@ -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).

42
memory-bank/progress.md Normal file
View file

@ -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.

View file

@ -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).

View file

@ -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 $510M 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.

View file

@ -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: <recipient ExpoPushToken>, 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 `<AdBanner/>` 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.

View file

@ -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.