Repository navigation
feat(hot): take over the last of webpack-dev-server's compiler and socket wiring - #2472
Conversation
…cket wiring Everything webpack-dev-server still wired up for itself, and the regressions a page using its client would see with this one in its place. - hot.client: false adds no runtime and still applies the plugin; the plugin goes to every compilation served, server bundles included - hot.client.transport accepts a module exporting a client of your own, provided as __webpack_dev_server_client__ - hot.ws is handed to the ws server: compression, verifyClient, or a port or server of its own, with cors and token still checked - the live connection is exported as webpack-dev-middleware/client/socket - client: malformed escapes in the page url, reload mode catching up after a restart, warnings alongside errors, webpack-dev-server's query spelling, an ES5 logger, overlay z-index and Esc, indicator without Shadow DOM and with progressbar semantics Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
…webpack-dev-server's client did A deprecated name found while reading the query is reported under the label and at the level the query asks for, and ?live-reload=true on its own is live reload, since that client read hot as off unless told. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
hot, liveReload and reconnect were never this client's query names; they arrive from entries written for webpack-dev-server's client, which is what webpack's guide shows for wiring it by hand. Only reload, autoConnect and timeout, which this client's own query offered, warn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
…warning Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
🦋 Changeset detectedLatest commit: 0c2aa2a The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
DO NOT MERGE THIS COMMIT. Revert it, and set the dependency back to a released `^8.4.0`, once webpack-dev-middleware ships. This branch needs APIs the published 8.3.0 does not have (`onConnect` with the request, `handleUpgrade`, `publish`, `publishTo`, `hot.ws`, `hot.client: false`, a client transport module, `client/socket`). CI installs from the registry, so without this `tsc` fails in the Build step and every job behind it is cancelled. This vendors a pack of webpack-dev-middleware `main` plus webpack/webpack-dev-middleware#2472 (feat/finish-dev-server-migration). The lockfile change is confined to that one dependency. To undo, with the release out: git revert <this commit> npm install webpack-dev-middleware@^8.4.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (1)
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review. WalkthroughThe changes add webpack-dev-server protocol support, custom client transports, and configurable WebSocket server listeners. They update browser-client query handling, reload behavior, message reporting, overlays, and progress indicators. The active socket connection is exposed through a package export. Build scripts generate ES5 logger modules, and tests cover the new runtime and client behavior. Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to The reviewed change adds coverage for the supported target spellings and runtimes, with no actionable merge risk identified in the supplied scope. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Organization UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
3833918b-f7fd-4c03-8ae4-88fcfab34aa4
📒 Files selected for processing (31)
.changeset/dev-server-client-parity.md.changeset/finish-dev-server-migration.md.cspell.jsonREADME.mdbabel.config.jsclient-src/clients/createSocket.jsclient-src/index.jsclient-src/indicator.jsclient-src/overlay.jsclient-src/socket.jspackage.jsonscripts/build-client-logger.mjssrc/hot.jssrc/options.check.jssrc/options.jsonsrc/servers/WebSocketServer.jssrc/utils.jstest/__snapshots__/validation-options.test.js.snap.webpack5test/client-bundle.test.jstest/client-socket.test.jstest/e2e/dev-server-parity.test.jstest/e2e/live-reload.test.jstest/e2e/transport.test.jstest/fixtures/custom-client-transport.jstest/hot.test.jstest/inject-client.test.jstypes/client/clients/createSocket.d.tstypes/client/socket.d.tstypes/hot.d.tstypes/servers/WebSocketServer.d.tstypes/utils.d.ts
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #2472 +/- ##
==========================================
+ Coverage 96.39% 96.55% +0.16%
==========================================
Files 24 24
Lines 2579 2642 +63
==========================================
+ Hits 2486 2551 +65
+ Misses 93 91 -2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
DO NOT MERGE THIS COMMIT. Revert it, and set the dependency back to a released `^8.4.0`, once webpack-dev-middleware ships. This branch needs what the published 8.3.0 does not have: `hot.protocol` with this server's runtime in `client/webpack-dev-server/`, `onConnect` with the request, `handleUpgrade`, `publishTo`, `hot.ws` and the `ws` server handed out as `hot.implementation`. CI installs from the registry, so without this `tsc` fails in the Build step and every job behind it is cancelled. This vendors a pack of webpack-dev-middleware `main` plus webpack/webpack-dev-middleware#2472 (feat/finish-dev-server-migration). The lockfile change is confined to that one dependency. To undo, with the release out: git revert <this commit> npm install webpack-dev-middleware@^8.4.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Organization UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
7cb54297-db22-40ad-985b-4b394e19d851
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (52)
.changeset/webpack-dev-server-protocol.md.cspell.jsonREADME.mdclient-src/clients/EventSourceClient.jsclient-src/webpack-dev-server/clients/WebSocketClient.jsclient-src/webpack-dev-server/globals.d.tsclient-src/webpack-dev-server/index.jsclient-src/webpack-dev-server/modules/logger/index.jsclient-src/webpack-dev-server/modules/logger/tapable.jsclient-src/webpack-dev-server/overlay.jsclient-src/webpack-dev-server/progress.jsclient-src/webpack-dev-server/socket.jsclient-src/webpack-dev-server/utils/log.jsclient-src/webpack-dev-server/utils/sendMessage.jspackage.jsonscripts/build-dev-server-logger.mjssrc/hot.jssrc/index.jssrc/options.check.jssrc/options.jsonsrc/servers/WebSocketServer.jssrc/utils.jssrc/webpack-dev-server.jstest/__snapshots__/validation-options.test.js.snap.webpack5test/client-transports.test.jstest/client/webpack-dev-server/ReactErrorBoundary.test.jstest/client/webpack-dev-server/__snapshots__/index.test.js.snap.webpack5test/client/webpack-dev-server/__snapshots__/socket-helper.test.js.snap.webpack5test/client/webpack-dev-server/bundle.test.jstest/client/webpack-dev-server/clients/WebsocketClient.test.jstest/client/webpack-dev-server/clients/__snapshots__/WebsocketClient.test.js.snap.webpack5test/client/webpack-dev-server/helpers/jsdom-setup.jstest/client/webpack-dev-server/index.test.jstest/client/webpack-dev-server/overlay-lifecycle.test.jstest/client/webpack-dev-server/socket-helper.test.jstest/client/webpack-dev-server/utils/__snapshots__/getCurrentScriptSource.test.js.snap.webpack5test/client/webpack-dev-server/utils/__snapshots__/log.test.js.snap.webpack5test/client/webpack-dev-server/utils/__snapshots__/sendMessage.test.js.snap.webpack5test/client/webpack-dev-server/utils/createSocketURL.test.jstest/client/webpack-dev-server/utils/getCurrentScriptSource.test.jstest/client/webpack-dev-server/utils/log.test.jstest/client/webpack-dev-server/utils/sendMessage.test.jstest/e2e/dev-server-parity.test.jstest/e2e/webpack-dev-server-runtime.test.jstest/validation-options.test.jstest/webpack-dev-server-protocol.test.jstsconfig.client.dev-server.jsontsconfig.client.jsontypes/hot.d.tstypes/index.d.tstypes/utils.d.tstypes/webpack-dev-server.d.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- .cspell.json
- test/e2e/dev-server-parity.test.js
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to GitHub limitations.
🟡 Minor · Keep the username and password separate until URL formatting. · index.js:830-843
client-src/webpack-dev-server/index.js:830-843
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winKeep the username and password separate until URL formatting.
runtimeQueryemitsusernameandpasswordseparately, butcreateSocketURLjoins them beforeformatURLsplits at the first colon. Forusername=a%3Ab&password=pw, the socket URL becomesws://a:b%3Apw@example.com/ws, encoding usernameaand passwordb:pwinstead of the configured values. An endpoint that uses these credentials can reject the connection. Pass both fields separately toformatURL, and add this case to thecreateSocketURLtests.Suggested fix
--- a/client-src/webpack-dev-server/index.js +++ b/client-src/webpack-dev-server/index.js @@ - * @param {{ protocol?: string, auth?: string, hostname?: string, port?: string, pathname?: string, search?: string, hash?: string, slashes?: boolean }} objURL object URL + * @param {{ protocol?: string, username?: string, password?: string, hostname?: string, port?: string, pathname?: string, search?: string, hash?: string, slashes?: boolean }} objURL object URL @@ - let auth = objURL.auth || ""; - - if (auth) { - // Only the first colon separates the username from the password. - const separator = auth.indexOf(":"); - - auth = - separator === -1 - ? encodeURIComponent(auth) - : `${encodeURIComponent(auth.slice(0, separator))}:${encodeURIComponent(auth.slice(separator + 1))}`; + let auth = ""; + + if (objURL.username) { + auth = encodeURIComponent(objURL.username); + + if (objURL.password) { + auth += `:${encodeURIComponent(objURL.password)}`; + } + auth += "@"; } @@ - let socketURLAuth = ""; + let socketURLUsername = ""; + let socketURLPassword = ""; @@ - socketURLAuth = decodeAuth(parsedURL.username); + socketURLUsername = decodeAuth(parsedURL.username); @@ - socketURLAuth = socketURLAuth.concat(":", decodeAuth(parsedURL.password)); + socketURLPassword = decodeAuth(parsedURL.password); @@ - auth: socketURLAuth, + username: socketURLUsername, + password: socketURLPassword, --- a/test/client/webpack-dev-server/utils/createSocketURL.test.js +++ b/test/client/webpack-dev-server/utils/createSocketURL.test.js @@ [ "?hostname=example.com&username=user&password=pa:ss", "http://example.com", "ws://user:pa%3Ass@example.com/ws", ], + [ + "?hostname=example.com&username=a%3Ab&password=pw", + "http://example.com", + "ws://a%3Ab:pw@example.com/ws", + ],
🟡 Minor · Limit the raw-client statement to built-in transports. · README.md:1018-1021
README.md:1018-1021
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winLimit the raw-client statement to built-in transports.
When the runtime uses a custom transport, the exported
clientis the adapter. TheCommunicationClienthooks do not require a.clientproperty, so the documented listener can skip attaching to a raw connection. State that the guarantee applies to built-in transports and that custom transports need not expose a native client.Suggested fix
-// `client` is live: `null` before the runtime starts to connect and while it -// waits to reconnect. `client.client` is the `WebSocket` or `EventSource` -// underneath, a new one for each connection, so a listener added to it hears -// that connection only. +// `client` is live: `null` before the runtime starts to connect and while it +// waits to reconnect. With a built-in transport, `client.client` is the +// `WebSocket` or `EventSource` underneath, a new one for each connection, so +// a listener added to it hears that connection only. Custom transports need +// not expose a native client.
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Organization UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
1685de6d-ac30-47fe-bc0b-f0c76e4a17bd
📒 Files selected for processing (8)
README.mdclient-src/clients/createSocket.jsclient-src/socket.jsclient-src/webpack-dev-server/index.jssrc/utils.jstest/client/webpack-dev-server/utils/createSocketURL.test.jstest/webpack-dev-server-protocol.test.jstypes/client/clients/createSocket.d.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- client-src/socket.js
- client-src/clients/createSocket.js
- test/webpack-dev-server-protocol.test.js
- types/client/clients/createSocket.d.ts
- src/utils.js
- README.md
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 6 remain after this review.
DO NOT MERGE THIS COMMIT. Revert it, and set the dependency back to a released `^8.4.0`, once webpack-dev-middleware ships. This branch needs what the published 8.3.0 does not have: `hot.protocol` with this server's runtime in `client/webpack-dev-server/`, `onConnect` with the request, `handleUpgrade`, `publishTo`, `hot.ws` and the `ws` server handed out as `hot.implementation`. CI installs from the registry, so without this `tsc` fails in the Build step and every job behind it is cancelled. This vendors a pack of webpack-dev-middleware `main` plus webpack/webpack-dev-middleware#2472 (feat/finish-dev-server-migration). The lockfile change is confined to that one dependency. To undo, with the release out: git revert <this commit> npm install webpack-dev-middleware@^8.4.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
DO NOT MERGE THIS COMMIT. Revert it, and set the dependency back to a released `^8.4.0`, once webpack-dev-middleware ships. This branch needs what the published 8.3.0 does not have: `hot.protocol` with this server's runtime in `client/webpack-dev-server/`, `onConnect` with the request, `handleUpgrade`, `publishTo`, `hot.ws` and the `ws` server handed out as `hot.implementation`. CI installs from the registry, so without this `tsc` fails in the Build step and every job behind it is cancelled. This vendors a pack of webpack-dev-middleware `main` plus webpack/webpack-dev-middleware#2472 (feat/finish-dev-server-migration). The lockfile change is confined to that one dependency. To undo, with the release out: git revert <this commit> npm install webpack-dev-middleware@^8.4.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
…xample `client/socket`'s `client` is now typed with its `client`, the `WebSocket` or `EventSource` underneath, which the documented usage reads. It is a type of that export's own, so the contract a custom transport implements is unchanged. The README's example reads it only when there is one, and both it and the module's comment now say when that is: from when the runtime starts to connect, with a new one for each connection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
A universal build (`target: "universal"` or `["web", "node"]`) is one bundle for a page and for Node. Tests build both spellings with the client over each transport, and through a re-export that hands it its query: Node runs the bundle with no error, no output from the client and nothing left running, and a browser updates the page in place and shows and clears the overlay. The check that the client is in the bundle takes either path separator, as webpack writes Windows paths with backslashes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
7eb4444 to
3c41aec
Compare
`@pmmmwh/react-refresh-webpack-plugin` applies Fast Refresh through this client, so a change to how it applies updates shows here, over each transport: - a component keeps its state across an edit, and across several; - an edit to another module updates it and keeps its parent's state; - a component whose hooks changed is started over, in place; - a build error shows the overlay, and the fix is applied in place; - an error thrown while rendering shows, and the root renders again; - an edit no component accepts reloads the page. The sources are written as `react-refresh/babel` leaves them, since babel-loader cannot load `@babel/core` in this test runner on every Node.js version CI uses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
DO NOT MERGE THIS COMMIT. Revert it, and set the dependency back to a released `^8.4.0`, once webpack-dev-middleware ships. This branch needs what the published 8.3.0 does not have: `hot.protocol` with this server's runtime in `client/webpack-dev-server/`, `onConnect` with the request, `handleUpgrade`, `publishTo`, `hot.ws` and the `ws` server handed out as `hot.implementation`. CI installs from the registry, so without this `tsc` fails in the Build step and every job behind it is cancelled. This vendors a pack of webpack-dev-middleware `main` plus webpack/webpack-dev-middleware#2472 (feat/finish-dev-server-migration). The lockfile change is confined to that one dependency. To undo, with the release out: git revert <this commit> npm install webpack-dev-middleware@^8.4.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Organization UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
0ba8706c-e994-42c0-97b0-f21269ffecfd
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (15)
.cspell.jsonREADME.mdpackage.jsonsrc/hot.jssrc/options.check.jssrc/options.jsonsrc/servers/WebSocketServer.jssrc/utils.jstest/__snapshots__/validation-options.test.js.snap.webpack5test/e2e/dev-server-parity.test.jstest/e2e/react-refresh.test.jstest/e2e/universal-target.test.jstest/universal-target.test.jstypes/hot.d.tstypes/utils.d.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- test/snapshots/validation-options.test.js.snap.webpack5
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.
The helper made its directory and started a watcher before anything was handed back, so an error on the way left both behind. The watcher is now closed either way, and the directory removed when nothing is returned to remove it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
DO NOT MERGE THIS COMMIT. Revert it, and set the dependency back to a released `^8.4.0`, once webpack-dev-middleware ships. This branch needs what the published 8.3.0 does not have: `hot.protocol` with this server's runtime in `client/webpack-dev-server/`, `onConnect` with the request, `handleUpgrade`, `publishTo`, `hot.ws` and the `ws` server handed out as `hot.implementation`. CI installs from the registry, so without this `tsc` fails in the Build step and every job behind it is cancelled. This vendors a pack of webpack-dev-middleware `main` plus webpack/webpack-dev-middleware#2472 (feat/finish-dev-server-migration). The lockfile change is confined to that one dependency. To undo, with the release out: git revert <this commit> npm install webpack-dev-middleware@^8.4.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
… here (#5750) * refactor(client): take the WebSocket transport from dev-middleware First step of moving the hot clients into webpack-dev-middleware: this package's `WebSocketClient` is a strict subset of the one that package now ships, and worse in three ways. It has no `close()` at all, despite declaring `@implements {CommunicationClient}`, which the interface requires. It has no guard against an event the socket had already queued reporting after the caller closed, so a close could schedule a reconnection nobody asked for. And it hands the url to `new WebSocket` unresolved, which throws on browsers before Chrome 125 / Firefox 124 / Safari 17.3 for a relative or `http(s):` url — this package always builds an absolute `ws:` url, so that one never bit here, but it is a trap for anyone reusing the class. Re-exported rather than deleted: `client.webSocketTransport` resolves to this path, so anything pointing at it keeps working. `import/no-unresolved` is switched off for the file, following the exemption already in place for `@changesets/get-github-info`: the import resolver cannot follow an `exports` subpath. TypeScript does resolve it, so `lint:types-client` still covers the import. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * refactor(client): take the error overlay from dev-middleware too 692 lines of overlay deleted in favour of the one dev-middleware ships — the state machine, the iframe, the rendering, the runtime-error listeners. What is left is an adapter: this package's `createOverlay`/`send` shape on top of that overlay's `showProblems`/`clear`, plus `formatProblem`, which stays because the console uses it and because this package still receives webpack's error objects rather than formatted strings. Three things had to be carried across so nothing outward changes: * the element id. It is what a test, a screenshot tool or an integration finds the overlay by, so `webpack-dev-server-client-overlay` is passed through dev-middleware's new `overlay.id` rather than renamed; * the Trusted Types policy name. Under an enforced `require-trusted-types-for 'script'` the page's CSP allowlists a policy by name, and dev-middleware's default is a different one, so `webpack-dev-server#overlay` is passed explicitly — including where this package's option is `false`, which means "no name of my own"; * `/webpack-dev-server/open-editor`. dev-middleware leaves the endpoint empty by default, having no route to point at, so the file references in a problem would have stopped being clickable. The first two came from the e2e suite failing, not from reading the code, which is the argument for swapping under the tests rather than after. What does change is the overlay's internal DOM, which is not an interface this package documents: a clickable file reference is `[data-open-file]` rather than `[data-can-open]`, and a problem is headed by its level and origin rather than "Compiled with problems". Two tests assert on those and move with the implementation. Checked against a baseline taken first: the overlay suite fails exactly the set it fails on a clean `main` in this container — no case newly failing, none newly passing — and the client and web-socket-url suites are unmoved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * feat(client): add Server-Sent Events as a second transport `client.webSocketTransport: "sse"` connects the page with `EventSource` instead of a WebSocket. The endpoint is webpack-dev-middleware's hot endpoint, which is an ordinary response rather than an upgrade, so there is no second server to start and it sits in the middleware chain where everything this package puts in front of it can still see it. Naming it as the client's transport picks the endpoint that serves it, so `webSocketServer` does not have to be set as well; `webSocketServer: "sse"` is accepted too. `lib/servers/EventSourceServer.js` is the adapter between that endpoint and the transport shape the rest of this package is written against, so nothing above the wire can tell which one it is talking to — same protocol, same messages, same options. All of the protection stays here: the endpoint hands over the request each client connected with, and `allowedHosts` and the origin check decide as they do for a socket. One difference in how that check reads a request. A browser puts an `Origin` on a WebSocket handshake whether or not it is cross-origin, so one without it is refused; `EventSource` sends no `Origin` on a same-origin request, so for a stream an absent one is taken as a page of this server's own. A stream that does carry an `Origin` — which is what a cross-origin page sends — is checked as before, and the `Host` check runs either way. Both transports are the middleware's now, re-exported from `client-src/clients/`, and each has a test of its own: the contract both answer, plus the silence watchdog only a stream needs. `test/e2e/event-source.test.js` drives a real browser through a hot update over the stream, the handshake it is greeted with, and both sides of the host check. `socket.js` no longer throws on a frame it cannot parse. A stream's keep-alive has to be a `data:` frame rather than a comment, or the client's own watchdog would count a quiet connection as a dead one, so something that is not JSON now arrives on the wire by design. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * refactor(client): no overlay code left to adapt `client-src/overlay.js` was the last of it: an adapter whose substance was turning webpack's error objects into the shape the shared overlay renders, and a `send({ type })` state machine over `showProblems`/`clear`. Both belong elsewhere. The formatting is webpack-dev-middleware's `client/problem` now, and the state machine was standing in for per-source slots that overlay already has, so the events map onto two calls. What stays is this package's identity on top of a shared overlay, as options: the `webpack-dev-server-client-overlay` element id, so anything querying it is unaffected; the `webpack-dev-server#overlay` Trusted Types policy name, which a page's CSP allowlists by name; and the `/webpack-dev-server/open-editor` route that makes a file reference clickable. `test/client/ReactErrorBoundary.test.js` goes with it — the heuristic it covers lives in webpack-dev-middleware, which tests it. Requires webpack/webpack-dev-middleware#2438. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * feat: reuse webpack-dev-middleware's browser runtime This server has no client of its own any more. `client-src/` is gone — the runtime, the error overlay, the progress indicator, the logger, the socket wrapper and the WebSocket client, around two thousand lines that existed in near-duplicate in both projects — and the middleware's hot option drives all of it, including putting the entry and `HotModuleReplacementPlugin` into the compilation. Nothing about a configuration changes. Every option keeps its name and its meaning and is mapped onto the middleware's vocabulary in `lib/hotOptions.js`: `hot`/`liveReload` become one `apply` mode, `client.reconnect` becomes `connect.retries`, `client.webSocketURL` is already the shape the middleware's `hot.client.path` takes, and the overlay is passed through with this project's own element id so anything querying for it still finds it. The `webpack-dev-server/client/*` paths are generated re-exports of the middleware's client, so importing them resolves to what it always did. The paths that were only this bundle's internals are no longer published. Protection stays here. `allowedHosts`, the `Origin` checks and the same-origin rule are unchanged: the middleware is handed `cors: true` and no token, which is it applying no policy of its own, and every connection is judged here before anything is published to it. A handshake is refused before it completes rather than after, since the middleware exposes the upgrade rather than taking the server over. A `webSocketServer` of your own still works. A class, a module exporting one, or options giving the socket a port or a server of its own are wrapped into the shape the middleware asks a custom transport for, so there is one path from a build to a page either way, and `BaseServer` keeps its export. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * refactor: serve "sse" from the middleware's own transport The adapter this branch added, `lib/servers/EventSourceServer.js`, existed to dress the middleware's hot endpoint up as the transport shape this server was written against. There is no such shape any more — the middleware owns both ends of both transports — so naming `sse` is now just choosing one of its transports, and the adapter and its special case in `normalizeOptions` are gone with it. `client.webSocketTransport: "sse"` and `webSocketServer: "sse"` keep working and keep picking each other, which is what the option values are for. The published `client/clients/EventSourceClient.js` path is generated alongside its WebSocket neighbour, so a transport named by module still resolves. One thing this caught: `"sse"` was being read as an implementation of this server's own, since the test for that was "not `ws`" — which sent it looking for a class to construct and failed before a page could connect. Both of the middleware's transports are named now, and a smoke run over each confirms the endpoint answers: `426` to a plain GET on the WebSocket one, an event stream on the other. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * fix: keep this package's name and its refusals in the browser console Two things the browser used to say that the middleware's runtime had no way to say for it. The console prefix: every message read `[webpack-dev-server]`, and four snapshots record exactly that, including `Invalid Host/Origin header` — a line people search for. Taking the runtime as-is would relabel all of it with a dependency's name and send anyone with a problem to the wrong repository, so the name travels with the logging option. The refusal itself: a client turned away by `allowedHosts` or the origin check is told why, and the reason is logged in the page. That needs the handshake to complete, so the upgrade is answered even for a client that will be refused and `guardHotEndpoint` closes it immediately after saying so — which is what this server has always done. Owning the `upgrade` listener is still what keeps a proxied upgrade with whoever it belongs to; `isHotEndpoint` went with the earlier attempt to refuse before the handshake, which would have made the reason unreachable. Verified in a browser: the console carries `[webpack-dev-server]`, and a client from a disallowed origin receives `{"action":"error","message":"Invalid Host/Origin header"}` and is closed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * test: wait for a build rather than the handshake's configuration push Two proxy cases waited for `{ type: "hot" }`, which was this server telling a client what to enable right after the handshake. That configuration is in the client's entry query now, so the first thing the socket says is the build the page is to run. What the cases are about is untouched and still asserted: the HMR upgrade must not reach the proxy target (`backendUpgradeCount` stays `0`), and must not make the proxy log an error. Both hold. `test/server/open-option.js` fails 33/33 here and on `main` alike — it drives a real browser through `open`, which this environment has none of. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * fix: refuse an event-stream client, not just a WebSocket one `allowedHosts` and the origin check were not being enforced on the `sse` transport. The guard ran and decided to refuse, and then neither half of the refusal landed: the message went through `sendMessage`, which tests `readyState === 1` and calls `client.send` — a `ServerResponse` has neither, so the loop skipped it — and `client.close()`, which a `ServerResponse` also does not have, threw and was swallowed. The stream stayed open and kept receiving builds. The message goes through the middleware's `publishTo` now, which puts it on the wire the client is actually on, and the connection is dropped the way that client is dropped: a WebSocket closes, a response ends. Verified on both transports: a disallowed origin is told `Invalid Host/Origin header` and dropped, over a WebSocket and over a stream alike — and a same-origin page, which `EventSource` gives no `Origin` at all, is still let through. Found via a CodeRabbit review of the middleware's README example, which had the same two mistakes in it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * fix: treat an empty `webSocketURL` part as one that was not given A `client.webSocketURL` written as a string is normalized by parsing it, which fills in every part — as `""` for the ones the url did not carry. Those were passed straight through to the middleware, which refuses an empty port: so `webSocketURL: "ws://example.test/ws"` stopped the server from starting at all. Empty has always meant "not said" here, and both the old client and the new one resolve such a part from the page, so it is left out rather than sent. The `allowed-hosts` snapshots are regenerated with it. Three changes, all consequences of the runtime moving rather than of anything going wrong: `connected` is logged where this server used to log which features were enabled, `[HMR] Waiting for update signal from WDS...` is gone with the `webpack/hot/dev-server` entry that printed it, and the prefix is still `[webpack-dev-server]`. The suite is 31 of 32 here; the one failure is an IPv6 case, and this container cannot listen on `::1` at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * fix: drop a test of a file that no longer exists, and regenerate the types Two things CI's Lint job reports that are not about the dependency. `test/client/clients/EventSourceClient.test.js` was added earlier on this branch and imports `client-src/clients/EventSourceClient.js`, which is gone. It never survived the rebase as a deletion because it did not exist on the side that deleted the rest of `test/client/`. Every case in it tests the middleware's own stream client through a re-export, and the middleware's `test/client-transports.test.js` already covers all six — plus the ones only a stream has — so nothing is lost. `types/` is tracked and the Lint job fails with "Missing types" when a build changes it. `Server.d.ts` still described the removed methods, and the two new modules had no declarations at all. The changeset now says the two public methods that went with them, `getClientEntry()` and `getClientHotEntry()`, are a breaking removal. It recorded the rest of the removal and not that. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * test: regenerate the web-socket-server-url console snapshots The suite reads the browser's console, which is now the middleware's client talking. Every difference is one the migration makes on purpose: - the startup banner, `Server started: Hot Module Replacement enabled, …`, and `[HMR] Waiting for update signal from WDS...` are gone — the old client printed the first from the configuration this server pushed after the handshake, and the second came from the `webpack/hot/dev-server` entry this server no longer adds; - `Hey.` comes first, because the page no longer waits on a banner; - the reconnect cases gain `Trying to reconnect...` and `Disconnected!`. Regenerated only after reading what kind of failure each of the 34 was: all snapshot mismatches, none of them an error. One test here was worse than a mismatch. "should not work and output disconnect wrong web socket URL" waits for `Disconnected!` with no timeout, and the middleware's client did not print it, so it spun forever with its server and browser open and took the whole file to the runner's 400 s limit. That is fixed in webpack-dev-middleware, which now says so when the connection goes away; with it the file runs in 53 s and passes twice running. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * fix: keep manual client entries, page-url opt-outs and a hand-made middleware working - `client/index.js` is now CommonJS and leaves the query written on it where the middleware's client reads it, so `client/index.js?...` entries keep their options instead of connecting with defaults. - Name the client's page-url parameters after this package again, so `?webpack-dev-server-hot=false` and `?webpack-dev-server-live-reload=false` still opt a tab out. - `client: false` no longer throws while reading `client.webSocketTransport`. - A middleware mounted through `setupMiddlewares` has no hot endpoint of this server's, so there is nothing to guard. - Update the e2e expectations for the middleware's client: its messages, the order of `connected` after the page's own output, and a wait that no longer polls forever for a message the client does not log. - Drop the tests of the removed `getClientEntry()`/`getClientHotEntry()`. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * test: bring the end-to-end suites in line with the middleware's client Every difference is one the migration makes on purpose, and the ones that were not are fixed: - Provide the transport through `__webpack_dev_server_client__` again for `ws` and `sse` too, as the global has always held the one in use, and have the `client/` re-exports hand back the default export of the modules that have one. - Point the overlay at this server's open-editor route. - Expect what the middleware's client logs, with the build's timing and absolute paths normalized, and its message for each way an update goes. - Port the tests that reached into the removed WebSocket server: the heartbeat, the client count, the host check on the event stream, and the federation entries, which are now checked on a browser target — a build for node is given no client. - Replace the tests of the old progress element with ones of what `client.progress` promises, the indicator's own being the middleware's. - Add unit tests for the options this server hands the middleware. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * test: update the command line tests for the middleware's client, and say what differs The hot option is now visible in the client's entry rather than in a webpack/hot entry, and a build for node is given neither. The changeset lists what a page and its console look like now, which it had called nothing. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * test: record what a CI browser sees, not what a sandbox does Snapshots regenerated in a sandbox carried its differences with them: - The overlay's style attributes came out with border: none, where the browser CI uses writes the four longhands the committed snapshots have. - A sandbox proxy refuses connections to the addresses the web socket URL tests use with a 403, so those entries recorded a refusal where CI connects or fails to resolve the name. - The IPv6 entry of the allowed hosts test could not run there, so its snapshot was dropped by the update. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * test: write the changed file's path the same on every platform A rebuild's message names the file that changed, and a Windows machine writes that path with backslashes, so the snapshots that record it could only match on a machine that does not. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * test: wait for the client to give up on a name that never resolves How long a machine takes to say a name does not resolve is its own, and the page was idle before the answer came on one runner, so the snapshot was taken of "Hey." alone. The test now waits for the message that ends the sequence it records. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * test: wait for the worker's client to connect before the snapshot The worker's own client connects after the page has settled, so on a slow runner the snapshot could see one "connected" line instead of two. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * chore: merge this change's changesets into one entry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * feat: leave the last of the compiler and socket wiring to the middleware dev-server no longer applies a ProvidePlugin or HotModuleReplacementPlugin, or runs a WebSocket server of its own: `client: false`, a custom `client.webSocketTransport`, `webSocketServer.options` (a port or server of its own, verifyClient, compression) and the HMR plugin for every compilation are webpack-dev-middleware's `hot` options now. lib/servers/WebsocketServer.js and bridge.js are gone; BaseServer stays as the contract for a `webSocketServer` implementation of your own. And what a page or a tool written against 6.0 relies on keeps working: - `client/socket.js` and this server's own `{ type }` messages, which React Refresh's overlay reads off the socket - `webSocketServer.clients` and `.implementation`, `sendMessage()` (deprecated; `static-changed` reloads), `getClientEntry()` and `getClientHotEntry()` (deprecated) - the Trusted Types policy `webpack-dev-server#overlay`, progress off unless asked, a one second ping, overlay filters handed `{ message }` - a hand-written `client/index.js` entry connects to `/ws` as before - a `setupMiddlewares` that replaces the dev middleware (a Hono app) keeps the hot endpoint - the editor is opened on the file resolved against webpack's context Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * fix: keep a subclass's client entries, the client paths in use and universal builds working - `getClientEntry()` and `getClientHotEntry()` overridden by a subclass put their modules in `entry` again, the client one with the query this server always wrote, and the middleware then adds no client of its own. - `client/` keeps only the paths something outside this package uses: `index.js`, written into `entry` by hand; `socket.js`, which React Refresh reads; and `clients/WebSocketClient.js`, which a transport of someone else's extends, still with `default` for `require()`. - `client/index.js` hands its query over only where there is a `self`, so a universal build runs in Node without it throwing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * test: main's suite on the middleware's client, and React Refresh's hot scenarios main's tests run as they are wherever they still pass; the rest only record what the middleware's client says and shows, in its console, overlay and progress indicator. A subclass's `getClientEntry()` and `getClientHotEntry()` keep main's test. React Refresh is tested with `@pmmmwh/react-refresh-webpack-plugin` itself: a component keeps its state across one edit and several, a child module updates in place, changed hooks remount in place, its overlay shows a build error and an error thrown while rendering and clears once fixed, an unaccepted edit reloads, `hot: "only"` does not, and all of it over Server-Sent Events too. A universal build is served to a page and run in Node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * chore(ci): TEMPORARY - install the unreleased webpack-dev-middleware DO NOT MERGE THIS COMMIT. Revert it, and set the dependency back to a released `^8.4.0`, once webpack-dev-middleware ships. This branch needs what the published 8.3.0 does not have: `hot.protocol` with this server's runtime in `client/webpack-dev-server/`, `onConnect` with the request, `handleUpgrade`, `publishTo`, `hot.ws` and the `ws` server handed out as `hot.implementation`. CI installs from the registry, so without this `tsc` fails in the Build step and every job behind it is cancelled. This vendors a pack of webpack-dev-middleware `main` plus webpack/webpack-dev-middleware#2472 (feat/finish-dev-server-migration). The lockfile change is confined to that one dependency. To undo, with the release out: git revert <this commit> npm install webpack-dev-middleware@^8.4.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * refactor: follow the middleware's hot.client.url and pageParamPrefix names Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * chore(ci): TEMPORARY - re-vendor webpack-dev-middleware with the settled option names Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * test: check the hot option against webpack-dev-middleware's schema Builds the middleware from each shape of configuration, so a name renamed on either side fails here, and checks a hand-written client entry still honours this server's page-url parameters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * refactor: hand a custom client to hot.client.module, and turn off both halves together Follows webpack-dev-middleware's split: `client.webSocketTransport` naming a module now maps onto `hot.client.module`, with `transport` left as the wire; and "add nothing" is `client: false` plus `inject: false`, since `inject: false` alone now only leaves the plugin out. Re-vendors the middleware build carrying the change (TEMPORARY until it is released). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * refactor: map client: false onto the middleware's hot.inject enum Reverts the `hot.client.module` mapping, so `client.webSocketTransport` goes to `hot.client.transport` again; `client: false` becomes `inject: "plugin"` with `hot` and `inject: "none"` without it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * chore(ci): TEMPORARY - re-vendor webpack-dev-middleware with the hot.inject enum Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * chore: mark sendStats and the client/socket shim for the next major release Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * chore: regenerate types for the sendStats TODO Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * chore(ci): TEMPORARY - re-vendor webpack-dev-middleware main (f084ec9) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * chore(deps): depend on webpack-dev-middleware ^8.4.0 Replaces the TEMPORARY vendored build with the released package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA * test: buffer split SSE frames and accept extra connects in the worker test Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This takes over the last pieces webpack-dev-server still wired up itself, so that server can delete its own client and socket server (webpack/webpack-dev-server#5750). There is one client: this package's. Whatever webpack-dev-server needs to stay compatible with its 6.x users (its
{ type, data }messages, itsclient/*paths,sendMessage()) lives in webpack-dev-server, not here.New options and exports
hot.client: falseHotModuleReplacementPlugin, for a page that wires its own client.target: "node") get the plugin too, sowebpack/hot/pollandwebpack/hot/signalkeep theirmodule.hot. The client entry is still added only where a browser runs the compilation.hot.client.transport: "<module>"__webpack_dev_server_client__, which is how webpack-dev-server'sclient.webSocketTransporthas always worked.hot.wswsserver: compression,maxPayload,handleProtocols,verifyClient, or aport/serverof its own.hot.corsandhot.tokenare still checked first.webpack-dev-middleware/client/socketclientis the current transport instance,nullbefore the runtime starts to connect and while it waits to reconnect, andclient.clientis theWebSocket/EventSourceunderneath, typed. This is the shape webpack-dev-server'sclient/sockethad, which@pmmmwh/react-refresh-webpack-pluginreads.Client fixes from comparing webpack-dev-server 6.0's client with this one
?discount=50%no longer makes every update fail with "Invalid HMR message".apply: "reload"after a reconnect: a tab that reconnects after a restart reloads onto the newer build; one already running it is left alone.protocol,hostname,port,pathname,username,password,live-reloadandhot=onlyare read, so a hand-writtenwebpack-dev-server/client/index.js?…entry keeps working.LoggerandcreateConsoleLogger; a test parses an["web", "es5"]bundle of it with acornecmaVersion: 5.z-index: 2147483647, andEsc/keyCode27 close it.role="progressbar"witharia-valuenow.Tests
React Refresh,
test/e2e/react-refresh.test.js(14):@pmmmwh/react-refresh-webpack-plugin0.6.3 with React 19, over Server-Sent Events and over a WebSocket:The sources are written as
react-refresh/babelleaves them: babel-loader cannot load@babel/corein this test runner on every Node.js version CI uses.Universal builds (
target: "universal"and["web", "node"]):test/universal-target.test.jsruns the bundle in Node (no error, nothing from the client, nothing left running), andtest/e2e/universal-target.test.jsupdates a page in place and shows and clears the overlay.Options and exports:
client: false, the plugin on Node targets, a client transport module,hot.wsin every shape, and the liveclientexport.test/e2e/dev-server-parity.test.js: one test per audit item above.Locally: unit 7353 of 7355 (the other two expect a
writeToDiskEACCES, which root never gets); e2e 215 of 215, plus the 14 React Refresh tests; lint and types clean.🤖 Generated with Claude Code
https://claude.ai/code/session_01UjuMAuk9o6UazjHzcAQCTA
Summary by CodeRabbit