UPGRADE.md dependency-debt table and PENDIENTE.md updated to reflect the 5 shipped phases and the two deferred sub-projects (react-leaflet 1.8->4 and the Bootstrap 4->5 CSS/JS jump), with rationale for each deferral. Final state: helmet/i18next/react-bootstrap/react-router warnings all gone from the console; only react-leaflet (deferred) and our own defaultProps/findDOMNode remain. REST smoke byte-identical.
372 lines
29 KiB
Markdown
372 lines
29 KiB
Markdown
# Meteor upgrade — 1.6.1.1 → 2.5 (Mongo 3.2) → 3.1 (Mongo 7, on `meteor3-wip`)
|
||
|
||
Incremental, verified upgrade of `todos-contra-el-fuego-web`. Branch:
|
||
`meteor3-upgrade` (base `tcef-master`). Local commits only — no push until
|
||
authorized.
|
||
|
||
## ⚠️ Hard blocker for the final 3.x jump: MongoDB 3.2
|
||
|
||
Production runs against the shared replica set **rsmain on MongoDB 3.2.11**.
|
||
Meteor 3 ships the modern `mongodb` Node driver, which requires a server
|
||
**≥ 4.2/4.4**. Meteor **2.x still works against Mongo 3.2**.
|
||
|
||
Therefore this branch lands on **Meteor 2.5** — empirically the highest release
|
||
whose bundled `mongodb` driver still connects to Mongo 3.2 (2.6 bumps the driver
|
||
to 4.x and fails; see "Escala ceiling" below) — with everything green. The
|
||
`meteor update --release 3.x` step (and even 2.6+) is **deliberately not taken**
|
||
until the Comunes infra team upgrades rsmain (shared infra — coordinated in a
|
||
separate phase, not unilaterally). Server code should still be migrated to
|
||
async/await before the final 3.x jump so it is mechanical (tracked as debt).
|
||
|
||
## ⚠️ Deploy-ordering dependency: notifications cutover
|
||
|
||
The old push/email notification code was removed from the web (see the
|
||
notifications commit) because it now lives in the **tcef-notifications**
|
||
microservice. **Production must not run this build until tcef-notifications is
|
||
emitting in prod** — otherwise users stop receiving notifications. The web
|
||
deploy and the notifications cutover must be coordinated.
|
||
|
||
## Safety net: REST smoke test
|
||
|
||
`smoke/` is a standalone Node harness that calls every REST endpoint the Flutter
|
||
app consumes, against a seeded local dev server, and diffs the responses against
|
||
committed snapshots (`smoke/snapshots/`). See `smoke/README.md`.
|
||
|
||
**Run after every escala — it must stay byte-identical:**
|
||
|
||
```bash
|
||
docker run -d --name tcef-mongo32 -p 27018:27017 mongo:3.2 # once
|
||
export PATH="$HOME/.meteor:$PATH" MONGO_URL="mongodb://localhost:27018/fuegos"
|
||
meteor --settings settings-development.json --port 3100 & # dev server
|
||
./smoke/smoke.sh # seed + compare
|
||
```
|
||
|
||
## Escalas
|
||
|
||
### Baseline — Meteor 1.6.1.1 (starting point)
|
||
|
||
- Boots against Mongo 3.2 with `settings-development.json`.
|
||
- Dev-enabling changes (do not affect production behavior):
|
||
- `IPGeocoder.js`: degrade gracefully when the MaxMind GeoLite2 DB is absent
|
||
(provisioned by cron in prod) instead of crashing at boot.
|
||
- `settings-development.json`: added dev-only `private.internalApiToken`
|
||
(`dev-smoke-token`) — the REST API only registers its routes when this is
|
||
set.
|
||
- **Notifications code removed** (migrated to tcef-notifications): deleted
|
||
`notificationsObserver.js`, `notificationsProcess.js`, the "Process pending
|
||
notif" SyncedCron job, and the `node-gcm` dependency. `subsUnion.js` kept (it
|
||
feeds `subs-public-union`, consumed by the app).
|
||
- REST smoke baseline captured here. ✅ green.
|
||
|
||
### Escala 1 — 1.6.1.1 → 1.8.3 ✅ green
|
||
|
||
```bash
|
||
meteor update --release 1.8.3
|
||
meteor npm install --save @babel/runtime@^7.26.0
|
||
```
|
||
|
||
Core packages bumped (highlights): `meteor` 1.8.6→1.9.3, `ecmascript`
|
||
0.10.7→0.13.2, `modules` 0.11.6→0.14.0, `mongo` 1.4.7→1.7.0, `npm-mongo`
|
||
2.2.34→**3.2.0** (mongodb driver 3.x — still fine against Mongo 3.2 server),
|
||
`webapp` 1.5.0→1.7.5, `standard-minifier-js` →2.5.2. `underscore` auto-added
|
||
(1.7 dropped it from meteor-base), `fetch` + `modern-browsers` added.
|
||
|
||
**Breaking change — Babel beta → stable.** Boot failed with
|
||
`Cannot find module '@babel/runtime/helpers/objectSpread2'`: the pinned
|
||
`@babel/runtime@7.0.0-beta.44` predates helpers that 1.8's `ecmascript` emits.
|
||
Fixed by upgrading to stable **7.x** (`@babel/runtime@^7.29.7`). Note: `@latest`
|
||
resolves to `8.x`, which is too new for Meteor's 7-style helper layout — pin to
|
||
`^7`.
|
||
|
||
REST smoke test: **byte-identical to baseline.** ✅
|
||
|
||
### Escala 2 — 1.8.3 → 1.11 (version resolution only)
|
||
|
||
```bash
|
||
meteor update --release 1.11
|
||
```
|
||
|
||
Resolution reached 1.11 (`ecmascript` 0.14.3, `mongo` 1.10.0, `npm-mongo`
|
||
**3.8.0** — still Mongo-3.2-compatible, `accounts-*` 1.x, `email` 2.0). But this
|
||
release surfaced the two big blockers below. The app was ultimately driven to
|
||
**2.3** (see next section); 1.11 was a transit point, not a landing.
|
||
|
||
### Escala 3 — → Meteor 2.3 ✅ green (builds + runs against Mongo 3.2, smoke byte-identical)
|
||
|
||
`.meteor/release = METEOR@2.3`. `npm-mongo` **3.9.0** (mongodb driver 3.x) — the
|
||
last driver line that still speaks to a Mongo 3.2 server. Core bumps:
|
||
`accounts-*` 2.0, `ecmascript` 0.15.2, `mongo` 1.12.0, HMR added.
|
||
|
||
This escala was dominated by **dead Atmosphere packages** (the plan's #1 risk).
|
||
Root causes and fixes, in the order they were hit:
|
||
|
||
1. **`arkham:comments-ui` — no Meteor 2.x build.** The vendored local package
|
||
used `Npm.depends`, and rebuilding a *local* package's npm deps crashes
|
||
meteor-tool ≥1.11 on this host (`node_contextify.cc … Assertion
|
||
args[1]->IsString()` → SIGABRT; reproducible with any `Npm.depends`, even
|
||
`is-number`). Switching to the Atmosphere isopack avoided the local rebuild
|
||
but the package is abandoned: its newest 2.x-solvable version (0.2.15) pins
|
||
`accounts-password@1.0.1`, conflicting with Meteor 2.0's accounts-password 2.0.
|
||
**Decision (user-approved): reimplement comments as a small React feature**
|
||
(`imports/api/Comments/*`, `imports/ui/components/Comments/CommentsBox.js`),
|
||
dropping the package entirely. Fire-page comments only (the sole usage), with
|
||
likes/dislikes, image/YouTube embed, owner edit/remove, and the
|
||
"email other commenters" side-effect preserved. Comment text is now rendered
|
||
as **safe plain text** (+ media embed) instead of markdown-to-HTML — a small
|
||
scope reduction and a security improvement (no raw-HTML injection of user
|
||
content). The old local fork is preserved in its nested git repo
|
||
(commit `4761da9`).
|
||
|
||
2. **`coffeescript@1.0.17` build plugin — crashes the build.** The same
|
||
`node_contextify` assertion fired at *build start* on this host whenever the
|
||
coffeescript compiler plugin loaded. It was pulled transitively by the dead
|
||
**test stack** (`meteortesting:mocha`, `practicalmeteor:chai`,
|
||
`xolvio:cleaner`) and by the old auto-downgraded `nimble:restivus@0.6.6`
|
||
(which needs `iron:router`). Removing those removed coffeescript and
|
||
unblocked the build. A trivial `meteor create` app builds fine on the same
|
||
tool, confirming the crash is package-specific, not a broken tool.
|
||
|
||
3. **`nimble:restivus` — the REST API package — pins `accounts-password@1.3.3`**
|
||
(incompatible with 2.x) and is CoffeeScript (needs the crashing plugin).
|
||
**Vendored** as `packages/nimble-restivus`: the `.coffee` sources were
|
||
precompiled to plain JS (`lib/restivus-all.js`, single translation unit so
|
||
the `Auth`/`Route`/`Restivus` classes share scope; `Restivus` left
|
||
un-`var`'d so Meteor's `api.export` picks it up), and `package.js` loosens
|
||
`accounts-password` to 2.x and drops the coffeescript dependency. This also
|
||
let restivus resolve to 0.8.12 (json-routes based), which **removed the whole
|
||
`iron:router` stack**. REST behavior unchanged (smoke byte-identical).
|
||
|
||
4. **`maximum:server-transform` (unused) removal** broke
|
||
`Meteor.publishTransformed` in `FalsePositives/server/publications.js`; no
|
||
transform was actually configured, so replaced with plain `Meteor.publish`.
|
||
|
||
5. **`fourseven:scss` 4.5.4 → 4.14.1** — `node-sass@4.5.3` won't build on node 12
|
||
(Meteor ≥1.9); 4.14.1 uses a node-12-compatible node-sass with prebuilt
|
||
binaries. (Meteor 1.8 built it on node 8.) `4.15.0` needs ecmascript ≥ 0.15.1
|
||
→ too new for 1.11.
|
||
|
||
6. **`less`, `markdown` removed** — no `.less`/`.md` source files; their build
|
||
plugins were dead weight.
|
||
|
||
### ⚠️ Local build-host caveat
|
||
|
||
Meteor-tool's bundled node (12.x/14.x) SIGABRTs (`node_contextify` assertion) on
|
||
this machine (kernel 6.12) when certain old build plugins load — this is an
|
||
environment interaction, not a code defect (the deploy host built these fine
|
||
historically). It was fully worked around by removing the dead build plugins
|
||
above. If a future escala hits it again, build in a container matching the
|
||
deploy target (Debian) rather than on this host.
|
||
|
||
### Escalas 2.4 & 2.5 — ✅ green
|
||
|
||
`meteor update --release 2.4`, then `--release 2.5`. Both trivial; `npm-mongo`
|
||
stays at **3.9.1** (mongodb driver 3.x). REST smoke byte-identical against
|
||
`mongo:3.2` on both.
|
||
|
||
## Escala ceiling vs Mongo 3.2 — **landing on Meteor 2.5** (empirically verified)
|
||
|
||
Meteor bumps the bundled `mongodb` Node driver to **4.3.1 at Meteor 2.6**, which
|
||
requires server wire version ≥ 6 (**MongoDB ≥ 3.6**). Production's rsmain is
|
||
**Mongo 3.2** (wire version 4). Verified directly by booting 2.6 against a real
|
||
`mongo:3.2`:
|
||
|
||
```
|
||
MongoCompatibilityError: Server at localhost:27018 reports maximum wire
|
||
version 4, but this version of the Node.js Driver requires at least 6
|
||
(MongoDB 3.6)
|
||
=> Exited with code: 1
|
||
```
|
||
|
||
So **Meteor 2.5 is the highest release that runs against the production Mongo
|
||
3.2** (npm-mongo 3.9.1). That is where this branch lands: everything green, REST
|
||
smoke byte-identical. Going past 2.5 (to 2.16 or 3.x) is gated on upgrading the
|
||
shared rsmain replica set to Mongo ≥ 4.4 first — the same DB blocker as 3.x.
|
||
|
||
## ✅ Meteor 3.1 + MongoDB 7 — REST smoke GREEN (branch `meteor3-wip`)
|
||
|
||
**The 3.x jump works end-to-end for the REST/Flutter contract.** On branch
|
||
`meteor3-wip`, the web runs on **Meteor 3.1** (Node 22, async/await, no Fibers)
|
||
against a **dockerized MongoDB 7** (npm-mongo 6.10 / mongodb driver 6), and the
|
||
REST smoke test is **byte-identical to the 1.6.1.1 baseline** (all 12 endpoints).
|
||
This is the "mongo superior" decision realized: give fuegos its own Mongo 7
|
||
instead of the shared rsmain 3.2 — which is what unblocks Meteor 3's driver.
|
||
|
||
`meteor3-upgrade` stays at the deployable **Meteor 2.5** (Mongo 3.2). The 3.x
|
||
work lives on `meteor3-wip` until (a) production Mongo is migrated to 7 and
|
||
(b) the web-UI async migration below is finished.
|
||
|
||
### Reproduce the 3.1 dev stack
|
||
```bash
|
||
docker run -d --name tcef-mongo7 -p 27019:27019 mongo:7 --replSet rs0 --port 27019 --bind_ip_all
|
||
docker exec tcef-mongo7 mongosh --port 27019 --quiet --eval 'rs.initiate({_id:"rs0",members:[{_id:0,host:"localhost:27019"}]})'
|
||
# (migrations run from scratch on an empty DB — all up() bodies are async now,
|
||
# no need to pre-mark db.migrations at version 18)
|
||
export MONGO_URL="mongodb://localhost:27019/fuegos?replicaSet=rs0"
|
||
export NODE_OPTIONS="--dns-result-order=ipv4first --no-network-family-autoselection" # reach warehouse.meteor.com on Node 22
|
||
meteor --settings settings-development.json --port 3100
|
||
MONGO_CONTAINER=tcef-mongo7 MONGO_SHELL=mongosh MONGO_PORT=27019 ./smoke/smoke.sh
|
||
```
|
||
|
||
### Key build/runtime unblocks (Meteor 3.1)
|
||
- **underscore linker crash** (`Runtime is not available … underscore`): resolved
|
||
`underscore@1.6.2` had a runtime-less `web.browser` unibuild → pin
|
||
**`underscore@1.6.4`** (diagnosed by instrumenting the tool's `linker.js`).
|
||
- **collection2 v4** is fully lazy with no main module → import its eager entry
|
||
`meteor/aldeed:collection2/static.js` from `server|client/00-collection2-init.js`
|
||
so `attachSchema`/autoValues are patched before any collection loads.
|
||
- **Fibers gone**: removed `fibers.js`; server sync Mongo → `*Async` throughout
|
||
(Rest.js + helpers + methods + startup: oauth/subsUnion/migrations/email/facts).
|
||
- **vendored restivus** patched to `await` async endpoint handlers.
|
||
- **`$near` + `countAsync()`**: driver 6 runs `count` via aggregation where
|
||
`$near` is illegal → derive total from `fetchAsync().length`.
|
||
- **json-routes 3.0** matches routes in registration order → reordered the
|
||
`mobile/subscriptions/all/...` route before `.../:subsId` in Rest.js.
|
||
- **fixtures.js / sitemaps.js** disabled (sync-only `@cleverbeagle/seeder` /
|
||
pre-0.9 `gadicohen:sitemaps`) — not on the REST path. Debt.
|
||
|
||
### Web-UI async migration (beyond the REST contract)
|
||
- ✅ **Fires publications → async** (`fireFromId`, `fireFromAlertId`,
|
||
`fireFromActiveId`, `fireFromHash`): `findOne`→`findOneAsync`,
|
||
`count()`→`countAsync()`, `firesUnion` awaited, handlers `async` so the
|
||
try/catch actually catches rejections. Verified over raw DDP (all subs
|
||
`ready`, docs flow; fire + falsePositives docs delivered for the seeded
|
||
fire). `falsePositivesMyloc` / `industriesMyloc` / `comments.forReference`
|
||
needed **no change** — they return bare `find()` cursors (still sync-safe on
|
||
Meteor 3). `subscriptions.*` methods were already `*Async`.
|
||
- Note: the module-level `new Counter(...)` (natestrauser:publish-performant-counts)
|
||
behind `falsePositivesTotal` boots fine; the publication itself is unused by
|
||
the current UI — watch it if re-enabled.
|
||
- ✅ **Comments methods → async** (`comments.insert/edit/remove/like/dislike`):
|
||
`findOne/insert/update/remove` → `*Async`, helpers (`requireOwner`, `toggle`)
|
||
async, `users.findOneAsync` for the username. `onCommentAdd` mail fan-out:
|
||
`users.find().forEach` → `forEachAsync`. Still fire-and-forget from the
|
||
insert. Comments UI staging verification pending (pre-existing note below).
|
||
- ✅ **fixtures.js re-enabled** — `@cleverbeagle/seeder` (sync Mongo, no Meteor
|
||
3 support) **removed from package.json** and replaced with a small in-repo
|
||
async seeder: idempotent, dev-only (staging opts in with `TCEF_SEED=1`);
|
||
same accounts as before (`admin@admin.com` / 5 test users / 5 Documents
|
||
each). Verified: `fixtures: 6 dev users ensured` on boot.
|
||
- ✅ **sitemaps.js re-enabled** — `gadicohen:sitemaps` (pre-0.9 API, dead on
|
||
Meteor 3) **removed from .meteor/packages**; `/sitemap.xml` is now a small
|
||
`WebApp.connectHandlers` handler serving the same static page list (the
|
||
per-fire section of the old handler was behind `firesMapEnabled = false`,
|
||
i.e. dead code, and was dropped). Verified serving on dev.
|
||
- ✅ **Dead client packages purged — the web UI now RENDERS on Meteor 3.**
|
||
First-ever browser verification of this branch (the smoke only covers REST)
|
||
found a chain of ancient Blaze-era packages whose client code threw at
|
||
bundle load, killing the whole app (`meteorInstall is not defined`):
|
||
- **alanning:roles 1.2.10** (client crash, and its server
|
||
`Roles.userIsInRole` is sync) → package removed; the app barely used it:
|
||
`facts.js` now checks the plain `user.roles` field (admin set cached at
|
||
startup) and App.js takes `roles` from the user doc (prop was unused).
|
||
Upgrading to roles v4 was rejected: needs a prod data migration.
|
||
- **selaias:cookie-consent** (its `Cookies` global is gone) → removed;
|
||
replaced by in-repo React `imports/ui/components/CookieConsent` reusing
|
||
the same i18n keys; `CookieConsent.init` dropped from `i18n.js`.
|
||
- **natestrauser:publish-performant-counts** server `Counter._publishCursor`
|
||
uses sync `cursor.count()` → **vendored** as
|
||
`packages/publish-performant-counts` (`tcef:publish-performant-counts`)
|
||
with `countAsync` and an async-aware interval; same public API.
|
||
- `Meteor.autorun` (removed in Meteor 3) → dropped (App.js debug) /
|
||
`Tracker.autorun` (FiresMap).
|
||
- Map/server publications not exercised by the REST smoke converted to
|
||
async: `activefiresmyloc`, `activefiresunionmyloc`, `fireAlerts`
|
||
(countAsync/fetchAsync/awaited firesUnion), and `oauth.verifyConfiguration`
|
||
(findOneAsync). `migrations.js`: all 18 historical `up()` bodies ported to
|
||
async (`fetchAsync`+`for..of`, `*Async` writes, `createIndexAsync`,
|
||
`Accounts.createUserAsync`; percolate:migrations 2.0.1 awaits async `up()`).
|
||
Verified: empty Mongo 7 DB migrates 0→18 with no errors (indexes +
|
||
industry registries created); pre-marking `version=18` is no longer needed.
|
||
Verified in a real browser: `/` and `/fires` render (map, search, layers,
|
||
cookie banner), zero uncaught exceptions; REST smoke byte-identical.
|
||
- ✅ **React 16 → 18.3** — `react`/`react-dom` bumped to `^18.3.1`
|
||
(`--legacy-peer-deps`: the ancient react-* libs below declare 15/16 peers),
|
||
client entry migrated to `createRoot` (`imports/startup/client/index.js`).
|
||
The legacy UI libs (react-bootstrap 0.31, reactstrap 5-alpha, react-leaflet
|
||
1.8, react-router-dom 4, react-i18next 7) stay pinned and WORK on 18, but
|
||
spam the console with legacy-context/defaultProps deprecation warnings and
|
||
will die on React 19. **Debt**: own front-end refresh project.
|
||
⚠️ npm gotcha: running `meteor npm install/uninstall` while the dev server
|
||
watches node_modules can crash the meteor-tool watcher mid-prune (ENOENT on
|
||
a watched file). Stop the server (or restart it after) when touching deps.
|
||
|
||
### Still TODO before the web (not just REST) is fully deployable on 3.x
|
||
- ✅ **React 16 → 18** render root + ancient react-* libs modernized: react-helmet→
|
||
react-helmet-async, i18next 10→23 + react-i18next 7→14, react-bootstrap 0.31→2,
|
||
react-router-dom 4→6. Dead deps (reactstrap, react-leaflet-sidebarv2,
|
||
react-router-hash-link, react-addons-pure-render-mixin) dropped. All
|
||
browser-verified, REST smoke byte-identical. **Remaining front debt:**
|
||
react-leaflet 1.8→4 (deferred — core map, 4 v1-only plugins) and the
|
||
Bootstrap 4→5 CSS/JS jump (deferred — jQuery carousel/navbar). Both are what's
|
||
left before a React 19 jump.
|
||
- ✅ Re-enabled dev fixtures / sitemaps / email default template with async ports.
|
||
- ✅ SCSS `@import`→`@use`, `lighten`/`darken`→`color.adjust` (dart-sass deprecations).
|
||
- **Production cutover** still needs: Mongo data migrated 3.2→7 (mongodump/restore),
|
||
the notifications service emitting in prod, and the Docker deployment (fase 3).
|
||
|
||
## Dependency debt (tracked, to resolve in the noted escala)
|
||
|
||
| Dep | From | Target | Status |
|
||
|---|---|---|---|
|
||
| React / react-dom | 16.0 | 18 | ✅ done — 18.3.1 + createRoot. The peripheral react-* libs that only worked on 18 via legacy context are now all modernized (see rows below) **except react-leaflet** (deferred). What still blocks React 19: react-leaflet 1.8, plus our own `defaultProps` on function components and one `findDOMNode` (gadicc:blaze-react-component in Reconnect) — cosmetic, tracked. |
|
||
| Server async/await | Fibers | `*Async` APIs | ✅ done — REST, publications, methods, startup and migrations all on `*Async`. |
|
||
| bcrypt | 1.0.3 (broken binding) | 5.x | ✅ done — 1.0.3 didn't load on Node 22 (accounts-password silently fell back to pure JS) and wouldn't compile in the Docker build; 5.1.1 ships Node-22 prebuilds. |
|
||
| raven / flowkey:raven | 2.4 | @sentry/node + @sentry/browser 8.x | ✅ done — `flowkey:raven` (Sentry legacy SDK, dead on 3.x, spammed the boot log with 502s against the dead sentry.comunes.org) replaced by the modern SDKs behind the same `ravenLogger.log()` facade (6 call sites unchanged). Backend: **GlitchTip** self-hosted on `aaron` (Sentry-compatible DSN/API; Sentry proper needs 16 GB RAM, didn't fit), deployed via the Comunes ansible (`glitchtip.yml`, org "comunes" / project "tcef-web"), fronted at `https://sentry.comunes.org` (nginx on `assange` → `aaron:8000`). DSN set in `settings-development.json`; end-to-end verified (a test exception sent from the dev server showed up in GlitchTip within seconds). |
|
||
| nodemailer | 4 | 6.10 | ✅ done — drop-in for our usage: `email.js` only calls `nodemailer.createTransport(MAIL_URL)` and reads `transport.options.auth.user` (in the production-only `from()` branch); both verified unchanged in v6. Sending goes through `ostrio:mailer` (MailTime 2.5), which is v6-compatible. |
|
||
| Babel | 7 beta | 7 stable | ✅ done (escala 1) |
|
||
| react-meteor-data | 0.2.16 | 3.0.6 | ✅ done — 0.2.16 (React-15/16-era) looped `withTracker` on fire-detail pages under React 18 (never-ready → blank). `meteor add react-meteor-data@3.0.6`; no call-site changes (HOC API kept). Also dropped `tmeasday:check-npm-versions` (the constraint pinning 0.2.16). See section above. |
|
||
| i18next | 10.5 | 23.16 | ✅ done — with react-i18next 14. `whitelist`→`supportedLngs`, added `compatibilityJSON: 'v3'` (our locale JSON uses natural-language keys + v3 `_plural` layout, not the v4 `_one`/`_other` suffixes), custom separators `ß`/`ð`/`đ` kept. `sendMissing`→`saveMissing`, missingKeyHandler signature is `(lngs, ns, key, fallbackValue)` now (array first). See react-i18next row. |
|
||
| react-i18next | 7.4 | 14.1 | ✅ done — 48 `translate([], {wait:true})`/`translate()` HOCs → `withTranslation()`; `react.wait:true`→`react.useSuspense:false`. `<Trans>` unchanged (locale JSON already stores the indexed-tag format `<1><0>{{x}}</0></1>`). ReSendEmail: removed `<Interpolate>` (deleted in v10) → `<Trans i18nKey values={{email}}/>`, dropped the removed bare `t` export. This is what silenced the per-component `Translate`/`I18n` legacy-context console spam. Backends: xhr→`i18next-http-backend` (client), sync-fs→`i18next-fs-backend` (server); `i18next-localstorage-cache` dropped (was `enabled:false`); languagedetector 2→8. Browser-verified: es/en switch, `<Trans>` interpolation (`{{countTotal}}`+`<strong>`) renders on /fires. |
|
||
| react-leaflet + leaflet | 1.8 / 1.3.1 | 4.x / 1.9 | ⏸️ **deferred as debt** (the one lib not modernized this pass). react-leaflet v4 is a ground-up hooks rewrite of the app's core feature (the fire map), and the migration is deeply entangled: (a) **4 unmaintained v1-only plugins with no v4 equivalent** — `react-leaflet-control` (custom map buttons, 3 maps), `react-leaflet-fullscreen`, `react-leaflet-google` (`GoogleLayer` base layers in DefMapLayers), `leaflet-sleep` (`sleep`/`wakeTime`… props on `<Map>`) — each needs a bespoke `createControlComponent`/`createLayerComponent`/googlemutant reimplementation; (b) the **controlled-viewport** pattern (`onViewportChanged` + `state.center/zoom` in FiresMap/SelectionMap) is gone in v4 (`MapContainer` center/zoom are initial-only; you drive it via a `useMap()` child); (c) `.leafletElement` refs in 6 files/13 sites (FiresMap, SelectionMap, SubscriptionsMap, Fires, SubsUnion) — v4 hands you the `L.Map`/layer directly with different timing; (d) `Leaflet.control.graphicScale().addTo(map)` + `subsUnion` reach into the raw map via the ref. **v1.8 works on React 18** (verified: all maps render), so this only blocks the eventual React 19 jump. Do it as its own focused project with heavy in-browser verification (drag markers, viewport-driven fire reload, fitBounds, layer switch, fullscreen, sleep, custom controls). Stays pinned at 1.8 until then. |
|
||
| arkham:comments-ui | 1.4.x | — | ✅ replaced with in-repo React feature |
|
||
| nimble:restivus | Atmosphere | vendored | ✅ local precompiled package, accounts-password 2.x |
|
||
| maximum:server-transform, meteorhacks:zones, less, markdown, test stack | — | — | ✅ removed (dead/unused) |
|
||
| fourseven:scss | 4.5.4 | 4.14.1 | ✅ node-12 compatible |
|
||
| node-gcm | 1.0.2 | — | ✅ removed (dead API, moved to microservice) |
|
||
| react-router-dom | 4.2.2 | 6.30 | ✅ done — with `history` 4→5 and `react-router-bootstrap` 0.24→0.26. Kills the `Router`/`Switch`/`Route`/`Link`/`LinkContainer` legacy-context warnings. Strategy: keep the shared `history` **singleton** (used outside React by NotificationsObserver and Utils/location) via `unstable_HistoryRouter`, and DON'T rewrite the ~20 class pages — a new `withRouterCompat` HOC bridges v6 hooks back to v4-shaped `history`/`match`/`location` props. `<Switch>`→`<Routes>`, `component=`→`element=`, `Authenticated`/`Public` become guard components rendering `children` or `<Navigate replace>`, `LocationListener` is now a `useLocation`+`useEffect` function. The regex route `/fire/:type(active\|archive\|alert)/:id` → `/fire/:type/:id` (type validated in the component; the only behavior delta is an invalid two-segment type now hits Fires instead of 404 — negligible). `history.listen` callback is `({location})` in v5. Browser-verified: SPA nav, `/subscriptions`→`/login` auth redirect, deep-link `/fire/archive/:id` (params flow), browser back/forward; REST smoke byte-identical. Gotcha hit & fixed: the rewritten guard files need `import React` for their `<Navigate>` JSX. |
|
||
| react-helmet | 5.2.0 | react-helmet-async 2.0.5 | ✅ done — react-helmet is unmaintained and misbehaves under React 18 StrictMode. Drop-in: same `<Helmet>` children API across the 15 pages; `<HelmetProvider>` wraps `<App/>` in `imports/startup/client/index.js`. Verified in browser: per-page titles, meta description and hreflang alternates injected (`data-rh`). Note: head updates are rAF-deferred (like react-helmet), so hidden/background tabs don't flush — irrelevant for real users. |
|
||
| react-bootstrap | 0.31.5 | 2.10 | ✅ done — kills the biggest batch of React-19-blocking warnings (Grid/FormGroup/ControlLabel/Navbar.Header/Navbar.Brand/Checkbox/SafeAnchor all used legacy context/defaultProps). 29 files: `Grid`→`Container`, `FormGroup/ControlLabel/FormControl/HelpBlock`→`Form.Group/Label/Control/Text`, `Checkbox`→`Form.Check` (label moves to a prop), `bsStyle`→`variant` (×23, `"default"`→`"secondary"`), `bsSize`→`size` (×2), `pull-right`→`float-end` (×7). Custom `Col.js` (used v0.31 `bootstrapUtils`/`StyleConfig` internals) now re-exports v2's `Col`; custom `NavItem.js` rewritten self-contained (dropped `SafeAnchor`+`createChainedFunction`); `Navigation.js` drops the react-bootstrap `Navbar` (was hand-rolled raw markup anyway). **CSS kept at Bootstrap 4** (see next row) — the only BS5-only class v2 emits that BS4 lacks is `.form-label`, shimmed in `forms.scss`. Browser-verified: home carousel+navbar, signup form + terms `Form.Check` (toggles submit), login form; REST smoke byte-identical. |
|
||
| Bootstrap CSS/JS | 4.1 (alexwine:bootstrap-4) | 5.x | ⏸️ **deferred as debt.** The app's CSS+JS still comes from the Atmosphere `alexwine:bootstrap-4` (Bootstrap 4 + jQuery). The BS4→5 jump is a separate invasive sub-project: the home **carousel** uses the jQuery `bootstrap-carousel-swipe` plugin (BS4-only), the **navbar collapse** and ~17 `data-toggle`/`data-slide` attributes across 6 files are BS4 jQuery behaviors, and BS5 is vanilla-JS with `data-bs-*`. react-bootstrap v2 runs fine on BS4 CSS for every component we use (Button/Alert/Row/Col/Table/ButtonGroup/Modal render identical classes; only `.form-label` needed a shim). Do this jump on its own: npm `bootstrap@5` SCSS+JS, rewrite carousel (BS5 has native swipe → drop the plugin), `data-toggle`→`data-bs-toggle`, `ml-auto`→`ms-auto`, etc. |
|
||
| reactstrap | 5.0.0-alpha.3 | — | ✅ removed — zero imports in the codebase (react-bootstrap covers all UI). |
|
||
| react-leaflet-sidebarv2 | 0.5.1 | — | ✅ removed — never imported. |
|
||
| react-router-hash-link | 1.1.1 | — | ✅ removed — only a commented-out import in Index.js. |
|
||
| react-addons-pure-render-mixin | 15.6.2 | — | ✅ removed — never imported (React-15 era leftover). |
|
||
| chimp | 0.51.1 | — | ✅ removed — dead e2e test runner (Selenium 2 / cucumber), sole remaining puller of native `fibers@1.x`, which cannot compile on Node 22 and broke the clean Docker build. Not imported anywhere. |
|
||
|
||
### Comments React feature — verification note
|
||
|
||
The server builds and the REST smoke test is green, and the client bundle
|
||
compiles, but the comments UI itself is **not exercised by the smoke test**
|
||
(it's not part of the Flutter REST contract). It needs manual staging
|
||
verification: on a fire archive page — logged-in post/edit/remove, like/dislike,
|
||
and image/YouTube embed.
|
||
|
||
### ✅ Fixed: fire-detail pages now render (react-meteor-data 0.2.16 → 3.0.6)
|
||
|
||
Browser-verifying the Comments feature surfaced a **pre-existing client bug**:
|
||
the fire-detail pages (`/fire/{active,archive,alert}/:id`, component
|
||
`imports/ui/pages/Fires/Fires.js`) stayed blank — the page's `withTracker`
|
||
subscription (`fireFromActiveId` / `fireFromId`) never reached `ready()`, so
|
||
`loading` stayed true and nothing rendered, pegging a CPU core in a
|
||
re-subscribe loop.
|
||
|
||
Diagnosis (isolated, not guessed):
|
||
- **The publications were fine.** Subscribing to `fireFromActiveId` /
|
||
`fireFromId` *standalone* from the browser console readied immediately and
|
||
delivered the doc — not a server/publication bug.
|
||
- **The bug was `react-meteor-data@0.2.16`** — a React-15/16-era version
|
||
(`componentWillMount` + legacy context, the source of the deprecation
|
||
warnings flooding the console) that misbehaves under React 18 for a component
|
||
gating its whole render on `subscription.ready()`.
|
||
|
||
Fix: **`meteor add react-meteor-data@3.0.6`** (the React-18-compatible line for
|
||
Meteor 3.1; 4.x targets Meteor 3.2+). No call-site changes were needed — 3.x
|
||
keeps the `withTracker` HOC API, and all 19 call sites work unchanged. The old
|
||
constraint that pinned 0.2.16 was **`tmeasday:check-npm-versions`**, pulled in
|
||
by 0.2.16 itself; the upgrade dropped it, which also silenced the
|
||
"npm peer requirements not installed (react@15-16)" boot/console warning.
|
||
|
||
Verified in a real browser at `/fire/archive/c0000000000000000000000c`: the
|
||
page renders fully (title, Leaflet + NASA/satellite map, the "not a wildfire"
|
||
FalsePositives widget, and the **Comments** box — all `withTracker`-driven),
|
||
zero console errors. REST smoke byte-identical (12/12).
|