[pull] canary from vercel:canary#1093
Merged
Merged
Conversation
The lint-language script is too verbose, it prints "no issues found" for all files it touches.
### What? Make `DiskFileSystem` write and symlink effects run on their own spawned tasks, so multiple pending writes can execute in parallel rather than serially on the caller's future. ### Why? Effects emitted by `DiskFileSystem::write` and `write_link` were previously applied inline within the caller's future. That meant the work for each effect ran sequentially in the awaiting task — writes did not overlap even though each one is largely I/O bound (path validation, lock acquisition, file write, fsync). ### How? - `WriteEffect` and `WriteLinkEffect` now derive `Clone` and own their data so they can be moved into a spawned task. `full_path` becomes `Arc<PathBuf>` to keep cloning cheap. - `apply_inner` takes `self` by value instead of `&self`. - `apply()` now calls `spawn(self.clone().apply_inner())` so the effect body runs on a dedicated turbo-tasks task. Multiple effects awaited concurrently will execute in parallel. Closes NEXT- Fixes # <!-- NEXT_JS_LLM_PR -->
…94316) `@typescript-eslint/switch-exhaustiveness-check` (enabled in eslint.cli.config.mjs) already guarantees complete switch coverage on enums and discriminated unions, so requiring an additional default case on those switches in TypeScript just forced dead branches. We turn on its `requireDefaultForNonUnion` option so that switches on plain types such as `string` or `number` still require a default, preserving the coverage that `default-case` previously gave us. We scope the `default-case: off` override to exactly the files where the type-checked rule runs — the same `files` and `ignores` as the switch-exhaustiveness check — and cross-reference the two config blocks so the globs stay in sync. That way `default-case` stays enabled for JavaScript files, `.mts` files, and the directories the type-checked config skips, so no switch is left unchecked. The two `// eslint-disable-next-line default-case` directives in `postcss-loader` and `head.tsx` are no longer the right escape hatch: with `requireDefaultForNonUnion` the exhaustiveness check now wants those switches handled, and a directive for that rule would be reported as unused under the editor config where it does not run. We give each switch an explicit `default` case instead, which both rules accept and which makes the previously implicit fall-through behavior explicit.
When the browser's HTTP cache entry for a back-navigation target has been evicted between forward visit and back-press — for example with a long-lived tab, storage pressure, or a manual cache clear — the browser re-fetches the document fresh from the server. Previously we relied on `type === 'back_forward'` alone to decide that the document came from cache, which meant we treated those re-fetches as cache restores too. The persisted-chunk lookup would miss on the fresh response, and we'd recover with an unnecessary `location.reload()`. The same edge case is exposed by the Playwright/WebKit combination in the upstack PR: bfcache isn't utilized there, so back-navigations always come over the network, and the old single-signal check classified those re-fetches as served-from-cache and triggered the same unnecessary reload. With this change we split the cache-restore detection into two phases. At script-execution time we look at `deliveryType` (Chrome ≥109, Safari ≥17) and the navigation entry's `transferSize`/`encodedBodySize` to decide cache-restore vs fresh-response when those fields are populated. When the body bytes haven't been measured yet — the common case for streaming Next.js dev RSC responses on every browser, and also WebKit leaving both size fields at zero at exec time — we suspend the readable until `pageshow` and re-check there. On a fresh re-fetch we route through the live WebSocket-backed channel, which already has the debug data for the new response, and skip the reload entirely. The new `bfcache-regression` test exercises this edge case on Chromium by clearing the browser cache via CDP between the forward and back navigations, using a new `clearBrowserCache()` helper on the test browser wrapper. The same exec-time code path is naturally hit by Safari whenever its navigation entry's size fields are still zero at script-execution time, but the harness can't force the eviction deterministically there.
) In development the debug channel that streams React's Server Component debug information to the browser is buffered for the initial document and persisted, so that it can be replayed when the browser later serves the page from its HTTP cache — for example on back/forward navigation or tab duplication — instead of forcing a full reload. This changes where that buffer is persisted, moving it from `sessionStorage` to `IndexedDB`. The largest win comes from no longer having to serialize the data. `sessionStorage` can only hold strings, so the binary debug chunks had to be encoded into a string before being written and parsed back out again on restore, which becomes expensive once the payload grows to several megabytes. `IndexedDB` stores the chunks directly as the `Uint8Array`s they already are, so there is no encode and parse round trip on either side. Its asynchronous API helps a little on top of that, since the write itself no longer has to happen synchronously. The other improvement is that persistence no longer competes with hydration. The initial document's debug stream closes while hydration is still running, and the previous synchronous `sessionStorage` write happened at exactly that moment, taking main thread time away from hydration. The write is now deferred with `requestIdleCallback`, so it only runs once the main thread is genuinely idle, which is after hydration has drained, and it is skipped entirely if the page navigates away before that happens, in which case a later restore simply falls back to a reload. This is visible in the profiles below: with `sessionStorage` there is still hydration work running after the persistence task, whereas with the idle-scheduled `IndexedDB` write the persistence only runs once hydration is done. In a profile of a test page that deliberately transfers a large amount of debug information, the persistence work on the main thread dropped from more than 700 ms to roughly 25 to 45 ms. A real application with far less debug data will see a smaller difference, so the test page amplifies the effect, but the direction is the same. The number of persisted entries stays bounded to 10, with the oldest pruned on each write, and an end-to-end test covers the case where an entry is pushed out by newer page loads so that navigating back to it recovers through a page reload. **Before with `sessionStorage`**: <img width="973" height="627" alt="sessionStorage" src="https://github.com/user-attachments/assets/b8ff8464-9ccf-4363-b1fe-78db0fd3e1eb" /> **After with idle-scheduled `IndexedDB`:** <img width="973" height="627" alt="indexedDB" src="https://github.com/user-attachments/assets/94a557a3-c82d-4771-bd81-3b4dfd906f25" />
The debug channel's Node streams implementation never closed on the client. This leaked memory, because the resources held for each debug channel — the buffered chunks and its entry in the client-side registry, and the pending reader and stream pipeline on the server — are only released once the channel closes. It also meant the buffered debug entry was never persisted to `IndexedDB`, so the bfcache restore test timed out waiting for it. The server-side write target forwarded React's writes into the readable side with `passthrough.push()` and signalled completion with `passthrough.push(null)`. Because a `PassThrough` is a `Duplex`, pushing `null` ends only its readable half and leaves the writable half open, so `writableEnded` stays `false`. The readable is later consumed through `Readable.toWeb()`, both in `connectReactDebugChannel` and inside `teeStream`, and `Readable.toWeb()` never closes the resulting web stream while the writable half is still open. The close therefore never reached the client, nothing was cleaned up, and the test's wait-for-persisted step never resolved. Forwarding through `passthrough.write()` and `passthrough.end()` instead ends both halves, so the close propagates through the conversion and the channel closes on the client as expected. This also re-enables the previously skipped node streams case of the bfcache regression test, which now passes alongside the web streams case.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )