Skip to content

Modernize dependency stack: Angular 20 + firebase-functions v2 + firebase-tools 15#161

Merged
seanmadden merged 24 commits into
hbx-luv:masterfrom
seanmadden:modernize-dependencies
Jun 15, 2026
Merged

Modernize dependency stack: Angular 20 + firebase-functions v2 + firebase-tools 15#161
seanmadden merged 24 commits into
hbx-luv:masterfrom
seanmadden:modernize-dependencies

Conversation

@seanmadden

@seanmadden seanmadden commented May 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

Brings the project's frontend, Cloud Functions, and build tooling forward by ~6 major Angular versions and across most of the Firebase stack. Net result: npm install clean, npm run build clean, npm run lint clean, npm run emulators boots all 9 functions with v2 triggers initialized, npm start serves the app at http://localhost:4200/.

Frontend (Angular + Ionic stack)

  • Angular 9.120.3 (incremental: 9 → 13 → 15 → 17 → 19 → 20 across the prior commits in this branch).
  • @angular/fire 6.020.0.1, migrated from compat namespaces to the modular SDK (@angular/fire/firestore, @angular/fire/auth, etc.).
  • Ionic 58.8, RxJS 6.57.8, chart.js 24.5, TypeScript 3.85.9.
  • Dropped: @ionic-native/*, lodash and moment from src/ (now luxon + native JS), karma/jasmine (no specs in repo), tslint/protractor/clang-format.

Cloud Functions

  • firebase-functions 87.2 v2 API. All 9 entry points (onCreateGame/Update/Delete, onCreateClue/UpdateClue, onWriteProposedClues, onCreateAdmin, api, askChatGpt) moved from firebase-functions/v1 (functions.firestore.document(...)) to v2 (onDocumentCreated/Updated/Deleted/Written, onRequest, onCall).
  • firebase-admin 813.10, Node 1220.
  • Removed lodash from functions; replaced with native equivalents (Object.entries, Array.prototype.filter, a 4-line Fisher-Yates shuffle, dedupe via Map).
  • The AI word-list theme util (functions/src/util/chatgpt.ts) was rewritten for the v7 / openai@6 stack: functions.config()defineSecret('CHATGPT_API_KEY'), deprecated Configuration/OpenAIApinew OpenAI({apiKey}), lodash → local shuffle. Secret is bound on onCreateGame.

Build / tooling

  • firebase-tools 815.13. firebase-tools 14.3.x's emulator runtime crashed every v2 function on load via a leftover functions.config() probe that throws in firebase-functions v7; 15.x drops that probe.
  • ESLint flat config + typescript-eslint (replaces tslint).
  • angular-eslint 20.7 for both TS and HTML template linting. Two opinionated rules left off for now and tracked as follow-ups: @angular-eslint/prefer-standalone (project is intentionally NgModule-based) and @angular-eslint/prefer-inject (91 sites; ng generate @angular/core:inject schematic available).

Vetted-package mirror + overrides

.npmrc sets registry=https://greenflagged.dev/npm — an organizational vetted-package mirror that holds an approved subset of public npm. Most transitives the modernized stack pulls in were either already vetted or auto-approved on request; the overrides block carries 1 scoped + 8 flat entries, each load-bearing:

  • path-to-regexp: 6.3.0 — global CVE forward-pin
  • express@4 → path-to-regexp: 0.1.13 — scoped: unblocks firebase emulators:start, which crashed on TypeError: pathRegexp is not a function because the global CVE pin was forcing the class-based 6.x API onto express 4's nested copy (express 4's layer.js needs the callable 0.1.x API and the 0.1.x :name(*) route syntax that firebase-tools registers)
  • babel-loader: 9.2.1 — Angular 20 toolchain wants ^10.0.0, mirror tops at 9
  • debug: 4.4.3, flat-cache: 6.1.21, pac-resolver: 9.0.1, jsesc: 2.5.2, spdy: 4.0.1, fresh: 0.5.2 — the older versions transitives wanted were either rejected by the mirror's CVE policy or pending review; the forward-pin stays until the requested version clears review.

The wider story (mirror integration, override patterns, dev-env story, emulator setup) is captured in CLAUDE.md for future contributors. If you don't use the greenflagged mirror, the overrides block is harmless — npm only enforces an override when the named package actually appears in the dep tree, and these all pin to valid public-npm versions.

Documentation

  • CLAUDE.md: new file capturing the registry/override story, env-file story, emulator setup, codebase conventions, and remaining modernization items.
  • README.md: updated dev workflow.

Deploy plan — read this before firebase deploy --only functions

v1 → v2 Cloud Functions don't deploy in place. Firebase CLI will refuse to swap a Gen 1 function for a Gen 2 function of the same export name; the deploy will fail with "upgrade from gen 1 to gen 2 is not allowed". The required pre-step is to delete each of the 9 affected exports first, then deploy:

firebase functions:delete onCreateGame onUpdateGame onDeleteGame \
  onCreateClue onUpdateClue onWriteProposedClues onCreateAdmin \
  api askChatGpt
firebase deploy --only functions

Plan this for low-traffic — there's a brief gap where the triggers won't fire between delete and deploy.

Set the ChatGPT secret before deploying so the AI word-list theme feature works:

firebase functions:secrets:set CHATGPT_API_KEY

(The previous functions.config().chatgpt.apikey mechanism was removed in firebase-functions v7; this PR migrates to a v2 secret.)

Test plan

  • npm install from clean: succeeds
  • npm run build: clean
  • npm run lint: clean (both src/**/*.{ts,html} and functions/src/**/*.ts)
  • cd functions && npm run build: clean
  • npm run emulators: all 9 v2 functions initialize, UI at http://127.0.0.1:4001/
  • npm start: serves at http://localhost:4200/, emulator-backed
  • Manual smoke test: create room, join room, play a game in the emulator

Out of scope (follow-ups)

  • Angular 21. A prerelease bump (Angular 21.2 + @angular/fire@21.0.0-rc.0 + firebase 12.13) installs / builds / lints / boots emulator cleanly, but the app hangs on <ion-spinner> when joining a room. The regression is likely in @angular/fire@21-rc's docData semantics or a firebase 12 modular SDK signature change. Worth resuming once @angular/fire@21 goes GA — browser console will name the regression directly.
  • inject() migration. Angular 20+ prefers inject() over constructor DI. @angular-eslint/prefer-inject flags 91 sites; schematic available (ng generate @angular/core:inject). Rule disabled in eslint.config.mjs until then.
  • Control-flow block syntax. Angular 17+ @if/@for/@switch over *ngIf/*ngFor. Schematic available (ng generate @angular/core:control-flow).

🤖 Generated with Claude Code

seanmadden and others added 23 commits April 21, 2026 18:13
First of a 7-PR sequence upgrading CodeTango from Angular 9 to Angular 17
LTS. This PR is structural cleanup only, no Angular version changes.

- Remove tslint, protractor, clang-format, and related devDeps (jasmine-spec-reporter,
  inquirer, inquirer-autocomplete-prompt, fuzzy, @types/jasminewd2, codelyzer, open)
- Delete tslint.json at root and in functions/, e2e/ directory, and 37 scaffold
  *.spec.ts files that only asserted "should be created"
- angular.json: remove lint (tslint) and e2e (protractor) architect targets
- firebase.json: drop tslint predeploy hook from functions
- functions/package.json: engines.node 14 -> 20, drop tslint devDep and lint script
- Remove "npm run update" script and its chained calls in deploy scripts

Note: .npmrc now points at greenflagged registry. npm install against pinned
Angular 9 versions will 404 until PR2 bumps them into the registry's approved
range. PR1 stands alone as a structural cleanup; verification of install and
build is gated by PR2.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Angular 9.1 -> 19.2 across all @angular/* packages
- @angular/cli + @angular-devkit/build-angular 9 -> 19.2
- @angular/fire 6 -> 19 (peer-locked to Angular 19)
- firebase 8 -> 11 (modular API required by AngularFire 19)
- @ionic/angular 5 -> 8.4, @ionic/angular-toolkit 2 -> 12
- rxjs 6.5 -> 7.8, zone.js 0.10 -> 0.15, typescript 3.8 -> 5.5, tslib 1 -> 2.8
- chart.js 2.9 -> 4.4, moment 2.27 -> 2.30, luxon 1 -> 3
- Drop @ionic-native/* (web-only deploy), drop karma-coverage-istanbul-reporter
- @types/node 12 -> 20 (matches Functions Node 20 runtime)
- Karma 5 -> 6.4, jasmine-core 3 -> 5

functions/package.json:
- Drop firebase client SDK (only referenced for types; migrating to admin types)
- firebase-admin 10 -> 12, firebase-functions 4 -> 6
- firebase-functions-test 0.1 -> 3.3, typescript 5.1 -> 5.5
- luxon 1 -> 3, @types/luxon 1 -> 3

Build config:
- tsconfig.json: target/module ES2022, bundler resolution, useDefineForClassFields false
- polyfills.ts: 'zone.js/dist/zone' -> 'zone.js'
- angular.json: browser-esbuild builder, new polyfills array shape, new
  serviceWorker shape, serve uses buildTarget, drop cordova targets, drop
  dead defaultProject + cli.defaultCollection
- Delete webpack.config.js (referenced defunct @ionic/app-scripts)
- Drop config.ionic_webpack from package.json

Source code migration (services, app.module, chart, etc.) in follow-up commits
on this branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Frontend:
- app.module.ts: AngularFireModule.initializeApp / AngularFireXModule ->
  provideFirebaseApp/provideFirestore/provideAuth/provideAnalytics/provideFunctions
- Drop @ionic-native/splash-screen + @ionic-native/status-bar (web-only deploy),
  remove SplashScreen/StatusBar wiring from app.component.ts, remove
  entryComponents: [] (gone in Angular 13+)
- 26 components/pages: add `standalone: false,` (Angular 19 default flipped)
- 10 service rewrites for modular API:
    AngularFirestore.collection('x').doc(id).valueChanges/snapshotChanges
      -> docData(doc(firestore,'x',id), {idField:'id'})
    .collection('x', ref => ref.where(...).orderBy(...))
      -> query(collection(...), where(...), orderBy(...)) + collectionData
    .add/.update/.delete/.set
      -> addDoc/updateDoc/deleteDoc/setDoc
    firebase.firestore.FieldValue.arrayUnion/arrayRemove/serverTimestamp
      -> arrayUnion/arrayRemove/serverTimestamp from @angular/fire/firestore
    AngularFireAuth.signInWithPopup(...) + firebase.auth().currentUser
      -> signInWithPopup(auth, ...) + auth.currentUser + authState(auth)
    AngularFireFunctions.httpsCallable(...).toPromise()
      -> (await httpsCallable(functions, ...)(data)).data
- admin.page.ts + game-detail.page.ts: same modular migration
- elo-chart.ts: chart.js 2 -> 4 config shape (scales.y/x single object, plugins.legend
  + plugins.title, explicit Chart.register of LineController/LineElement/PointElement/
  LinearScale/CategoryScale/Legend/Title/Tooltip/Filler, destroy-on-rebuild)
- rxjs 6 -> 7 : .toPromise() -> firstValueFrom() in pregame.component,
  game-history.page, chat-gpt.service

Functions:
- Drop firebase (client SDK) imports in types.ts / util/big-batch.ts / util/elo.ts;
  switch to `import type {firestore} from 'firebase-admin'` for FieldValue,
  WriteBatch, DocumentReference, SetOptions, UpdateData types
- firebase-functions v6 defaults to v2 API; all v1-style triggers switched to
  `import * as functions from 'firebase-functions/v1'` (v1 stays supported).
  Trigger call sites unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zone.js 0.14+ flattened the deep imports; tests must use the new path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Install: 86 transitive `overrides` pin vetted versions (some forward, some
backward to CJS-compatible ones for firebase-tools' chain). firebase-tools
bumped 13.6.1 -> ~14.3 to escape inquirer@8 / pg-gateway@0.3-beta. .npmrc
adds `legacy-peer-deps=true` for @angular/fire's optional firebase-tools peer.

Local-only by default: environment.ts now points at `demo-codetango` with
fake creds + emulator-connect attempts, so even an accidental Firestore read
can't reach prod. `npm run start:prod-data` is the explicit opt-in (banner-
warned). environment.prod.ts is reserved for `ng build --configuration=production`
deploys. firebase.json gains an emulator block on non-conflicting ports.

Source modernization for the Angular 19 / Firebase 11 / esbuild stack:
moment removed (-> luxon, 3 files); lodash removed from the app (-> native
?./??/destructuring/Math.max, 7 files). Adds `standalone: false` to the one
directive Angular 19 would default to standalone. Switches `ReplaySubject<never>`
-> `<void>` to satisfy rxjs 7's stricter typing. Drops `~` prefix in
src/global.scss @imports (esbuild ignores the webpack-loader convention).
types.ts swaps the v8 `import {default as firebase}` for modular `FieldValue`.

Drops karma/jasmine + their angular.json `test` target + karma.conf.js +
src/test.ts + tsconfig.spec.json — there are zero spec files in the project.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…7 / TS 5.9

- functions: bump firebase-admin 12 -> 13.10, firebase-functions 6 -> 7.2,
  typescript 5.5 -> 5.9; target es2020; pin `types` to node/luxon/lodash so
  tsc stops crawling root @types
- app: typescript 5.5 -> 5.8
- ESLint flat-config (eslint 10 + typescript-eslint 8); replaces dropped
  tslint; `npm run lint` covers src/, functions/src/, types.ts
- Fix the 5 errors that lint surfaced:
  - chat-gpt: drop useless try/catch (was just rethrowing)
  - onCreate: drop stale @ts-ignore (target line type-checks clean now)
  - room.service: unescape `\-` in character class (no-useless-escape)
  - util.service: replace `new Promise(async resolve => ...)` with plain
    async functions; semantics preserved via Ionic's alert.onDidDismiss

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Prefix unused catch vars with `_` (`catch (_e)` etc.) so they pass the
  `^_` allowance instead of triggering no-unused-vars
- Drop unused `Observable` and `GameStatus` imports
- Prefix unused `context` args on firestore trigger callbacks
- Mark intentional-ignore destructured keys with `_status` / `_id` / `_date`
  in clue.service and elo-history.service `omit`-via-rest patterns

`npm run lint` is now zero warnings, zero errors.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…istry overrides)

The previous README told contributors to `ionic serve`, which would have
silently connected to prod. The new copy:

- documents `npm start` (default = local-only, emulator-bound)
- documents `npm run start:prod-data` as the explicit prod-data opt-in
- explains why ~90 transitive overrides live in package.json (vetted-registry
  alignment, both forward and backward)
- lists the lint script and the build/deploy scripts in one table
- drops the now-stale references to the global Ionic CLI and `npm run update`

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers the registry/overrides contract, the local-only-by-default env
story, why emulators don't run yet, build/lint commands, and the known
modernization queue (functions v1→v2, Angular 19→21).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All 9 entry points moved from firebase-functions/v1 to v2 modular imports:
- Firestore triggers (games, clues, proposed-clues, admin) now use
  onDocumentCreated/Updated/Deleted/Written from v2/firestore. Handler
  signatures collapse to (event) and unwrap event.data + event.params
  defensively, since v2 typings allow event.data to be undefined.
- https/api.ts uses onRequest from v2/https.
- callable/chat-gpt.ts uses onCall from v2/https; the handler reads
  req.data instead of receiving the value directly. Wire protocol is
  unchanged, so the Angular httpsCallable client needs no edits.

Deploy note: v2 functions run on Gen 2 (Cloud Run). Firebase CLI will
not silently swap a v1 deployment for a v2 deployment of the same name
— the v1 functions must be deleted (firebase functions:delete) before
the v2 versions can claim those export names, otherwise the deploy
fails with an "upgrade from gen 1 to gen 2 is not allowed" error.

tsc + eslint both clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`npm run emulators` was crashing in firebase-tools' Express hub with
`TypeError: pathRegexp is not a function`. Root cause: the global
`path-to-regexp@6.3.0` override (in place to satisfy the mirror's
ReDoS CVE policy) forced express 4's nested copy to the class-based
6.x API, but express 4 — and firebase-tools' route registrations like
`/functions/projects/:project_id/triggers/:trigger_name(*)` — need the
callable 0.1.x API and 0.1.x route syntax. 6.x and later don't accept
`(*)` as a custom-pattern form and reject it as invalid regex.

Fix: scoped npm override pinning path-to-regexp to 0.1.13 only when
nested under express@4:

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

`0.1.13` was vetted into the greenflagged mirror specifically to
unblock this; it has the ReDoS CVE patched. Every other consumer
(router, superstatic, etc.) keeps the flat 6.3.0 override.

Verified: `npm run emulators` boots cleanly with all 9 v2 functions
initialized — auth on 9099, firestore on 8090, functions on 5001,
UI at http://127.0.0.1:4001/.

CLAUDE.md updated to replace the "emulators currently don't run"
section with the new working setup.

Lockfile note: regenerating the lockfile was required for the new
scoped override to take effect — npm 10.9.2 won't reinterpret nested
overrides without a full re-resolve. The resulting diff is large
(~4500 lines removed) but it's lockfile shape change, not behavior
change: cross-platform optional binaries (@esbuild/*, @rollup/*,
@parcel/watcher-*) and other transitives that were over-pinned in
the old lockfile got pruned to npm 10's defaults. node_modules on
disk is consistent and the project builds/lints/emulates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All @angular/* packages and toolchain (cli, devkit/build-angular,
compiler, compiler-cli, language-service) move from 19.2 → 20.3.
@angular/fire bumps 19 → 20.0.1 in lockstep.

No source changes were needed — the code already used patterns
(modular @angular/fire imports, NgModules + `standalone: false`,
typed QueryConstraint[] queries) that Angular 20 accepts without
adjustment. `npm run build` and `npm run lint` both clean.

Mirror-side adjustments:
- Added `babel-loader: 9.2.1` override since @angular-devkit/build-angular
  20.3.x pulls babel-loader@^10.0.0 but the mirror only has 9.2.1
  vetted; legacy-peer-deps swallows the peer warning and the actual
  build pipeline is unaffected.
- `algoliasearch@5.35.0` (a new @angular/cli direct dep for `ng update`
  search) needed @algolia/{requester-fetch,abtesting,ingestion,
  monitoring} to be vetted into the mirror. Sean approved them
  manually mid-install.

Angular 21 remains blocked on @angular/fire@21 (mirror tops at 20.0.1).
CLAUDE.md updated to reflect: frontend is now 20.3, the v2 Functions
migration is done, and the next modernization step is firebase-tools
14 → 15 (blocked on pg-gateway@^0.3.0-beta.4 vetting).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
firebase-tools 14.3.1's emulator runtime probed `functions.config()`
on every function load (see
`functionsEmulatorRuntime.js:initializeFunctionsConfigHelper`),
which is a hard error in firebase-functions v7 — every v2 trigger
was getting killed with "Your function was killed because it
raised an unhandled error" the moment a Firestore write fired it.
firebase-tools 15.x drops that probe (and the legacy v1 config
proxy that fed it).

Unblocked by `pg-gateway@0.3.0-beta.4` being vetted into the
greenflagged mirror (Sean approved). No other transitives needed
vetting for this bump.

Verified:
- `npm run emulators` boots cleanly on 15.13.0
- All 9 v2 functions (onCreateGame/Update/Delete, onCreateClue,
  onUpdateClue, onWriteProposedClues, onCreateAdmin, api,
  askChatGpt) load and initialize without error
- Firestore emulator jar auto-upgraded 1.19.8 → 1.20.4 in the process
- `npm run build` and `npm run lint` both clean

The path-to-regexp scoped override from PR3
(`express@4 → path-to-regexp@0.1.13`) is still needed — express 4
is still the runtime here, just the firebase-tools-side probe is
the thing that changed.

CLAUDE.md updated: removed the firebase-tools 14 → 15 item from the
modernization list (now done), renumbered the remaining items.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLAUDE.md flagged that src/ had been lodash-free for a while but
functions/ still pulled it in for a few small helpers. All five
usages had trivial native equivalents:

- util/elo.ts: toPairs(userMap) → Object.entries(userMap)
  (replaced the two-step `userMapPairs` + `pair[0]/pair[1]` pattern
  with a single destructured for-of loop)
- games/onUpdate.ts: without(rooms, roomId) →
  rooms.filter(r => r !== roomId)
- games/onCreate.ts: shuffle(tiles) → a local 4-line Fisher-Yates
  helper. Doesn't mutate input; matches lodash.shuffle's contract.
- games/onDelete.ts: `import * as _ from 'lodash'` was unused
  entirely — just deleted the import.
- games/clues/onUpdate.ts: map(arr, 'word') → arr.map(g => g.word),
  uniqBy(arr, 'word') →
  Array.from(new Map(arr.map(g => [g.word, g])).values())

functions/package.json no longer depends on lodash.
`cd functions && npm run build` and `npm run lint` at root both clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tic@10

firebase-tools 15.13 declares `superstatic: ^10.0.0` and the mirror
has 10.0.0 vetted. The leftover `"superstatic": "9.1.0"` override
was a CJS-compat back-pin from the firebase-tools 14 era and is no
longer needed.

Removing the override lets npm resolve superstatic to 10.0.0
naturally; 2 transitive packages get pruned and 1 changes version.
`npm run emulators` still boots cleanly with all 9 v2 functions
initializing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The functions/ workspace was already pinned to ~5.9.0; the root
was lagging at ~5.8.0. Angular 20's
@angular-devkit/build-angular peers `typescript >=5.8 <6.0`, so
5.9 is in range. Mirror has 5.9.3 vetted.

`npm run build` and `npm run lint` both clean on 5.9.3.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Downgrade eslint 10 → 9 to satisfy angular-eslint@20.x's peer
(`eslint ^8.57.0 || ^9.0.0`) and add `angular-eslint@~20.7.0`. The
flat config is split into a `**/*.ts` block that extends
`angular.configs.tsRecommended` + processes inline templates, and a
`**/*.html` block that extends `angular.configs.templateRecommended`.
The lint script now matches `*.{ts,html}` under `src/`.

Two angular-eslint rules are explicitly disabled:
- `@angular-eslint/prefer-standalone`: this project is intentionally
  NgModule-based (see CLAUDE.md "Components are non-standalone by
  default"), so 29 errors against the convention are noise here.
- `@angular-eslint/prefer-inject`: 91 sites use constructor DI; the
  inject() function migration is a substantive change worth its own
  focused PR (with `ng generate @angular/core:inject`). Deferred.

Three small fixes from the new rules:
- `template/eqeqeq`: `game.gameType == GameType.MEMES` →
  `game.gameType === GameType.MEMES` in game-board.component.html
- `use-lifecycle-interface`: 6 warnings across 5 files. Added the
  appropriate `implements OnInit / OnChanges / OnDestroy` clauses
  (and the corresponding @angular/core imports) on
  GameComponent, PregameComponent, SameTeamComponent,
  UsersComponent, VersusComponent. No runtime behavior change —
  Angular calls the lifecycle methods by name regardless; the
  interface just documents/typechecks the contract.

`npm run lint` and `npm run build` both clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… work

Replace the "angular-eslint integration" remaining-step (now done in
the prior commit on this branch) with an "inject() migration" entry,
since @angular-eslint/prefer-inject is the rule deliberately deferred
when angular-eslint was wired up. Also tighten the Angular 21 entry:
angular-eslint@21 peers @angular/cli >= 21, so the angular-eslint
bump goes with the framework bump rather than on its own.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each was forcing a version of a package no longer present in the dep tree
after the firebase-tools 15 + Angular 20 bumps. Removed:

- @sindresorhus/merge-streams
- unicorn-magic
- isbinaryfile
- void-elements
- html-escaper
- global-directory

`npm install` is a no-op (nothing to reconcile), build and lint stay green.
This trims the overrides block from 85 → 79 flat entries (plus the scoped
`express@4` override).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stripped the overrides block to just the 3 critical pins (path-to-regexp
global, express@4 nested path-to-regexp, babel-loader) and iterated
`npm install --prefer-online` against the greenflagged mirror, calling
`mcp__greenflagged__request_package` on every ETARGET. Most clean older
versions auto-approved within seconds of being requested. A few got
rejected or needs_review and their overrides stayed.

Vetted into the mirror via this PR (auto-approved on request):
- p-defer 3.0
- postgres-array 2.0.0, postgres-bytea 1.0.0, postgres-date 1.0.4,
  postgres-interval 1.1.0
- err-code 2.0.3
- eventsource 3.0.2
- tuf-js 4.1.0
- minipass-flush 1.0.5, minipass-pipeline 1.2.4
- buffer-crc32 1.0.0 (manually approved by Sean)
- @types/serve-static 1.15.10 (manually approved by Sean)
- @apidevtools/json-schema-ref-parser 9.1.2
- is-docker 3.0.0, is-buffer 1.1.6, is-wsl 1.1.0, is-what 3.14.1,
  is-yarn-global 0.3.0
- copy-anything 2.0.6
- parse-node-version 1.0.1
- cli-truncate 4.0.0
- proc-log 4.2.0 and 5.0.0
- hashery 1.5.1
- lodash.isobject 2.4.1
- pg-connection-string 2.13.0
- adjust-sourcemap-loader 4.0.0
- @tsconfig/node12 1.0.11, @tsconfig/node14 1.0.3, @tsconfig/node16 1.0.4
- @types/eslint-scope 3.7.7
- json-parse-even-better-errors 2.3.1
- selfsigned 2.4.1
- stdin-discarder 0.2.2
- hosted-git-info 7.0.2
- npm-install-checks 6.3.0, npm-normalize-package-bin 3.0.1
- @types/send 0.17.5, @types/mime 1.3.5
- error-ex 1.3.2, is-arrayish 0.2.1
- unicode-emoji-modifier-base 1.0.0

Kept overrides — these were rejected (CVE policy) or needs_review,
or are project-specific pins:
- `path-to-regexp: 6.3.0`: global CVE forward-pin (kept)
- `express@4 → path-to-regexp: 0.1.13`: scoped emulator unblock (kept)
- `babel-loader: 9.2.1`: Angular wants 10, mirror has 9 (kept)
- `debug: 4.4.3`: 2.6.9 rejected by mirror CVE policy
- `flat-cache: 6.1.21`: 4.0.1 rejected
- `pac-resolver: 9.0.1`: 7.0.1 needs_review
- `jsesc: 2.5.2`: 3.1.0 needs_review (backward-compat pin)
- `spdy: 4.0.1`: 4.0.2 needs_review (one-patch back-pin)
- `fresh: 0.5.2`: 2.0.0 needs_review (backward-compat pin)

Final: 1 scoped + 9 flat overrides (was 85). `npm install`,
`npm run build`, `npm run lint` all clean. `npm run emulators` boots
all 9 v2 functions. `npm start` serves the app.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous "~90 entries in overrides" guidance is no longer accurate
after the cleanup commit — overrides are 1 scoped + 9 flat. Rewrite
the section to:

- name each of the 9 surviving overrides and the reason it's kept
- explicitly route ETARGET / E404 through mcp__greenflagged__request_package
  rather than the "manually pin to the highest vetted version" reflex
- warn that override deletion requires runtime smoke testing, not just
  a clean `npm install`

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolve 8 content conflicts against the upstream changes (mostly the
AI word-list theme feature, emoji-remix asset pack, scorecard refactor,
and assetUrlPattern enrichment in game.service):

- README.md: take HEAD (Node 20/22 dev setup) over upstream (nvm node 14)
- package.json / package-lock.json (root + functions/): take HEAD
  wholesale — the whole point of this branch is the modernized deps
- functions/src/games/onCreate.ts: keep HEAD's v2 trigger
  (onDocumentCreated) but take upstream's aiWordlistTheme support
  (getRoom returns it; getGameType + generateNewGameTiles thread it
  through). Drop the `// @ts-ignore` upstream had; the param is genuinely
  used. Bind chatgptApiKey secret to the onDocumentCreated options.
- src/app/services/game.service.ts: keep HEAD's modular Firebase SDK
  in all four query helpers; merge in upstream's
  assetUrlPattern = buildAssetUrlPattern(game.gameType) enrichment via
  a .pipe(map(...)) on top of collectionData/docData.
- src/app/pages/scorecard/scorecard.page.ts: take upstream's
  formatUserData extraction but swap the moment-based date format
  inside it for the luxon equivalent
  (DateTime.fromMillis(...).toFormat('LLL d, yyyy')) since we removed
  moment in the modernization.

Functions chatgpt util needed a rewrite to be compatible with the
modernization, not just the merge:
- functions/src/util/chatgpt.ts: upstream used `functions.config()`
  (hard-removed in firebase-functions v7), the deprecated openai@3
  Configuration/OpenAIApi API, and lodash.shuffle. Rewrote to use
  `defineSecret('CHATGPT_API_KEY')` from firebase-functions/params,
  the openai@6 client API (`new OpenAI({apiKey}).chat.completions.create(...)`),
  and a local Fisher-Yates `shuffle`. Added `openai: ^6.38.0` to
  functions/package.json.

Three lint fixes for upstream code that didn't pass the (stricter,
post-modernization) lint config:
- src/app/components/pregame/pregame.component.ts: `let idToDelete`
  → `const idToDelete` (prefer-const)
- src/app/services/game.service.ts: drop unused `GameType` import
- src/app/services/util.service.ts: replace `new Promise(async resolve
  => { await ... })` with `new Promise(resolve => ...then(...))` to
  satisfy `no-async-promise-executor`

Verified: `npm install` clean, `npm run build` clean,
`npm run lint` clean, `cd functions && npm run build` clean.

**Deploy note (in addition to the v1 → v2 Gen-1/Gen-2 swap noted in
the PR description):** the AI word-list theme feature now reads its
OpenAI key from a v2 secret named `CHATGPT_API_KEY`. Set it via
`firebase functions:secrets:set CHATGPT_API_KEY` before deploying
or the feature will return empty word lists.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ameplay on auth

Step 1 of the rules hardening (zero client changes required):

- users.update: was `auth.uid == request.resource.id` with no field
  guard, letting anyone set role: 'Admin' on their own doc and then
  write the admin/{action} triggers. Now restricted to the doc owner
  AND a field allowlist (name/email/rooms/nickname/photoURL) — the
  same list the previously-dead onlyWhitelistedKeys() already encoded.
- users.create: same allowlist applied for first-time signup so the
  bypass can't happen on doc creation either.
- games / rooms writes: require request.auth != null. Was wide open.
- deletedGames writes: now `false` (server-only via admin SDK). Reads
  stay open for the in-app recovery UI (game-detail / game-history).
- wordlists writes: now isAdmin(). The only client caller is the
  admin page's setupDatabase(); cloud functions read via admin SDK.
- isAdmin(): added explicit `request.auth != null` short-circuit so
  the get() doesn't run for unauthenticated requests.

What this intentionally does NOT change:
- Reads on games / rooms / users / eloHistory / userToUserHistory /
  wordlists stay open, because the landing-page leaderboard and
  bookmarkable room URLs need to load pre-login. Step 2 will gate
  reads behind a route AuthGuard.
- Game/room writes still don't enforce membership. Step 2 will
  narrow these to "must be a participant" once join is moved to a
  callable function (current `arrayUnion(currentUserId)` join writes
  can't be expressed as a member-only rule).

Validated by uploading the rules to the local emulator's
/emulator/v1/.../securityRules endpoint (HTTP 200).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

@jjzabkar jjzabkar left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nits:

  1. modify the nodejs version in .tool-versions for us asdf peeps; looks like you maybe have at least 20.19.0
  2. modify package.json to include engines:
"engines": {
  "node": ">=20.19.0"
}

I verified it starts up & runs with nodejs 20.19.0.

Image

Nice job, Clau... er, I mean, Sean!

Addresses review nits on PR hbx-luv#161 — surfaces the required Node version
for asdf users (.tool-versions) and for tools that read engines.node
(package.json).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@Matttaylor8910

Copy link
Copy Markdown
Member

@seanmadden are you actually able to join the game? That screenshot isn't showing a player list to the left and the controls to the right like it is today:

image

@seanmadden

seanmadden commented May 21, 2026

Copy link
Copy Markdown
Collaborator Author

gameplay (This was emoji, so I think them not loading is correct?):

Screenshot 2026-05-21 at 09 56 47 Screenshot 2026-05-21 at 09 57 26

@seanmadden

Copy link
Copy Markdown
Collaborator Author
Screenshot 2026-05-21 at 10 00 41

@seanmadden seanmadden merged commit 5a36deb into hbx-luv:master Jun 15, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants