Initial commit: repo hygiene (README, .gitignore) + memory-bank planning docs
This commit is contained in:
commit
8fec909c45
10 changed files with 469 additions and 0 deletions
67
.clinerules/memory-bank.md
Normal file
67
.clinerules/memory-bank.md
Normal 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
39
.gitignore
vendored
Normal 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
35
README.md
Normal 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/).
|
||||
38
memory-bank/activeContext.md
Normal file
38
memory-bank/activeContext.md
Normal 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.
|
||||
40
memory-bank/productContext.md
Normal file
40
memory-bank/productContext.md
Normal 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
42
memory-bank/progress.md
Normal 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.
|
||||
51
memory-bank/projectbrief.md
Normal file
51
memory-bank/projectbrief.md
Normal 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).
|
||||
40
memory-bank/research/yo-app.md
Normal file
40
memory-bank/research/yo-app.md
Normal 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 $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.
|
||||
66
memory-bank/systemPatterns.md
Normal file
66
memory-bank/systemPatterns.md
Normal 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.
|
||||
51
memory-bank/techContext.md
Normal file
51
memory-bank/techContext.md
Normal 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.
|
||||
Loading…
Reference in a new issue