chore: update readme
Some checks failed
Checks / lint (push) Has been cancelled
Checks / test (push) Has been cancelled
Checks / content (push) Has been cancelled

This commit is contained in:
ayo 2026-07-18 22:26:19 +02:00
parent 93e8258270
commit d5109b8dc9
6 changed files with 257 additions and 82 deletions

View file

@ -91,7 +91,8 @@ pnpm run db:stop # stop a stray emulator holding :8080
``` ```
Deploy scripts (`deploy:db`, `deploy:site`) also exist but require project Deploy scripts (`deploy:db`, `deploy:site`) also exist but require project
credentials, so they're for maintainers. See [README.md](README.md) and credentials, so they're for maintainers — the first-time setup is documented in
[`docs/deployment.md`](docs/deployment.md). See [README.md](README.md) and
[`docs/`](docs/) for the full backend reference. [`docs/`](docs/) for the full backend reference.
## Project structure ## Project structure

View file

@ -14,7 +14,6 @@ The goal is to reveal every safe cell without detonating a mine. Your **first cl
- **Left + right click together** (chording) — reveal the neighbors of a satisfied number - **Left + right click together** (chording) — reveal the neighbors of a satisfied number
- **Touch** — tap to reveal, long-press to flag - **Touch** — tap to reveal, long-press to flag
## Ways to Use ## Ways to Use
The web is a wonderful, free, and open platform to create and distribute value. You can use **mnswpr** in different ways: The web is a wonderful, free, and open platform to create and distribute value. You can use **mnswpr** in different ways:
@ -44,7 +43,7 @@ As of now the tooling I use are:
- [PNPM](https://pnpm.io/installation) for dependency management - [PNPM](https://pnpm.io/installation) for dependency management
- and a bunch of automation using scripts and Continuous Integration actions - and a bunch of automation using scripts and Continuous Integration actions
Because a big part of this project's purpose is to track how the software development industry evolves — and because it has come a long way in modernizing along the way — I now also use it as a **playground for coding agents**. It's a small, framework-free, well-scoped codebase, which makes it a great sandbox to see how AI agents read, reason about, and change real code. To help them get their bearings quickly, the repo ships an [`AGENTS.md`](./AGENTS.md) describing the architecture and conventions. Because a big part of this project's purpose is to track how the software development industry evolves, I now also use it as a **playground for coding agents**.
## Development ## Development
@ -67,64 +66,10 @@ pnpm run preview # serve the production build
The game engine and leaderboard are consumed from npm — they live in The game engine and leaderboard are consumed from npm — they live in
[ayo-run/cozy-games](https://github.com/ayo-run/cozy-games), not here. [ayo-run/cozy-games](https://github.com/ayo-run/cozy-games), not here.
### Leaderboard (local Firestore emulator)
The leader board is backed by [Google Firestore](https://firebase.google.com). For local development the app talks to the **Firestore emulator** by default — fully local, no cloud, no deploy. The flag `VITE_FIRESTORE_EMULATOR=1` is already set in `.env.development`.
`dev` is the whole loop in one command — it wraps Vite in `firebase emulators:exec`, so the Firestore emulator (on :8080, + UI) comes up, gets **seeded with sample scores automatically**, and the app dev server starts against it; everything shuts down when you stop it. (Each `dev` starts a fresh in-memory emulator, so the auto-seed writes exactly one clean set every time — no accumulation.) `firebase-tools` is a pinned **devDependency** of this app (installed by `pnpm install`, invoked as the `firebase` binary), so the only extra prerequisite is **Java** — the Firestore emulator is a Java program (`java -jar cloud-firestore-emulator-*.jar`), and firebase-tools does not bundle a JRE.
**Usually automatic.** `pnpm install` runs a root `postinstall` ([`scripts/ensure-java.mjs`](../../scripts/ensure-java.mjs)) that installs a user-local Temurin JRE 21 into `~/.local` (no `sudo`) when `java` isn't already on your PATH. It's idempotent and never fails the install, and it skips when `CI` or `SKIP_JRE_SETUP=1` is set, or on unsupported platforms.
If that skipped and you need Java (or prefer a system-wide install), do it manually — **install a JRE (Java 11+; 21 recommended):**
```bash
# Debian / Ubuntu / Pop!_OS
sudo apt update && sudo apt install -y openjdk-21-jre-headless
# Fedora
sudo dnf install -y java-21-openjdk-headless
# macOS (Homebrew)
brew install openjdk@21
java -version # verify: should print "openjdk 21.x" (or 11+)
```
No `sudo`? Install a JRE into your home directory instead (no root needed):
```bash
curl -fsSL -o /tmp/jre21.tgz "https://api.adoptium.net/v3/binary/latest/21/ga/linux/x64/jre/hotspot/normal/eclipse"
mkdir -p ~/.local/lib && tar xzf /tmp/jre21.tgz -C ~/.local/lib
ln -sf ~/.local/lib/jdk-21*-jre/bin/java ~/.local/bin/java # ~/.local/bin is already on PATH
java -version
```
Without Java, `dev` and `db:start` fail with `Could not spawn 'java -version'`. Install it to a permanent location — a JRE unpacked under `/tmp` disappears when the OS cleans temp files. Then just:
```bash
pnpm run dev # emulator (:8080 + UI) + auto-seed + app dev server — one command
```
That's the everyday loop. The other DB scripts are for when you want to run pieces separately:
```bash
pnpm run db:start # emulator only (stays up across app restarts); pair with dev:no-db
pnpm run db:seed # seed a separately-running emulator (what dev does for you)
pnpm run db:stop # kill a stray/orphaned emulator holding :8080
```
To skip the emulator entirely — for quick UI-only work, or if you don't have a JDK — run `pnpm run dev:no-db` (plain Vite) and set `VITE_FIRESTORE_EMULATOR=` (empty) in a local, gitignored `.env.local`; the app then uses the cloud `mw-test` namespace instead.
See [`docs/firebase-leaderboards.md`](./docs/firebase-leaderboards.md) for the full data model, security rules, environments, and deployment.
## Contributing ## Contributing
Contributions are welcome! See [`AGENTS.md`](./AGENTS.md) for the architecture, conventions, and release workflow before opening a pull request. Contributions are welcome! See [`AGENTS.md`](./AGENTS.md) for the architecture, conventions, and release workflow before opening a pull request.
## You just want to play?
*👉 The live site is here: [mnswpr.com](https://mnswpr.com)*
## Background ## Background
One day, while working in my home office, I heard loud and fast mouse clicks coming from our bedroom. It's my wife, playing her favorite game (Minesweeper) on a crappy website full of advertisements. One day, while working in my home office, I heard loud and fast mouse clicks coming from our bedroom. It's my wife, playing her favorite game (Minesweeper) on a crappy website full of advertisements.
@ -144,11 +89,9 @@ Can I make a page with complex interactions (more on this later) without any lib
1. Competition motivates users to use your app more ✨ 1. Competition motivates users to use your app more ✨
1. Hash in bundled filenames helps avoid issues with browser caching (when shipping versions fast) ✨ 1. Hash in bundled filenames helps avoid issues with browser caching (when shipping versions fast) ✨
## License ## License
[MIT](../../LICENSE) [MIT](./LICENSE)
--- ---

175
docs/deployment.md Normal file
View file

@ -0,0 +1,175 @@
# First-time deployment
Everything needed to take this repo from a fresh clone to a live deploy of
[mnswpr.com](https://mnswpr.com). Once it's set up you won't need this again —
day to day, `pnpm release` is the whole story.
Two services are involved: **Netlify** hosts the site, **Firebase** (Firestore)
stores the leaderboard. Both are driven entirely from the CLI; nothing here
requires a web dashboard except one Netlify setting noted below.
For the conceptual split between infra *config* (committed, declarative) and
infra *tools* (pinned devDependencies), see AGENTS.md → "Infra".
---
## 0. Prerequisites
```bash
pnpm i # installs firebase-tools and netlify-cli as pinned devDependencies
```
Both CLIs are invoked through `pnpm exec`, so you never need a global install.
---
## 1. Netlify
### Log in and link the repo
```bash
pnpm exec netlify login
pnpm exec netlify link # binds this directory to the site; writes .netlify/ (gitignored)
```
If you know the site ID you can skip the prompts:
```bash
pnpm exec netlify link --id <site-id>
```
Losing `.netlify/` is harmless — it's local state. Re-run `netlify link`.
### Set the base directory
The site's **base directory must be the repo root**. This is the one setting
that isn't in a config file — it lives on the site itself (Site configuration →
Build & deploy → Base directory) and must be **empty**.
If it points at a subdirectory that doesn't exist, git-triggered builds fail
before they start.
### Set the environment variables
This is the step most likely to be missed, because it only affects one of the
two deploy paths:
- **`pnpm run deploy:site`** builds *locally* and uploads `dist/`. It reads your
local, gitignored `.env.production` — Netlify's env vars are never consulted.
- **`pnpm release`** force-pushes the `release` branch, and **Netlify builds it
from git**. That build runs on Netlify's machines with no access to any local
file, so it needs the variables set on the site.
A site that deploys fine via `deploy:site` can still ship a broken build from
git if these are missing — the Firebase config comes out `undefined` and the
leaderboard fails for every visitor.
The eight values are the same ones committed in `.env.development`: `prod` and
`dev` are the *same* Firebase project (see `firebase/.firebaserc`), separated
only by collection namespace. So you can set them straight from that file:
```bash
grep -E '^VITE_FIREBASE_' .env.development | while IFS='=' read -r k v; do
pnpm exec netlify env:set "$k" "$v"
done
```
That covers:
```
VITE_FIREBASE_API_KEY VITE_FIREBASE_STORAGE_BUCKET
VITE_FIREBASE_AUTH_DOMAIN VITE_FIREBASE_MESSAGING_SENDER_ID
VITE_FIREBASE_DATABASE_URL VITE_FIREBASE_APP_ID
VITE_FIREBASE_PROJECT_ID VITE_FIREBASE_MEASUREMENT_ID
```
Two variables are deliberately **not** in that list:
- **`VITE_LB_NAMESPACE`** is already set to `mw` in `netlify.toml`, so it ships
with the repo. (`src/main.js` defaults to `mw-test` when it's missing, so a
misconfigured build writes to the test board, never production.)
- **`VITE_FIRESTORE_EMULATOR`** must stay **absent or empty** in production.
`src/main.js` treats `1`/`true`/`yes` as "connect to a local emulator", so if
it leaks into the site's environment the live leaderboard silently tries to
reach `127.0.0.1:8080` and fails for everyone. It belongs only in
`.env.development`.
Verify what's actually set:
```bash
pnpm exec netlify env:list
```
> **These are not secrets.** Firebase Web API keys are public by design — they
> identify the project, they don't authorize anything. Access is governed by
> `firebase/firestore.rules`. They're kept out of git as prod hygiene, not
> because exposure is dangerous; the dev values are committed for this reason.
---
## 2. Firebase
### Log in
```bash
pnpm exec firebase login
```
### Deploy the security rules
**Do this before the first real release.** `firebase/firestore.rules` is the
source of truth for who can read and write the leaderboard, but it only takes
effect once deployed — a project can be running rules set by hand in the console
long ago, which won't match the repo.
```bash
pnpm run deploy:db # firebase --config firebase/firebase.json deploy --only firestore
```
That deploys rules *and* indexes. Note that `prod` and `dev` are the same
Firebase project, so a rules deploy affects both environments at once.
If the rules are stale, leaderboard writes fail with permission-denied even
though the site loads fine — worth confirming after the first deploy by
finishing a game and checking the score appears.
---
## 3. Deploy
Two paths, both supported:
```bash
pnpm run deploy:site # build locally, upload dist/ via the Netlify CLI
```
or the full release, which is what tags a version and triggers the git build:
```bash
pnpm release # lint + test, then bumpp, then sync the release branch
```
`pnpm release` runs `scripts/release.js`, which force-pushes `main` to the
`release` branch on the `gh` remote. **That push is the deploy** — Netlify
watches that branch. It refuses to run from a branch other than `main` or with a
dirty working tree.
---
## Verifying a first deploy
1. `pnpm exec netlify env:list` shows all eight `VITE_FIREBASE_*` and no
`VITE_FIRESTORE_EMULATOR`.
2. The site loads and the version in the heading matches `package.json`.
3. Finish a game — your score appears on the board. This is the real end-to-end
check: it exercises the env vars, the rules, and the namespace together.
4. The leaderboard shows the `mw` namespace, not `mw-test`.
## Related
- [`firebase-leaderboards.md`](./firebase-leaderboards.md) — data model, rules,
environments, deployment details
- [`firestore-emulator.md`](./firestore-emulator.md) — running the leaderboard
locally
- [`leaderboard-env-migration.md`](./leaderboard-env-migration.md) — history of
the env-var/namespace migration

View file

@ -6,9 +6,9 @@ still has to be touched in the Firebase Console.
## Overview ## Overview
Leaderboards are powered by Firestore (`firebase/firestore/lite`) through the Leaderboards are powered by Firestore (`firebase/firestore/lite`) through the
reusable, game-agnostic [`@cozy-games/leaderboard`](../leaderboard/leader-board.js) reusable, game-agnostic [`@cozy-games/leaderboard`](https://github.com/ayo-run/cozy-games)
package. The app wires minesweeper's specifics (finish-time as the `score`, package. The app wires minesweeper's specifics (finish-time as the `score`,
ascending sort, time formatting) in [`app/main.js`](../app/main.js). ascending sort, time formatting) in [`app/main.js`](../src/main.js).
The board offers four time windows — **Today** (default), **Week**, **Month**, The board offers four time windows — **Today** (default), **Week**, **Month**,
**All Time** — selected by tabs. Each played game that qualifies is written to a **All Time** — selected by tabs. Each played game that qualifies is written to a
@ -64,11 +64,11 @@ competition, not a boundary quirk.
The Firestore schema (security rules + indexes) lives in the repo: The Firestore schema (security rules + indexes) lives in the repo:
- [`firebase.json`](../firebase.json) — points at the rules and indexes files. - [`firebase.json`](../firebase/firebase.json) — points at the rules and indexes files.
- [`.firebaserc`](../.firebaserc) — project aliases: `prod` (default, live site) - [`.firebaserc`](../firebase/.firebaserc) — project aliases: `prod` (default, live site)
and `dev` (`secure-moment-188701`). and `dev` (`secure-moment-188701`).
- [`firestore.rules`](../firestore.rules) — access + validation rules. - [`firestore.rules`](../firebase/firestore.rules) — access + validation rules.
- [`firestore.indexes.json`](../firestore.indexes.json) — **empty**: rolling - [`firestore.indexes.json`](../firebase/firestore.indexes.json) — **empty**: rolling
windows (`time_stamp >=`) and all-time (`orderBy('score')`) use Firestore's windows (`time_stamp >=`) and all-time (`orderBy('score')`) use Firestore's
automatic single-field indexes, so no composite indexes are needed. automatic single-field indexes, so no composite indexes are needed.
@ -76,7 +76,7 @@ The Firestore schema (security rules + indexes) lives in the repo:
There is **one Firebase project** (`secure-moment-188701`). Production and test There is **one Firebase project** (`secure-moment-188701`). Production and test
data are separated not by project but by **collection namespace**, chosen with data are separated not by project but by **collection namespace**, chosen with
the `VITE_LB_NAMESPACE` env var read in [`app/main.js`](../app/main.js): the `VITE_LB_NAMESPACE` env var read in [`app/main.js`](../src/main.js):
| Environment | `VITE_LB_NAMESPACE` | Collections | | Environment | `VITE_LB_NAMESPACE` | Collections |
| --- | --- | --- | | --- | --- | --- |
@ -88,7 +88,7 @@ project — so the only difference is the namespace. `app/main.js` defaults to t
**test** namespace, so a missing/misconfigured var can never write into the **test** namespace, so a missing/misconfigured var can never write into the
production board; production must set `VITE_LB_NAMESPACE=mw` explicitly. production board; production must set `VITE_LB_NAMESPACE=mw` explicitly.
Dev config lives in the committed [`app/.env.development`](../app/.env.development) Dev config lives in the committed [`app/.env.development`](../.env.development)
(the keys are public). Production sets `VITE_FIREBASE_*` **and** (the keys are public). Production sets `VITE_FIREBASE_*` **and**
`VITE_LB_NAMESPACE=mw` as Netlify environment variables. `.env.production` and `VITE_LB_NAMESPACE=mw` as Netlify environment variables. `.env.production` and
`.env*.local` stay gitignored. `.env*.local` stay gitignored.
@ -121,7 +121,7 @@ pnpm exec firebase deploy --only firestore:rules,firestore:indexes --project dev
``` ```
> ⚠️ Deploying **replaces** whatever rules currently live in the Console. The > ⚠️ Deploying **replaces** whatever rules currently live in the Console. The
> committed [`firestore.rules`](../firestore.rules) is written to cover every > committed [`firestore.rules`](../firebase/firestore.rules) is written to cover every
> collection the app uses, so a deploy is safe — but review it first. > collection the app uses, so a deploy is safe — but review it first.
No composite indexes are required — the rolling-window and all-time queries use No composite indexes are required — the rolling-window and all-time queries use
@ -131,9 +131,9 @@ Firestore's automatic single-field indexes.
Local development runs against the **Firebase Local Emulator Suite** by default — Local development runs against the **Firebase Local Emulator Suite** by default —
no cloud, no deploy, no auth, no `permission-denied` — and it loads the committed no cloud, no deploy, no auth, no `permission-denied` — and it loads the committed
[`firestore.rules`](../firestore.rules) and [`firestore.indexes.json`](../firestore.indexes.json) [`firestore.rules`](../firebase/firestore.rules) and [`firestore.indexes.json`](../firebase/firestore.indexes.json)
locally (so you validate them before deploying). The flag locally (so you validate them before deploying). The flag
`VITE_FIRESTORE_EMULATOR=1` is set in [`app/.env.development`](../app/.env.development). `VITE_FIRESTORE_EMULATOR=1` is set in [`app/.env.development`](../.env.development).
> Prerequisite: the Firestore emulator is a Java process, so you need a JDK > Prerequisite: the Firestore emulator is a Java process, so you need a JDK
> (11+) installed. `firebase-tools` is a pinned devDependency of this app > (11+) installed. `firebase-tools` is a pinned devDependency of this app
@ -166,7 +166,7 @@ Everything above is doable via the CLI. If you can't use it:
(If you ever add a query that needs a composite index, Firestore prints a (If you ever add a query that needs a composite index, Firestore prints a
console error with a direct link to create it.) console error with a direct link to create it.)
- **Rules**: edit them directly under **Firestore → Rules** in the Console - **Rules**: edit them directly under **Firestore → Rules** in the Console
(paste from [`firestore.rules`](../firestore.rules)). (paste from [`firestore.rules`](../firebase/firestore.rules)).
- **Server config**: `mw-config/configuration` (`passingStatus`, `message`) is - **Server config**: `mw-config/configuration` (`passingStatus`, `message`) is
**always** managed by hand in the Console — it is read-only to clients and has **always** managed by hand in the Console — it is read-only to clients and has
no code representation. `passingStatus` is the `status` value that makes a game no code representation. `passingStatus` is the `status` value that makes a game
@ -175,7 +175,7 @@ Everything above is doable via the CLI. If you can't use it:
## Legends (frozen hall of fame) ## Legends (frozen hall of fame)
The old all-time leaders are preserved as a **fully-rendered static page** The old all-time leaders are preserved as a **fully-rendered static page**
[`legends.html`](../legends.html). The records are baked straight into the [`legends.html`](../src/legends.html). The records are baked straight into the
HTML with times pre-formatted; there is **no JavaScript and no Firebase** at page HTML with times pre-formatted; there is **no JavaScript and no Firebase** at page
load. The data never changes. load. The data never changes.
@ -209,7 +209,7 @@ new LeaderBoardService({
To run on **Supabase** instead, swap in `SupabaseAdapter` — nothing else in the To run on **Supabase** instead, swap in `SupabaseAdapter` — nothing else in the
game changes. The adapter interface and the Supabase table/SQL schema are game changes. The adapter interface and the Supabase table/SQL schema are
documented in [`leaderboard/README.md`](../leaderboard/README.md). documented in the [@cozy-games/leaderboard README](https://github.com/ayo-run/cozy-games).
Then submit `{ name, playerId, score, category, time_stamp, status?, meta? }` Then submit `{ name, playerId, score, category, time_stamp, status?, meta? }`
and render with `render(category, title, duration)`. and render with `render(category, title, duration)`.

View file

@ -0,0 +1,56 @@
# Leaderboard: the local Firestore emulator
How to run the leaderboard locally. For the data model, security rules,
environments, and deployment, see [`firebase-leaderboards.md`](./firebase-leaderboards.md).
The leader board is backed by [Google Firestore](https://firebase.google.com). For local development the app talks to the **Firestore emulator** by default — fully local, no cloud, no deploy. The flag `VITE_FIRESTORE_EMULATOR=1` is already set in `.env.development`.
`dev` is the whole loop in one command — it wraps Vite in `firebase emulators:exec`, so the Firestore emulator (on :8080, + UI) comes up, gets **seeded with sample scores automatically**, and the app dev server starts against it; everything shuts down when you stop it. (Each `dev` starts a fresh in-memory emulator, so the auto-seed writes exactly one clean set every time — no accumulation.) `firebase-tools` is a pinned **devDependency** of this app (installed by `pnpm install`, invoked as the `firebase` binary), so the only extra prerequisite is **Java** — the Firestore emulator is a Java program (`java -jar cloud-firestore-emulator-*.jar`), and firebase-tools does not bundle a JRE.
## Java
**Usually automatic.** `pnpm install` runs a root `postinstall` ([`scripts/ensure-java.mjs`](../scripts/ensure-java.mjs)) that installs a user-local Temurin JRE 21 into `~/.local` (no `sudo`) when `java` isn't already on your PATH. It's idempotent and never fails the install, and it skips when `CI` or `SKIP_JRE_SETUP=1` is set, or on unsupported platforms.
If that skipped and you need Java (or prefer a system-wide install), do it manually — **install a JRE (Java 11+; 21 recommended):**
```bash
# Debian / Ubuntu / Pop!_OS
sudo apt update && sudo apt install -y openjdk-21-jre-headless
# Fedora
sudo dnf install -y java-21-openjdk-headless
# macOS (Homebrew)
brew install openjdk@21
java -version # verify: should print "openjdk 21.x" (or 11+)
```
No `sudo`? Install a JRE into your home directory instead (no root needed):
```bash
curl -fsSL -o /tmp/jre21.tgz "https://api.adoptium.net/v3/binary/latest/21/ga/linux/x64/jre/hotspot/normal/eclipse"
mkdir -p ~/.local/lib && tar xzf /tmp/jre21.tgz -C ~/.local/lib
ln -sf ~/.local/lib/jdk-21*-jre/bin/java ~/.local/bin/java # ~/.local/bin is already on PATH
java -version
```
Without Java, `dev` and `db:start` fail with `Could not spawn 'java -version'`. Install it to a permanent location — a JRE unpacked under `/tmp` disappears when the OS cleans temp files.
## Running it
```bash
pnpm run dev # emulator (:8080 + UI) + auto-seed + app dev server — one command
```
That's the everyday loop. The other DB scripts are for when you want to run pieces separately:
```bash
pnpm run db:start # emulator only (stays up across app restarts); pair with dev:no-db
pnpm run db:seed # seed a separately-running emulator (what dev does for you)
pnpm run db:stop # kill a stray/orphaned emulator holding :8080
```
## Skipping the emulator
To skip it entirely — for quick UI-only work, or if you don't have a JDK — run `pnpm run dev:no-db` (plain Vite) and set `VITE_FIRESTORE_EMULATOR=` (empty) in a local, gitignored `.env.local`; the app then uses the cloud `mw-test` namespace instead.

View file

@ -7,7 +7,7 @@ Every environment — a developer running `pnpm dev`, a Netlify preview, and the
live site — reads and writes the same collections (`mw-scores`, `mw-all`, live site — reads and writes the same collections (`mw-scores`, `mw-all`,
`mw-config`, legacy `mw-leaders`) in the single Firebase project `mw-config`, legacy `mw-leaders`) in the single Firebase project
`secure-moment-188701`. `version`/`import.meta.env.MODE` only affects UI text `secure-moment-188701`. `version`/`import.meta.env.MODE` only affects UI text
([lib/mnswpr.js:96](../lib/mnswpr.js:96)); it never changes collection names. So (in the [@cozy-games/mnswpr engine](https://github.com/ayo-run/cozy-games)); it never changes collection names. So
local play pollutes the production leaderboard. local play pollutes the production leaderboard.
We keep **one Firebase project** (there is no separate prod project — see We keep **one Firebase project** (there is no separate prod project — see
@ -27,7 +27,7 @@ Existing production data under `mw-*` is untouched; `mw-test-*` starts empty.
| Piece | Now | After | | Piece | Now | After |
| --- | --- | --- | | --- | --- | --- |
| Namespace | hardcoded `'mw'` in [app/main.js](../app/main.js) | `import.meta.env.VITE_LB_NAMESPACE` | | Namespace | hardcoded `'mw'` in [app/main.js](../src/main.js) | `import.meta.env.VITE_LB_NAMESPACE` |
| Dev writes | into prod `mw-*` | into `mw-test-*` | | Dev writes | into prod `mw-*` | into `mw-test-*` |
| Rules | only `mw-*` matches | any `mw` / `mw-test` namespace | | Rules | only `mw-*` matches | any `mw` / `mw-test` namespace |
| Indexes | `games` collection group | unchanged (already covers all namespaces) | | Indexes | `games` collection group | unchanged (already covers all namespaces) |
@ -37,14 +37,14 @@ Existing production data under `mw-*` is untouched; `mw-test-*` starts empty.
### 1. Add the namespace env var ### 1. Add the namespace env var
- [app/.env.development](../app/.env.development): add `VITE_LB_NAMESPACE=mw-test` - [app/.env.development](../.env.development): add `VITE_LB_NAMESPACE=mw-test`
- [app/.env.example](../app/.env.example): document `VITE_LB_NAMESPACE` - [app/.env.example](../.env.example): document `VITE_LB_NAMESPACE`
- Netlify (production build): set `VITE_LB_NAMESPACE=mw` **and** the same - Netlify (production build): set `VITE_LB_NAMESPACE=mw` **and** the same
`VITE_FIREBASE_*` values as dev (same project). `VITE_FIREBASE_*` values as dev (same project).
### 2. Read it in the app ### 2. Read it in the app
In [app/main.js](../app/main.js), replace the hardcoded namespace. Default to the In [app/main.js](../src/main.js), replace the hardcoded namespace. Default to the
**test** namespace so a missing/misconfigured var can never write to production: **test** namespace so a missing/misconfigured var can never write to production:
```js ```js
@ -59,7 +59,7 @@ of a missing var is an empty test board, never prod pollution.)
### 3. Generalize the security rules ### 3. Generalize the security rules
Rewrite [firestore.rules](../firestore.rules) so a rule matches by the namespace Rewrite [firestore.rules](../firebase/firestore.rules) so a rule matches by the namespace
*suffix* instead of a literal `mw-` prefix. Firestore ORs all matching rules, so *suffix* instead of a literal `mw-` prefix. Firestore ORs all matching rules, so
one set of generic blocks covers `mw`, `mw-test`, and any future namespace. one set of generic blocks covers `mw`, `mw-test`, and any future namespace.
Note: `{ns}-scores/{cat}/games/{id}`, `{ns}-all/{id}/games/{s}` and Note: `{ns}-scores/{cat}/games/{id}`, `{ns}-all/{id}/games/{s}` and
@ -117,7 +117,7 @@ The regex `mw(-[a-z]+)?-scores` matches `mw-scores` and `mw-test-scores`
Time windows are rolling (`time_stamp >=`) and all-time sorts by `score`, so the Time windows are rolling (`time_stamp >=`) and all-time sorts by `score`, so the
queries use Firestore's automatic single-field indexes. queries use Firestore's automatic single-field indexes.
[firestore.indexes.json](../firestore.indexes.json) is empty — nothing to add for [firestore.indexes.json](../firebase/firestore.indexes.json) is empty — nothing to add for
any namespace. any namespace.
### 5. Seed the test config doc ### 5. Seed the test config doc
@ -155,6 +155,6 @@ Both aliases target the one project we actually have:
## Rollback ## Rollback
Set `VITE_LB_NAMESPACE=mw` everywhere (or revert [app/main.js](../app/main.js) to Set `VITE_LB_NAMESPACE=mw` everywhere (or revert [app/main.js](../src/main.js) to
the hardcoded `'mw'`) to return to shared collections. The generalized rules are the hardcoded `'mw'`) to return to shared collections. The generalized rules are
a superset of the old ones, so they stay valid either way. a superset of the old ones, so they stay valid either way.