A shared virtual home for long-distance couples. The home is a mirror for the relationship: stay attentive and it glows; go quiet and it visibly softens — plants wilt, music stops, candles go dark. No scores, no points. The home state is the feedback.
This repo is the playable web MVP (Phase 1 core loop from the PRD) plus a Node + MongoDB backend ready to grow into the full product.
Open public/index.html in any browser. That's it. The app runs fully
offline using browser storage — onboard, tend the home, run rituals, watch it
decay and restore.
Tip: use the demo bar at the bottom (
+6h,+2d,partner,reset) to fast-forward decay and simulate your partner arriving, so you can feel weeks of the loop in seconds and experience both sides solo.
This is the sync mode: real accounts, a partner invite link, one shared home, and live updates between partners.
npm install
cp .env.example .env # point MONGODB_URI at local mongod or Atlas
npm start # http://localhost:3000Then flip the client to sync mode — in public/index.html:
window.PRESENCE_CONFIG = { mode: 'sync', apiBase: '/api' };Flow: sign up → Invite my partner gives you a link → open it in a second browser/device, sign up, Join this home. Now both of you share one home — water the plant on one screen and it updates on the other, candles light when you're both connected, letters and songs cross over live.
How the sync works (no third-party realtime service needed):
- Accounts: scrypt-hashed passwords + signed tokens (
server/auth.js). - Live updates: Server-Sent Events per couple (
/api/stream) push partner actions and presence; candles light only when both are connected.
Two layers, each playing to its strength:
- Slim live-state doc (
Snapshot, one per couple) — only the bounded, always-needed state: each object's health + last-restored time, the current song, streak, candle/presence, and a capped notification list. Small and fast to read/write on every interaction. Decayed on read, mutated on each ritual (server/engine-core.js). - Append-only history collections —
Memory,Letter, andRitual, one document per event. They grow without bloating the live doc (no 16MB-document ceiling), are independently queryable (memory wall, anniversary video, analytics), and — because each is its own insert — don't suffer the read-modify-write race two partners would hit when sharing one blob.
The client receives the same assembled game shape either way, so the UI
doesn't care which layer a field came from.
Concurrency note: history writes are race-free. The slim live-state doc is still read-modify-write; under heavy simultaneous use the final hardening step is per-object atomic updates (move object health to a
GameObjectcollection with$inc/$max). Not needed at MVP scale.
(There's also a simpler account-less mode: 'api' that shares one demo home
via /api/state — handy for quick local testing.)
Run the decay job (the PRD's pg_cron equivalent) on a schedule or once:
npm run decay # loops every 6h
node server/decay.js --once # single passThere's a native shell in mobile/ that wraps this web client and adds push
notifications. With your phone on the same Wi-Fi:
npm start # backend, in the project root (one terminal)
cd mobile && npm install && npx expo start # second terminal
Install Expo Go on your phone and scan the QR code. Full guide:
mobile/README.md.
| Area | Status |
|---|---|
| Home renders as a warm, lit 2D scene (5 rooms) | ✅ SVG/CSS (stands in for Pixi.js) |
| Objects: plant, fridge, record player, letterbox, candles, garden | ✅ all six, 4 health states each |
| Decay logic (per-object rates, warmth = avg of 3) | ✅ §2.3 / §5.2 faithful |
| Warmth → ambient light mapping (full→dormant) | ✅ §2.3 |
| Async rituals: water plant, drop song, leave letter | ✅ |
| Sync rituals: morning coffee, cook together | ✅ (simulated, single-player) |
| Memory wall (polaroid grid, milestone pinning) | ✅ §6 |
| Poetic notification feed ("Home, speaking") | ✅ §8 copy, home's-perspective |
| Candle / presence mechanic | ✅ partner-online toggles candles §2.4 |
| Streak + garden day-30 unlock | ✅ |
| Onboarding (name → date → starter theme) | ✅ §9 condensed |
| MongoDB models matching PRD schema | ✅ §2.2 (Postgres → Mongoose) |
| REST API + seed + decay job | ✅ |
| Real accounts + partner invite/join | ✅ sync mode |
| One shared home between two people | ✅ server-authoritative |
| Live partner updates + presence | ✅ SSE (Realtime stand-in) §2.4 |
| Native iOS/Android app | ✅ Expo WebView shell (mobile/) |
| Push notifications to partner's phone | ✅ Expo Push on ritual/join §8 |
Supabase Realtime (we use SSE), Daily.co voice, Spotify OAuth, Remotion anniversary video, RevenueCat paywalls, and the Printful book. The engine and schema are structured so these drop in without a rewrite. See Path to the App Store.
public/ the playable web client (open index.html)
index.html
styles.css warmth-driven lighting, sheets, animations
js/
engine.js decay / warmth / rituals — pure logic, framework-free
store.js persistence: localStorage OR MongoDB API
home.js SVG home renderer (4 health states per object)
ui.js onboarding, object sheets, rituals, memory wall
sync.js sync mode: auth, invite/join, SSE, server rituals
app.js bootstrap + demo controls (chooses local vs sync)
server/ Node + Express + MongoDB
server.js API + static hosting
db.js mongoose connection
auth.js password hashing + signed tokens
routes.js auth, invite/join, shared home, rituals, SSE presence
engine-core.js server-authoritative decay/warmth/ritual logic
models/index.js PRD schema as Mongoose models
decay.js pg_cron-equivalent decay job
seed.js demo couple + home + objects
scripts/ verification tests (run: npm test)
npm test # auth, engine parity, API routes, SVG render, two-partner sync61 checks across six suites: 16 engine logic (decay rates, restoration, streak, day-30 garden unlock), 6 auth (hashing + tokens), 3 client/server rule parity, 8 API routes, 6 SVG render, and a 22-check two-partner simulation (signup → invite → join → shared rituals → live presence → letters → auth guards).
This web app can't be submitted to the stores directly — that requires Apple and Google developer accounts, code signing, and native build/test devices. The intended route, matching the PRD:
- Wrap this client in Expo (React Native) via a WebView, or port the
screens to React Native and keep
engine.jsas shared logic. - Swap browser storage for Supabase/Mongo + real auth and partner invite.
- Add Expo push, then submit through EAS Build → App Store Connect / Play Console.
The decay/warmth engine, schema, and ritual model here are the parts you'd otherwise rebuild — they carry straight over.