|
| 1 | +# our dates — working notes |
| 2 | + |
| 3 | +A private date planner for couples. Calendar + Google Map + a list, with each |
| 4 | +partner rating a date blind after the fact. Live at |
| 5 | +**https://kjubikstronk.github.io/dateideas/** — Firebase project `dates-8910a`. |
| 6 | + |
| 7 | +Two couples use it today: mck097@gmail.com + alice@dates.local (couple |
| 8 | +`founders`), and milan@obradovic.at + sarah@hutter.at (couple `milan-sarah`). |
| 9 | + |
| 10 | +--- |
| 11 | + |
| 12 | +## The two things that shape everything |
| 13 | + |
| 14 | +**The site is public; the data is not.** GitHub Pages can't restrict access, so |
| 15 | +the deployed page is an empty shell — a login box and nothing else. All privacy |
| 16 | +is enforced by Firestore rules on Google's servers. The Firebase and Maps keys |
| 17 | +are in the bundle deliberately; they're identifiers, not secrets. Sign-up is |
| 18 | +disabled in the console, so nobody can self-register. |
| 19 | + |
| 20 | +**Membership is a document, not a rule.** `members/{uid}` holds one field, |
| 21 | +`coupleId`; every date carries the same field and the rules compare them. |
| 22 | +`firestore.rules` contains no UIDs at all. |
| 23 | + |
| 24 | +To add a person: create the account in Firebase Auth, then create |
| 25 | +`members/{their-uid}` with the right `coupleId`. No code, no rules edit, no |
| 26 | +deploy. An account with no members doc sees "not paired up yet". |
| 27 | + |
| 28 | +--- |
| 29 | + |
| 30 | +## Commands worth knowing |
| 31 | + |
| 32 | +```bash |
| 33 | +npm run rules # deploys firestore.rules from the repo — never paste into the console |
| 34 | +npm run ui # preview mode: sample data, no Firebase, no login |
| 35 | +``` |
| 36 | + |
| 37 | +`.env.local` (keys) and the Firebase MCP need `firebase login` once. |
| 38 | + |
| 39 | +--- |
| 40 | + |
| 41 | +## Gotchas that have already cost us time |
| 42 | + |
| 43 | +**Firestore rejects any query it can't prove is safe.** An unscoped |
| 44 | +`collection(db,'dates')` read is denied outright once the rules scope by |
| 45 | +`coupleId` — not filtered, denied. Client query and rules must agree. This is |
| 46 | +why the couples migration had to ship in two ordered steps. |
| 47 | + |
| 48 | +**Rules take effect instantly; data doesn't migrate itself.** Deploying rules |
| 49 | +that require a field before backfilling that field locks everyone out. Deploy |
| 50 | +the app first, then tighten the rules. |
| 51 | + |
| 52 | +**`Map` from `@vis.gl/react-google-maps` shadows the global `Map`.** It's |
| 53 | +aliased to `GoogleMap` in DateMap.tsx. Don't un-alias it. |
| 54 | + |
| 55 | +**No hooks after an early return.** This has bitten twice. Lint catches it — |
| 56 | +run `npm run lint` before committing. |
| 57 | + |
| 58 | +**`DateCard` is memoised and keyed by id**, so it survives every Firestore |
| 59 | +snapshot. Any local state seeded at mount goes stale when the partner writes. |
| 60 | +Seed on open, not on mount. |
| 61 | + |
| 62 | +**Dates are `yyyy-MM-dd` strings, never Date objects or timestamps.** They sort |
| 63 | +and compare correctly and can't drift by timezone. A date happening *today* is |
| 64 | +not past — use `<`, not `<=`. |
| 65 | + |
| 66 | +**One service worker only.** `vite-plugin-pwa` owns it. Adding Firebase |
| 67 | +messaging later needs `injectManifest` and a single combined worker, not a |
| 68 | +second file at the same scope. |
| 69 | + |
| 70 | +**Places bills by the highest field tier requested.** `rating` is the only |
| 71 | +Enterprise-tier field left and buys just the `4.3 ★` on the map candidate card. |
| 72 | +Detail lookups are cached per session — don't remove that, every map POI tap is |
| 73 | +otherwise billable. |
| 74 | + |
| 75 | +--- |
| 76 | + |
| 77 | +## Design constraints — do not violate |
| 78 | + |
| 79 | +Y2K handheld device: 3px ink borders, hard offset shadows with no blur, |
| 80 | +`steps()` easing only, `border-radius: 0`. |
| 81 | + |
| 82 | +- ink `#1A1033` · paper `#FFE5F1` · card `#FFFDFE` · hot `#FF5CA8` · |
| 83 | + deep `#B31E67` · lavender `#B8A6FF` · aqua `#5BE0E6` · mute `#6B6480` |
| 84 | +- **Hot pink is fills and borders only** — it fails contrast as text. Pink text |
| 85 | + uses deep. Everything must pass WCAG AA. |
| 86 | +- Mobile-first, 375px primary. Touch targets ≥44px. **Inputs ≥16px** or iOS |
| 87 | + zooms the page and won't zoom back. |
| 88 | +- **Emoji, not custom pixel art**, for categories and weather. Decided; my |
| 89 | + hand-drawn set was rejected and rightly so. |
| 90 | +- Google's map colours stay untouched. |
| 91 | +- Agenda entries are flat rows; a card lifts only while being worked on. The |
| 92 | + device bezel keeps the only 6px shadow. |
| 93 | + |
| 94 | +--- |
| 95 | + |
| 96 | +## Verification habits that have paid off |
| 97 | + |
| 98 | +- Probe security live rather than assuming: unauthenticated reads/writes of |
| 99 | + `dates`, `members`, `reports` must all return 403. |
| 100 | +- The in-app browser **cannot composite** — CSS transitions freeze mid-flight, |
| 101 | + `requestAnimationFrame` never fires, and Google Maps tiles never paint. Verify |
| 102 | + logic and inline styles, not appearance. Never schedule anything on rAF alone. |
| 103 | +- Deployed and local bundle hashes differ legitimately (CI uses `npm install`, |
| 104 | + not `npm ci`). Use the Actions status to confirm a deploy, not a hash. |
| 105 | + |
| 106 | +--- |
| 107 | + |
| 108 | +## Open work |
| 109 | + |
| 110 | +**Notifications** — planned, not started. Decided: GitHub Actions cron + FCM |
| 111 | +(free, no Blaze upgrade). Girlfriend has an iPhone 13, so Web Push is viable, |
| 112 | +but on iOS it works **only** from a home-screen install — never in a Safari tab. |
| 113 | +Needs: combined service worker, permission asked after scheduling (not on load), |
| 114 | +tokens on `members/{uid}` behind a narrow rules exception, daily sender, real |
| 115 | +device testing. |
| 116 | + |
| 117 | +**Before any public release** — three product decisions, not bugs: |
| 118 | +1. There is no sign-up flow. Every couple is manual work. |
| 119 | +2. `reports` are readable by all members — one couple's bug reports are visible |
| 120 | + to every other. Fine for people who know each other; a leak with strangers. |
| 121 | +3. The Maps budget is €2. It would be exhausted fast at scale, and the map then |
| 122 | + silently dies for everyone. |
| 123 | + |
| 124 | +Also note: this is a **web app**. "Release on mobile" means Add to Home Screen. |
| 125 | +A store listing would need Capacitor-style wrapping, review, and Apple's |
| 126 | +$99/year. |
| 127 | + |
| 128 | +**Dropped deliberately** — couple nameplate, "go again" duplicate button, |
| 129 | +inline day-picking on someday cards, calendar `.ics` subscription, JSON export, |
| 130 | +surprise-me dice, custom pixel stickers, photos on memories. |
| 131 | + |
| 132 | +--- |
| 133 | + |
| 134 | +## Working style that suits this project |
| 135 | + |
| 136 | +The owner is watching a weekly token budget. Prefer Sonnet for routine work and |
| 137 | +save Opus for security rules, race conditions and genuine debugging. Be |
| 138 | +economical, verify rather than assume, and say plainly when something is |
| 139 | +unverified. |
0 commit comments