Skip to content

EPBDS-16211 H9: burn the dev-tooling audit down to zero, and record today's decisions - #128

Merged
AlexSamBY merged 1 commit into
masterfrom
h9-audit-burn-down
Sep 30, 2026
Merged

AlexSamBY merged 1 commit into
masterfrom
h9-audit-burn-down

Conversation

@AlexSamBY

Copy link
Copy Markdown
Member

Audit: 28 → 0

before after
npm audit 28 (2 critical, 11 high, 12 moderate, 3 low) 0
npm audit --omit=dev 0 0
  • npm audit fix moved 45 lock entries inside their declared ranges (Babel, PostCSS, browserslist, svgo, axios, ws, …). It closed 21 findings, both criticals among them (shell-quote, websocket-driver).
  • webpack-dev-server 5 → 6 closed webpack-dev-server, sockjs, uuid and qs: Express 5 resolves qs 6.16, where Express 4 pinned ~6.14.0. Only npm start uses the dev server, since Playwright builds the demo and serves it statically. Checked in the running demo: it compiles, hot-reloads over ws, and logs no error.
  • react-router and react-router-dom 6 → 7.18.4. The demo is the only user; 8.x needs React ≥ 19.2.7. Version 7 wraps location updates in startTransition by default. That brought back the extra history entry NavTabs.history.test.js pins: after a click the tabs were still given the previous location's tab for a render, StandaloneTabs reported it, and NavTabs navigated back to /. Traced with the tab reports logged. The demo now mounts BrowserRouter with useTransitions={false}, which is 6's synchronous update. Checked in the running demo: two tab clicks push two history entries, and Back then Forward push none. The test also loads the router after installing Node's TextEncoder, which 7 reads at module load and jsdom does not provide. That is scoped to this suite, the only one that loads the router.
  • moment dev copy ~2.29.4 → ~2.31.0 (GHSA-4p3w-j4w9-5jqw, a path traversal through a crafted locale name). The peer range stays ^2.29.4: the library never calls moment.locale, so the advisory is not reachable through it. Raising a host's floor is a consumer-facing decision this PR does not take.

The published package did not change

build-lib was run before and after, and all 282 files it publishes (dist/ and the root static/ payload) are byte-identical. That includes static/all.css at dcdb0a40…, the hash the plan has recorded since 2026-09-17. Two builds of the same tree were identical first, so the comparison is meaningful.

The plan

docs/UPGRADE-PLAN.md records the burn-down in the §10 gate table and the Appendix B baseline. It also records the decisions taken today:

  • TypeScript E2/E3 tail: GO (§9.6 governance).
  • moment: keep (§9.7-F2).
  • ESM: not now (§9.7-F3). Both motivations are gone: script-tag consumption is unsupported, and nothing is installed twice since dependencies was emptied.
  • Form-stack bump: postponed, and no longer a React 19 prerequisite (§9.7-F4, §8 gate 2). No host installs the form stack since the 2026-09-22 trim, so its react ^18 peer caps nobody, and the React 19 advisory job runs the whole suite on 6.5.9 green. The cost measured today on 7.0.1 is recorded:
    • commits at mount 107 → 118 and translate calls 1013 → 1046 over the 38 examples;
    • defaultValue is missing from the first render;
    • 5 tests fail, including a popup regression.

It also marks Phase 6 done in §10, and corrects two stale §8 rows (the semantic-ui-react blocker, gone with the package; propTypes importers 40 → 31).

Gates

All exited 0:

  • lint:js, lint:css, typecheck;
  • css:fixture:check, docs:props:check, docs:views:check;
  • test:env-flags, test:coverage, build, build-lib;
  • test:react16 and test:react17, 2907 tests on each;
  • npx jest, 2907 tests;
  • test:e2e, 41 Playwright tests;
  • the manifest contract.

🤖 Generated with Claude Code

…oday's decisions

npm audit: 28 findings (2 critical, 11 high, 12 moderate, 3 low) -> 0.
The production audit was 0 and stays 0.

- `npm audit fix` moved 45 lock entries inside their declared ranges and
  closed 21, both criticals among them.
- webpack-dev-server 5 -> 6 closed webpack-dev-server, sockjs, uuid and
  qs (Express 5 resolves qs 6.16). Only `npm start` uses it; the demo
  starts, hot-reloads over ws, and logs no error.
- react-router and react-router-dom 6 -> 7 (demo only; 8 needs
  React >= 19.2.7). Version 7 wraps location updates in startTransition,
  which brought back the demo's extra history entry:
  NavTabs.history.test.js caught it, and the demo now mounts
  BrowserRouter with useTransitions={false}. Checked in the running demo:
  two tab clicks push two entries, and Back and Forward push none. The
  test loads the router after installing Node's TextEncoder, which the
  router reads at module load and jsdom lacks.
- moment's dev copy ~2.29.4 -> ~2.31.0 (GHSA-4p3w-j4w9-5jqw). The peer
  range stays ^2.29.4: the library never calls moment.locale, and a
  host's floor is a consumer-facing decision.

All 282 files build-lib publishes are byte-identical before and after.

The plan records the burn-down and the decisions taken today:
- the TypeScript tail is a go;
- moment stays;
- ESM is not now;
- the form-stack bump is postponed, because since `dependencies` was
  emptied it no longer caps a host's React. Its cost on 7.0.1 is
  recorded: mount commits 107 -> 118 on the corpus, `translate` calls
  1013 -> 1046, `defaultValue` missing from the first render, and 5
  failing tests including a popup regression.
It also marks Phase 6 done in §10 and corrects two stale §8 rows.
@AlexSamBY
AlexSamBY merged commit 72874ea into master Sep 30, 2026
5 checks passed
@AlexSamBY
AlexSamBY deleted the h9-audit-burn-down branch September 30, 2026 11:00
AlexSamBY added a commit that referenced this pull request Sep 30, 2026
…#130)

## The problem, introduced by #128

#128 moved `webpack-dev-server` to 6.
`@pmmmwh/react-refresh-webpack-plugin` 0.5.17 declares it as an optional
peer only up to 5.x:

```
peerOptional webpack-dev-server@"3.x || 4.x || 5.x" from @pmmmwh/react-refresh-webpack-plugin@0.5.17
```

`npm ci` installs the lock regardless, which is why #128's and #129's CI
stayed green. But every `npm install <package>` since then aborts with
`ERESOLVE`. It was found while adding a devDependency for the React 19
leg.

## The fix

`@pmmmwh/react-refresh-webpack-plugin` `^0.5.16` → `^0.6.3`. 0.6.3
(2026-08-27) adds `webpack-dev-server` 6.x to that peer range
([#1046](pmmmwh/react-refresh-webpack-plugin#1046)
upstream). The one breaking release between them, 0.6.0, raised minimums
we already exceed (Node 18.12, webpack 5.2, webpack-dev-server 4.8). It
also removed `overlay.sock*` options we never set: the demo uses `new
ReactRefreshWebpackPlugin()` with no options.

## Checked

- **The install that failed now succeeds**, run as `npm install
--dry-run --save-dev <the same packages>`.
- **`npm ls --all` reports no invalid peer**, and `npm audit` stays at
0.
- **Fast refresh works on the new plugin.** In the running demo (`npm
start`), the refresh runtime is injected (`__reactRefreshInjected`). A
temporary edit to `NavTabs.jsx`, reverted after, is hot-applied: `[HMR]
Updated modules: ./src/demo/components/NavTabs.jsx` and `[HMR] App is up
to date.` A marker set on `window` beforehand survives, so the page did
not reload.
- **The published package is unchanged.** All 282 files `build-lib`
publishes are byte-identical to master's; the plugin is demo-only.

## Gates

All exited 0:
- `lint:js`, `lint:css`, `typecheck`;
- `test:env-flags`, `test:coverage`, `build`, `build-lib`;
- `test:react16` and `test:react17`, 2907 tests on each;
- the manifest contract.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
AlexSamBY added a commit that referenced this pull request Sep 30, 2026
On master since #130 merged, which fixed the `npm install` peer conflict
this change needed gone to add a devDependency. The rebased tree is
identical to the one the local gates below ran on.

## The flip

`peerDependencies`: `react` and `react-dom` now read **`^16.14.0 ||
^17.0.0 || ^18.0.0 || ^19.0.0`**. The range is widened additively, so
hosts on 16.14, 17 and 18 change nothing. This closes §8's fast path:
- gates 1 (Semantic UI React gone) and 3 (`defaultProps`, then the
automatic JSX runtime in #129) were met;
- gate 2 (the form stack) lapsed when `dependencies` was emptied (#128
records why);
- this PR is gate 4.

## How 19 is gated, in three matrices

| | 16.14 | 17 | 18 | 19 |
|---|---|---|---|---|
| whole jest suite | `test:react16` | `test:react17` | `npx jest` /
`test:coverage` | **`test:react19` (new)** |
| packed artifact server-rendered | `test:pack:peers` |
`test:pack:peers` | `test:pack:consumer` | **`test:pack:peers` (added
19.3.0)** |
| published `.d.ts` compiled | `test:types` | `test:types` |
`test:types` | **`test:types` (added `@types/react` 19.3.0)** |

- **The suite on React 19 is a fixture leg, like 16.14 and 17.**
`scripts/fixtures/react19` is an install-only package pinning
`react`/`react-dom` 19.3.0. Plain `npm ci` nests it, it is mapped in at
resolve time, and `npm run test:react19` runs it locally too. The
harness still asserts the loaded React (19.3.0) and scheduler (0.28) in
every worker.
- **What differs for 19.** React 19 has no legacy root and ships
`react-dom/client`, so the leg renders through `createRoot` as the
default suite does. `floors.js` records `legacyRoot: false`, which skips
three things that exist only for 16/17: the RTL `legacyRoot` patch, the
`ReactDOM.render` assertion and the `react-dom/client` stub.
- **CI.** The new `react-19` job replaces the `continue-on-error`
`react-19-advisory` job, which installed React 19 over the lock with
`--legacy-peer-deps`; the new job needs neither. No required checks are
configured on master today, so `react-19` gates exactly as
`react-16-floor` and `react-17` do. If branch protection ever named
`react-19-advisory`, it should name `react-19` now.
- **Types.** The published declarations compile against `@types/react`
19, with both the interop default import and the direct CommonJS import.
That matters because 19 removed the global `JSX` namespace. `gen-ts`
stays on the dev tree's 18; the plan's "pin `@types/react@19` for
`gen-ts`" item was written assuming the dev tree would move to 19, and
it does not.

## Docs

- README and the demo's docs page state the new range.
- The demo's changelog (Unreleased) says React 19 is supported and how
it is tested. It also records #129's change: the bundle imports
`react/jsx-runtime` from the host's React, which a host that aliases
`react` to one copy has to alias too. Two older Unreleased paragraphs
that said the range was not widened yet are updated, so the section does
not contradict itself.
- `CLAUDE.md` lists the three React legs.
- The plan closes §8's gate 4, the CI-coverage row, the `gen-ts` row,
the Appendix C "React 19 flip" checklist and §10's Phase 7 React 19
item.

## Gates

All exited 0:
- `lint:js`, `lint:css`, `typecheck`, `typecheck:contract`;
- `css:fixture:check`, `docs:props:check`, `docs:views:check`;
- `test:env-flags`, `test:types`, `test:coverage`, `build`;
- `test:pack`, `test:pack:peers` (16.14.0, 17.0.2, 19.3.0);
- `test:react16`, `test:react17`, `test:react19`, 2907 tests on each, as
on 18;
- `test:e2e`, 41 Playwright tests;
- the manifest contract.

Two first runs lost a jest worker to `SIGSEGV`, each killing one suite
before its tests ran: `test:react19` once and `test:react17` once. That
is the Node 24 V8 garbage-collector crash
([nodejs/node#62393](nodejs/node#62393)),
fixed on `v24.x-staging` but not in a release yet. Both passed 2907/2907
on rerun. CI runs Linux, where it has not been seen.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.

1 participant