Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
8a354a2
Drop tslint/protractor/clang-format tooling, bump Functions to Node 20
seanmadden Apr 22, 2026
3916b60
Bump package.json + build config to Angular 19.2 stack
seanmadden Apr 22, 2026
27f115f
Migrate to modular Firebase SDK, chart.js 4, rxjs 7, drop ionic-native
seanmadden Apr 22, 2026
c0d2dc7
src/test.ts: zone.js/dist/zone-testing -> zone.js/testing
seanmadden Apr 22, 2026
5aca0de
Make local dev work against vetted-registry mirror; isolate from prod
seanmadden May 18, 2026
cdb7b7f
Add ESLint, bump functions to firebase-admin 13 / firebase-functions …
seanmadden May 18, 2026
8851c01
Resolve all ESLint warnings (catch/import/destructure cleanup)
seanmadden May 18, 2026
2c25730
README: document new dev workflow (npm start, local-only default, reg…
seanmadden May 18, 2026
7892808
Add CLAUDE.md onboarding doc
seanmadden May 18, 2026
1764d73
Migrate Cloud Functions to firebase-functions v2 API
seanmadden May 18, 2026
cda20bb
Unblock local emulators via scoped path-to-regexp override
seanmadden May 18, 2026
c2bd9a0
Bump frontend to Angular 20.3
seanmadden May 19, 2026
263e67d
Bump firebase-tools 14.3 → 15.13
seanmadden May 19, 2026
a83606c
Remove lodash from Cloud Functions
seanmadden May 19, 2026
4d9875f
Drop stale superstatic@9 override; let firebase-tools 15 use supersta…
seanmadden May 19, 2026
88817b8
Bump root TypeScript 5.8 → 5.9 to match functions/
seanmadden May 19, 2026
1efcfd4
Add angular-eslint template linting + fix lifecycle interface lint
seanmadden May 19, 2026
b70c6e2
CLAUDE.md: reflect angular-eslint done, capture inject() as remaining…
seanmadden May 19, 2026
1297dd2
Prune 6 dead overrides
seanmadden May 19, 2026
366f790
Shrink overrides block 85 → 9 by vetting transitives via greenflagged
seanmadden May 19, 2026
d9accf5
CLAUDE.md: rewrite overrides guidance to match trimmed block
seanmadden May 19, 2026
835f3f7
Merge branch 'upstream/master' into modernize-dependencies
seanmadden May 19, 2026
0bef3a0
firestore.rules: tighten writes — block self-promote to Admin, gate g…
seanmadden May 19, 2026
8714fe4
Declare Node 20.19.0 in .tool-versions and package.json engines
seanmadden May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,20 @@ npm-debug.log*
/plugins
/www
*.cache

# Angular build cache
/.angular

# Firestore emulator local state (when emulators are used)
/data
/firebase-debug.log
/firestore-debug.log
/ui-debug.log

# Local scratchpads (not part of project source)
/404s.txt
/greenflagged-*.md
/greenflagged-*.txt

# Empty project-local MCP config — don't commit
/.mcp.json
3 changes: 2 additions & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
registry=https://registry.npmjs.com
registry=http://greenflagged.dev/npm
legacy-peer-deps=true
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1 +1 @@
nodejs 16.18.1
nodejs 20.19.0
165 changes: 165 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
# CLAUDE.md

Notes for agents working in this repo. Skip the parts that don't apply to your task.

## What this repo is

CodeTango — a Codenames-style web game. Ionic + Angular frontend, Firebase
Auth/Firestore/Functions backend, deployed at https://codetango.web.app/.
Originally a hackathon project, still actively developed.

- Frontend: `src/` (Angular 20.3, Ionic 8, RxJS 7, chart.js 4)
- Cloud Functions: `functions/src/` (Firebase Functions 7 v2 API, Node 20)
- Shared types: `types.ts` at repo root (imported as bare `'types'` via
tsconfig path mapping)

## Vetted-package registry — read this before any `npm install`

The org installs packages from a vetted-package mirror, not public npm:

```
# .npmrc
registry=https://greenflagged.dev/npm
legacy-peer-deps=true
```

Two things follow from this:

1. **The `overrides` block is small and principled — keep it that way.**
`package.json` carries 1 scoped + 9 flat overrides. Each one is there
for an explicit reason: a global CVE forward-pin
(`path-to-regexp: 6.3.0`), a scoped emulator unblock
(`express@4 → path-to-regexp: 0.1.13`), a peer-mismatch pin
(`babel-loader: 9.2.1` — Angular wants 10, mirror tops at 9), or a
transitive whose desired version was rejected/needs_review by the
mirror (`debug`, `flat-cache`, `pac-resolver`, `jsesc`, `spdy`,
`fresh`). **Don't add a new override without first trying
`mcp__greenflagged__request_package`** — most clean transitive
versions auto-vet within seconds, and growing the mirror's vetted set
is preferred over papering over with a pin. **Don't delete an
existing override without first confirming `npm install`,
`npm run build`, `npm run lint`, and `npm run emulators` all still
pass** — some overrides exist for runtime compat that doesn't show up
as an install error.

2. **`npm install` from scratch may fail** if the mirror's vetted set
doesn't match `package.json`'s declared versions. The remedy depends on
what's missing:

- **ETARGET** (version not in range): the requested version isn't
vetted. Call `mcp__greenflagged__request_package` with the exact
version from the error message, then
`mcp__greenflagged__check_package` to confirm it lands. Most clean
packages auto-vet in seconds. If the mirror returns
`needs_review` / `pending` / `rejected`, pin the version to whatever
the mirror has (highest vetted) via an override and document it.
- **E404** (package not in mirror): same path —
`mcp__greenflagged__request_package`. The mirror's auto-prioritize
handles both E404 and ETARGET.
- **Runtime TypeError** (`X is not a function`, `Cannot find module
'...../package.json' is not defined by exports`): the override picked
an ESM-only version of a CJS-consumed package. Find the older
CJS-friendly version and pin to that instead.

## Local dev — the env story

**`npm start` is local-only by default and must stay that way.**

`src/environments/environment.ts` points at the `demo-codetango` Firebase
project ID with fake credentials. The app's Firebase SDK refuses to talk
to a real backend on demo project IDs, and `app.module.ts` calls
`connectFirestoreEmulator` / `connectAuthEmulator` / `connectFunctionsEmulator`
during init. This is defense in depth: even if the env file got swapped,
the emulator-connect calls would still firewall the app from prod.

**Three env files, three purposes:**

- `environment.ts` — dev (demo + emulators). Used by `ng serve` and
`ng build` without a configuration flag.
- `environment.prod.ts` — production. Used by `ng build --configuration=production`
for actual deploys. Has `production: true` (enables service worker, etc.).
- `environment.prod-data.ts` — opt-in: `ng serve` against prod data with
`production: false`. Triggered by `npm run start:prod-data`, which
prints a red warning. Never wire this into the default workflow.

If you find yourself wanting to "make the dev server talk to prod" for any
reason — don't. Use `npm run start:prod-data` for one-off needs.

## Local emulators

`npm run emulators` boots auth, firestore, and functions against the
`demo-codetango` project. UI at http://127.0.0.1:4001/, hub at 4400,
auth 9099, firestore 8090, functions 5001.

This used to crash with `TypeError: pathRegexp is not a function`
because firebase-tools' emulator code (and its bundled express 4) needs
the callable `path-to-regexp@~0.1.x` API, and the global override pins
`6.3.0` (class-based) to satisfy the mirror's CVE policy. Fix is a
**scoped npm override**:

```json
"express@4": { "path-to-regexp": "0.1.13" }
```

`0.1.13` is patched for the relevant ReDoS CVE and was vetted into the
mirror specifically to unblock this. The flat `path-to-regexp@6.3.0`
override stays for every other consumer in the tree (router, superstatic,
etc.). Don't merge the two — express genuinely needs the 0.1.x callable
shape *and* the 0.1.x route syntax (`:name(*)` custom-pattern form),
neither of which 1.x+ supports.

## Build / test / lint

```
npm install # install all deps
npm start # dev server at http://localhost:4200/
npm run build # dev build
npm run build:prod # production build (uses environment.prod.ts)
npm run lint # eslint over src/, functions/src/, types.ts
```

There are no spec files — the karma/jasmine infrastructure was removed
because it was unused. If you add tests, you'll need to re-add the test
runner.

Functions builds with plain `tsc`:

```
cd functions && npm install && npm run build
```

## Codebase conventions

- **No moment, no lodash in `src/`.** Use luxon's `DateTime` for dates and
native JS (`?.`, `??`, destructuring, `Math.max`) instead of lodash
helpers. Functions still uses lodash for now.
- **Catch errors that aren't used start with `_`** (`catch (_e) {}`) — the
ESLint config allows the `^_` prefix to opt out of `no-unused-vars`.
- **Components are non-standalone by default** (`standalone: false` in the
decorator). Angular 20 defaults to standalone if not specified, but this
project uses NgModules; adding `standalone: false` keeps it consistent
with the existing app/page modules.
- **Firebase access is via the modular SDK** (`@angular/fire/firestore`,
not `@angular/fire/compat/*`). Constraint arrays for queries should be
typed `QueryConstraint[]` — rxjs 7 + Firebase 11 are strict about this.

## Known modernization next steps

1. **Angular 20 → 21.** The registry has Angular 21 vetted but
`@angular/fire` tops out at 20.0.1 (peers `@angular/core@^20.0.0`).
When `@angular/fire@21` lands, bump all `@angular/*` to 21 plus the
toolchain (`@angular-devkit/build-angular`, `@angular/cli`,
`angular-eslint` to 21.x). Note that angular-eslint@21 also peers
`@angular/cli >= 21`, so this bump goes hand-in-hand.

2. **inject() migration.** Angular 20 prefers the `inject()` function
over constructor parameter DI; `@angular-eslint/prefer-inject` flags
91 such sites across the services and components. Rule is currently
off in `eslint.config.mjs`. Use `ng generate @angular/core:inject`
to auto-migrate when ready, then re-enable the rule.

## Things that already exist — don't reinvent

- `greenflagged-feedback.md` (gitignored) — feedback memo with specific
asks of the greenflagged mirror itself, written from this session's
experience. Reference if working on related tooling.
82 changes: 44 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,40 +15,48 @@ If you're a developer interested in contributing, read on:

### Dev setup

Install NVM
Install Node 20 (Functions runtime) or 22 (local dev). Then:

Run `nvm use` to install node 14
```
npm install
npm start
```

Install the Ionic CLI, Angular CLI, and the Firebase CLI:
`npm install -g @ionic/cli @angular/cli firebase-tools`
The dev server runs at http://localhost:4200.

Install all the dependencies:
`npm run update`
`npm start` is **local-only by default** — `src/environments/environment.ts` points the app at the `demo-codetango` Firebase project ID, so the SDK will refuse to talk to a real Firebase backend. Auth, Firestore, and Functions calls all try to reach a local emulator on `127.0.0.1`. This is intentional so an accidental write never lands in prod data.

Serve the project:
`ionic serve`
### Running modes

Ionic Framework docs:
https://ionicframework.com/docs/components
| Command | What it does | Talks to prod? |
| --- | --- | --- |
| `npm start` | `ng serve` against the demo Firebase config; emulator-bound | No (firewalled) |
| `npm run emulators` | Boots the Firebase emulator suite (auth/firestore/functions/UI) | No |
| `npm run start:emulators` | Starts emulators + `ng serve` together (one Ctrl-C kills both) | No |
| `npm run start:prod-data` | `ng serve` against the **production** Firebase project — explicit opt-in, prints a red warning banner | **Yes** |
| `npm run build` | Dev `ng build` (uses `environment.ts`) | No |
| `npm run build:prod` | Production `ng build` (uses `environment.prod.ts`) | N/A (build output) |
| `npm run lint` | ESLint on `src/`, `functions/src/`, `types.ts` | N/A |

Firestore Docs:
https://firebase.google.com/docs/firestore
### Vetted package registry

### CLI niceties
`.npmrc` points npm at `https://greenflagged.dev/npm`, the org's vetted package mirror. Most transitive deps had no satisfying vetted version on the first install — `package.json` has ~90 `overrides` entries that pin each to whatever was both vetted and compatible with the rest of the tree (some pinned forward to drop CVE-tainted versions, some pinned back to the last CJS-friendly release because firebase-tools' internals are CJS).

If a fresh `npm install` ever fails with `ETARGET` (mirror lacks a satisfying version), the registry can usually auto-prioritize the request — re-run `npm install` after ~30 s. For unblocked work, `legacy-peer-deps=true` is on in `.npmrc` for one optional peer (`@angular/fire` → `firebase-tools`).

You can leverage the Ionic CLI for creating pages, components, and services. It uses the Angular CLI under the hood for most of the heavy lifting.
### CLI niceties

Create a new page:
`ionic g page "pages/Game Detail"`
This project uses the Angular CLI directly (no Ionic CLI required):

Create a new component:
`ionic g component components/board`
```
npx ng g page pages/game-detail
npx ng g component components/board
npx ng g service services/auth
```

Create a new service:
`ionic g service services/auth`
Ionic Framework docs: https://ionicframework.com/docs/components

More options through interactive CLI:
`ionic generate`
Firestore Docs: https://firebase.google.com/docs/firestore

### CI/CD

Expand All @@ -58,22 +66,20 @@ When a pull request is created (and any future commits are pushed to that branch

First, you'll need to be invited to the [firebase cloud console for this project](https://console.firebase.google.com/u/0/project/codetango) and then login to the cli via `firebase login`.

You need to run `ionic build` in the root of the project to get the static webapp built for deployment in `www/`. At that point you can just `firebase deploy --only hosting -P [env]` to deploy just that content. Here are some aliases in the `package.json` and other quick commands:

You can deploy to either the `dev` database or the `prod` database. By default, when serving the application, it uses the `dev` project, so we don't mess with `prod` data. These configurations are stored in `environment.ts` and `environment.prod.ts`

Deploy everything in one go:
`npm run deploy:dev`
`npm run deploy:prod`
`npm run build:prod` produces a static webapp in `www/`. `firebase deploy --only hosting -P [env]` ships just that content. The `package.json` has aliases for the common combinations:

Deploy hosting separately:
`npm run deploy-hosting:dev`
`npm run deploy-hosting:prod`
```
npm run deploy:dev # build + deploy everything to dev
npm run deploy:prod # build + deploy everything to prod
npm run deploy-hosting:dev # build + deploy only hosting to dev
npm run deploy-hosting:prod # build + deploy only hosting to prod
npm run deploy-functions:dev
npm run deploy-functions:prod
```

Deploy functions separately:
`firebase deploy --only functions -P dev`
`firebase deploy --only functions -P prod`
To deploy a single cloud function:

Or just deploy a single cloud function like so:
`firebase deploy --only functions:onGameCreate -P dev`
`firebase deploy --only functions:onGameCreate -P prod`
```
firebase deploy --only functions:onGameCreate -P dev
firebase deploy --only functions:onGameCreate -P prod
```
Loading
Loading