From d5109b8dc97b5603fe338c4fd732aa39e88ceaa7 Mon Sep 17 00:00:00 2001 From: Ayo Date: Sat, 18 Jul 2026 22:26:19 +0200 Subject: [PATCH] chore: update readme --- CONTRIBUTING.md | 3 +- README.md | 61 +---------- docs/deployment.md | 175 ++++++++++++++++++++++++++++++ docs/firebase-leaderboards.md | 28 ++--- docs/firestore-emulator.md | 56 ++++++++++ docs/leaderboard-env-migration.md | 16 +-- 6 files changed, 257 insertions(+), 82 deletions(-) create mode 100644 docs/deployment.md create mode 100644 docs/firestore-emulator.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 15681d0..1a426e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 -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. ## Project structure diff --git a/README.md b/README.md index 774a40c..924cf35 100644 --- a/README.md +++ b/README.md @@ -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 - **Touch** — tap to reveal, long-press to flag - ## Ways to Use 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 - 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 @@ -67,64 +66,10 @@ pnpm run preview # serve the production build 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. -### 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 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 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. Hash in bundled filenames helps avoid issues with browser caching (when shipping versions fast) ✨ - - ## License -[MIT](../../LICENSE) +[MIT](./LICENSE) --- diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..d8441dc --- /dev/null +++ b/docs/deployment.md @@ -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 +``` + +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 diff --git a/docs/firebase-leaderboards.md b/docs/firebase-leaderboards.md index aa331ea..2a93a04 100644 --- a/docs/firebase-leaderboards.md +++ b/docs/firebase-leaderboards.md @@ -6,9 +6,9 @@ still has to be touched in the Firebase Console. ## Overview 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`, -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**, **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: -- [`firebase.json`](../firebase.json) — points at the rules and indexes files. -- [`.firebaserc`](../.firebaserc) — project aliases: `prod` (default, live site) +- [`firebase.json`](../firebase/firebase.json) — points at the rules and indexes files. +- [`.firebaserc`](../firebase/.firebaserc) — project aliases: `prod` (default, live site) and `dev` (`secure-moment-188701`). -- [`firestore.rules`](../firestore.rules) — access + validation rules. -- [`firestore.indexes.json`](../firestore.indexes.json) — **empty**: rolling +- [`firestore.rules`](../firebase/firestore.rules) — access + validation rules. +- [`firestore.indexes.json`](../firebase/firestore.indexes.json) — **empty**: rolling windows (`time_stamp >=`) and all-time (`orderBy('score')`) use Firestore's 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 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 | | --- | --- | --- | @@ -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 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** `VITE_LB_NAMESPACE=mw` as Netlify environment variables. `.env.production` and `.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 -> 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. 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 — 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 -`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 > (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 console error with a direct link to create it.) - **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 **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 @@ -175,7 +175,7 @@ Everything above is doable via the CLI. If you can't use it: ## Legends (frozen hall of fame) 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 load. The data never changes. @@ -209,7 +209,7 @@ new LeaderBoardService({ To run on **Supabase** instead, swap in `SupabaseAdapter` — nothing else in the 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? }` and render with `render(category, title, duration)`. diff --git a/docs/firestore-emulator.md b/docs/firestore-emulator.md new file mode 100644 index 0000000..0d9d1b6 --- /dev/null +++ b/docs/firestore-emulator.md @@ -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. diff --git a/docs/leaderboard-env-migration.md b/docs/leaderboard-env-migration.md index f16f567..d0bbbd3 100644 --- a/docs/leaderboard-env-migration.md +++ b/docs/leaderboard-env-migration.md @@ -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`, `mw-config`, legacy `mw-leaders`) in the single Firebase project `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. 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 | | --- | --- | --- | -| 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-*` | | Rules | only `mw-*` matches | any `mw` / `mw-test` namespace | | 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 -- [app/.env.development](../app/.env.development): add `VITE_LB_NAMESPACE=mw-test` -- [app/.env.example](../app/.env.example): document `VITE_LB_NAMESPACE` +- [app/.env.development](../.env.development): add `VITE_LB_NAMESPACE=mw-test` +- [app/.env.example](../.env.example): document `VITE_LB_NAMESPACE` - Netlify (production build): set `VITE_LB_NAMESPACE=mw` **and** the same `VITE_FIREBASE_*` values as dev (same project). ### 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: ```js @@ -59,7 +59,7 @@ of a missing var is an empty test board, never prod pollution.) ### 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 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 @@ -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 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. ### 5. Seed the test config doc @@ -155,6 +155,6 @@ Both aliases target the one project we actually have: ## 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 a superset of the old ones, so they stay valid either way.