Skip to content

dev server: refresh a framework route's meta.styles when its imports change - #42849

Open
robobun wants to merge 1 commit into
mainfrom
robobun/cd532503/bake-framework-route-styles-stale
Open

robobun wants to merge 1 commit into
mainfrom
robobun/cd532503/bake-framework-route-styles-stale

Conversation

@robobun

@robobun robobun commented Sep 16, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • A framework route's meta.styles stays stale after a save changes its CSS imports. With no browser tab open, later requests get the first list.
  • Cause: finalize_bundle (src/runtime/bake/DevServer.rs:4363) clears cached_css_file_array only for routes in route_bits. Server-side routes enter route_bits only in the hot update payload block. That block needs a subscriber, a file-change rebuild, and zero bundling failures.
  • The clear also needs an added or removed import edge. Swapped imports keep the old order.

Fix

  • Before the payload code, finalize_bundle drops cached_css_file_array for every route the rebuild reached and every route below an affected layout. invalidate_client_bundle drops it for routes reached through a client component.
  • A list computed while one of the route's stylesheets fails to build is not cached. It misses that stylesheet, and the later fix marks no route.
  • Correct because the payload block reuses that route set under its old conditions: subscribers receive the same bytes.
  • Verified: three new tests in test/bake/dev/css.test.ts, all fail on 1.4.3-canary.1. Self-reviewed: 5 items addressed, 1 rejected (Notes).

Background

  • A framework route handler gets meta.styles: the stylesheet URLs its imports reach, rendered as <link> tags.
  • A route bundle caches per-route values between requests. cached_css_file_array is its cached meta.styles.
  • A browser tab subscribes to hot updates on the /_bun/hmr WebSocket. Each rebuild sends it one payload that starts with the routes to reload.
  • A _layout file wraps the routes below it. A "use client" file is a client component.
Notes

Relation to #39488 and #37867. The finalize_bundle and route_bundle.rs hunks are the same text as #37867, which was closed into #39488 (commit debf54eade there). #39488 is still open, was last pushed on August 20 and conflicts with main. Main does not have the fix. Those hunks apply on main with no change, so a rebase of #39488 turns them into no-ops. The guard in generate_css_js_array and its test are new and are not in #39488.

Relation to #40255. #40255 moves this code into finalize_bundle_phases, one indent level deeper, and keeps the subscriber-gated clear. It also rewrites compute_arguments_for_framework_request and generate_css_js_array as safe methods that take the route bundle index. The route_bundle.rs hunks apply there as they are. Six of the eight DevServer.rs hunks do not apply: the four in finalize_bundle, and the signature and the call site of the guard. The port below has the same edits in the shape of that branch. It applies to #40255 at 408cbf5be0. I did not compile it there, because that branch needs its own codegen output.

Port of the DevServer.rs hunks to #40255
--- a/src/runtime/bake/DevServer.rs
+++ b/src/runtime/bake/DevServer.rs
@@ -2140,9 +2140,11 @@
             styles: match cached_css_file_array {
                 Some(a) => a,
                 None => 'arr: {
-                    let js = self.generate_css_js_array(route_bundle_index)?;
-                    self.framework_bundle_mut(route_bundle_index)
-                        .cached_css_file_array = jsc::StrongOptional::create(js, global);
+                    let (js, complete) = self.generate_css_js_array(route_bundle_index)?;
+                    if complete {
+                        self.framework_bundle_mut(route_bundle_index)
+                            .cached_css_file_array = jsc::StrongOptional::create(js, global);
+                    }
                     break 'arr js;
                 }
             },
@@ -3123,7 +3125,8 @@
         Ok(client_bundle)
     }
 
-    fn generate_css_js_array(&mut self, rbi: route_bundle::Index) -> JsResult<JSValue> {
+    /// The `bool` is false when the list is incomplete and must not be cached.
+    fn generate_css_js_array(&mut self, rbi: route_bundle::Index) -> JsResult<(JSValue, bool)> {
         let trace_key = {
             let route_bundle = &self.route_bundles[rbi.get() as usize];
             debug_assert!(matches!(
@@ -3144,6 +3147,19 @@
         self.client_graph.reset();
         self.trace_all_route_imports(trace_key, &mut gts, TraceImportGoal::FindCss)?;
 
+        // A stylesheet whose build failed is not a CSS root, and its fix marks no route.
+        let mut complete = true;
+        {
+            let files = self.client_graph.bundled_files.values();
+            let mut visited = gts.client_bits.iterator::<true, true>();
+            while let Some(i) = visited.next() {
+                if files[i].failed {
+                    complete = false;
+                    break;
+                }
+            }
+        }
+
         let names: &[u64] = &self.client_graph.current_css_files;
         let global = self.vm().global();
         let arr = jsc::JSArray::create_empty(global, names.len())?;
@@ -3171,7 +3187,7 @@
                 bun_string_jsc::create_utf8_for_js(global, path)?,
             )?;
         }
-        Ok(arr)
+        Ok((arr, complete))
     }
 
     fn trace_all_route_imports(
@@ -3914,6 +3930,32 @@
 
         let mut has_route_bits_set = false;
 
+        // Unlike the payload below, this must not depend on anybody listening for hot updates.
+        let mut framework_route_bits = DynamicBitSet::init_empty(dev.route_bundles.len())?;
+        for request in &dev.incremental_result.framework_routes_affected {
+            let route = dev.router.route_ptr(request.route_index());
+            if let Some(id) = route.bundle {
+                framework_route_bits.set(id.get() as usize);
+            }
+            if request.should_recurse_when_visiting() {
+                mark_all_route_children(
+                    &dev.router,
+                    &mut [&mut framework_route_bits],
+                    request.route_index(),
+                );
+            }
+        }
+        {
+            let mut it = framework_route_bits.iterator::<true, true>();
+            while let Some(bundled_route_index) = it.next() {
+                dev.route_bundles[bundled_route_index]
+                    .data
+                    .framework_mut()
+                    .cached_css_file_array
+                    .clear_without_deallocation();
+            }
+        }
+
         let mut hot_update_payload: Vec<u8> = Vec::with_capacity(65536);
         hot_update_payload.push(MessageId::HotUpdate.char());
 
@@ -3944,19 +3986,7 @@
         {
             has_route_bits_set = true;
 
-            for request in &dev.incremental_result.framework_routes_affected {
-                let route = dev.router.route_ptr(request.route_index());
-                if let Some(id) = route.bundle {
-                    route_bits.set(id.get() as usize);
-                }
-                if request.should_recurse_when_visiting() {
-                    mark_all_route_children(
-                        &dev.router,
-                        &mut [&mut route_bits],
-                        request.route_index(),
-                    );
-                }
-            }
+            framework_route_bits.copy_into(&mut route_bits);
             for route_bundle_index in &dev.incremental_result.html_routes_hard_affected {
                 route_bits.set(route_bundle_index.get() as usize);
                 route_bits_client.set(route_bundle_index.get() as usize);
@@ -4034,8 +4064,7 @@
         }
 
         // `route_bits` will have all of the routes that were modified.
-        if has_route_bits_set && (will_hear_hot_update || dev.incremental_result.had_adjusted_edges)
-        {
+        if has_route_bits_set && will_hear_hot_update {
             // Note: copy out before the loop so the `&mut RouteBundle` borrow
             // below doesn't overlap a `&dev.incremental_result` read.
             let had_adjusted_edges = dev.incremental_result.had_adjusted_edges;
@@ -4043,22 +4072,14 @@
             // List 2
             while let Some(i) = it.next() {
                 let route_bundle = &mut dev.route_bundles[i];
-                if had_adjusted_edges {
-                    match &mut route_bundle.data {
-                        route_bundle::Data::Framework(fw_bundle) => {
-                            fw_bundle.cached_css_file_array.clear_without_deallocation()
-                        }
-                        route_bundle::Data::Html(html) => html.cached_response = None,
-                    }
-                }
-                if route_bundle.active_viewers == 0 || !will_hear_hot_update {
+                if route_bundle.active_viewers == 0 {
                     continue;
                 }
                 let trace_key = route_bundle.trace_key();
                 w_int!(i32, i32::try_from(i).expect("int cast"));
 
                 // If no edges were changed, then it is impossible to
-                // change the list of CSS files.
+                // change the set of CSS files.
                 if had_adjusted_edges {
                     ctx.gts.clear();
                     dev.client_graph.current_css_files.clear();

Relation to #42841. #42841 makes a save under the clientEntryPoint invalidate the client bundle of every route of the router type. Its notes rely on the List 2 loop to drop cached_css_file_array for those routes. This PR moves that drop into invalidate_client_bundle, which the new block of #42841 already calls. The two branches merge with no conflict. I built the merge: css.test.ts (18) and bundle.test.ts (26) pass, and both tests of #42847 pass.

Other open PRs. #42813 makes route_lookup hold a list of routes per file. It has no hunk near this change, and the new loop reads framework_routes_affected as before, so the two compose. These PRs also edit test/bake/dev/css.test.ts: #37051, #40337, #42697. All three touch the harness import line, and #42697 also appends at the end of the file.

Repro from the report (fails on 1.4.3-canary.1+09bb54630: Expected: "styles=1 v2", Received: "styles=0 v2"). src/runtime/bake of that build is identical to main at 7e56b40.

import { devTest, minimalFramework } from "../bake-harness";
devTest("styles after a css import is added, no hot update listener", {
  framework: minimalFramework,
  files: {
    "routes/index.ts": `export default (req, meta) => new Response("styles=" + meta.styles.length);`,
    "s.css": `body { color: red; }`,
  },
  async test(dev) {
    await dev.fetch("/").equals("styles=0");
    await dev.write("routes/index.ts", `
      import "../s.css";
      export default (req, meta) => new Response("styles=" + meta.styles.length + " v2");
    `);
    await dev.fetch("/").equals("styles=1 v2");
  },
});

The harness socket subscribes only to the test synchronization topic r, not to hot updates h. With a socket that sends sh and n/ first, the same sequence gives the new list on main.

What this PR refreshes, and what it does not. meta.styles comes from four roots: the page, its layouts, the serverEntryPoint and the clientEntryPoint of the router type. Client components are reached through the first three.

Why the guard. A stylesheet that fails to build loses its content in the client graph. It is no longer a CSS root, so the styles trace does not list it. Its fix rebuilds only the stylesheet. A stylesheet that a server file imports has no importer in the client graph, so the fix marks no route. Main hides most of this by accident. Its clear needs bundling_failures to be empty, so a cached list survives the failure. That list still has the URL of the stylesheet, and the URL works again after the fix. Main still has the bug when the first request of a route falls in the failure window (the first half of the third test). With the unconditional clear of this PR, a save that reaches the route in that window would cache the incomplete list too (the second half). The guard checks the client files that the trace visited, so an unrelated failing file costs nothing. While a stylesheet of the route fails, each request of that route traces its imports again.

The three tests.

  1. No hot update subscriber: add an import, swap two imports, remove an import, the same through a "use client" component, then an import in _layout seen from two routes.
  2. A subscriber that views /. While another route has a syntax error, the payload names no routes (unchanged) and meta.styles is refreshed (new). After the error is fixed, the payload names the viewed route in List 1. List 2 carries exactly the stylesheet ids that the route then serves. The subscriber is the harness socket: one socket orders the hot update before the frame that resolves dev.write(), so a stray rebuild cannot shift the accounting.
  3. A stylesheet fails to build and is fixed. The first time, the first request of a route falls in that window. The second time, a save reaches a route in that window.

Each clause of the fix breaks a test when removed (checked with a debug build for each):

  • No clear in invalidate_client_bundle: test 1 fails at the client component step (/other stays []).
  • No framework_route_bits.copy_into(&mut route_bits): test 2 fails at the last payload check (reloadedRoutes: []).
  • No mark_all_route_children in the new block: test 1 fails at the layout step for the child route /other. The route / that owns the layout still refreshes.
  • No guard (always cache): test 3 fails after the first fix of the stylesheet ([] in place of ["two.css"]), the same as on main.
  • Without the whole source change, tests 1 and 2 fail at their first check after a write.

Why the removed html.cached_response = None was redundant. An HTML route enters route_bits in two ways. A hard-affected route is also put in route_bits_client, and both branches that follow call invalidate_client_bundle for every bit of route_bits_client. A soft-affected route gets invalidate_client_bundle directly. That function sets cached_response = None.

Why no had_adjusted_edges condition on the new clear. A swap of two imports rebuilds the edge list in the new order. It adds or removes no edge, so had_adjusted_edges stays false. The order of meta.styles is the order of the <link> tags, so it decides the cascade. The cost of a clear is one import trace on the next request.

Self-review. Addressed: (1) the incomplete list cached in a failure window, found as a regression of the first version of this change, fixed by the guard and test 3. (2) The subscriber test used a second socket and counted hot updates, which a stray rebuild could shift. It now uses the harness socket. (3) #40255 named, with the port. (4) The coverage limits above stated and tracked. (5) The overlap with other open PRs listed. Rejected: a note that the client component branch of finalize_bundle clears html_routes_soft_affected before the block that reads it. That needs app and an HTML import in one server. Bun.serve rejects that ('app' + HTML loader not supported.), and with development: { hmr: false } a debug assertion in ServerConfig.rs forbids it.

Not in this PR.

The Zig dev server had the same logic (DevServer.zig before #30412), so this is not a port regression.

Suites run on a debug build with the fix, all pass. test/bake/dev/css.test.ts (18), and in test/bake/dev/: bundle (22), hot (11), html (11), esm (17), react-spa (6), ssg-pages-router (9), response-to-bake-response (5), react-response (11), plugins (3), incremental-graph-edge-deletion (1), vfile (1), request-cookies (2), server-sourcemap (5), sourcemap (2), harness (3), import-meta-inline (6). Also test/bake/dev-and-prod.test.ts (12), test/bake/deinitialization.test.ts (10) and test/js/bun/http/bun-serve-html.test.ts (30). The three new tests passed five runs in a row.


[human-review] gate passed · iteration 0 · 3 files touched

fails on main (without fix)
ASAN without fix: 3 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bake/dev/css.test.ts
bun test v1.4.3 (09bb54630)

test/bake/dev/css.test.ts:
Dev server testing directory: /tmp/bun-dev-test-Xwyevn
^[[0;30mdev|^[[0m Started development server: http://localhost:42309
^[[0;30mdev|^[[0m ^[[32mBundled page in 76ms^[[0m^[[2m:^[[0m index.html
^[[0;30mweb|^[[0m [I] [Bun] Hot-module-reloading socket connected, waiting for changes...
^[[0;30mdev|^[[0m ^[[39merror^[[0m^[[2m: ^[[0m^[[1mUnexpected end of input^[[0m
^[[0;30mdev|^[[0m     ^[[2mat ^[[0m^[[36m/tmp/bun-dev-test-Xwyevn/css1/styles.css^[[0m^[[2m:^[[0m^[[33m4^[[0m^[[2m:^[[0m^[[33m1^[[0m
^[[0;30mdev|^[[0m ^[[32mReloaded in 48ms^[[0m^[[2m:^[[0m styles.css
^[[0;30mdev|^[[0m ^[[36m[x2]^[[0m ^[[32mReloaded in 51ms^[[0m^[[2m:^[[0m styles.css
(pass)  DEV:css-1: css file with syntax error does not kill old styles [3324.98ms]
^[[0;30mdev|^[[0m Started development server: http://localhost:37727
^[[0;30mdev|^[[0m ^[[39merror^[[0m^[[2m: ^[[0m^[[1mUnexpected end of input^[[0m
^[[0;30mdev|^[[0m     ^[[2mat ^[[0m^[[36m/tmp/bun-dev-test-Xwyevn/css2/styles.css^[[0m^[[2m:^[[0m^[[33m3^[[0m^[[2m:^[[0m^[[33m3^[[0m
^[[0;30mweb|^[[0m [I] [Bun
... (truncated)

release without fix: 3 FAILED
bun test v1.4.3-canary.1 (09bb54630)

test/bake/dev/css.test.ts:
Dev server testing directory: /tmp/bun-dev-test-uj1gYx
^[[0;30mdev|^[[0m Started development server: http://localhost:46317
^[[0;30mdev|^[[0m ^[[32mBundled page in 2ms^[[0m^[[2m:^[[0m index.html
^[[0;30mweb|^[[0m [I] [Bun] Hot-module-reloading socket connected, waiting for changes...
^[[0;30mdev|^[[0m ^[[39merror^[[0m^[[2m: ^[[0m^[[1mUnexpected end of input^[[0m
^[[0;30mdev|^[[0m     ^[[2mat ^[[0m^[[36m/tmp/bun-dev-test-uj1gYx/css1/styles.css^[[0m^[[2m:^[[0m^[[33m4^[[0m^[[2m:^[[0m^[[33m1^[[0m
^[[0;30mdev|^[[0m ^[[32mReloaded in 1ms^[[0m^[[2m:^[[0m styles.css
^[[0;30mdev|^[[0m ^[[36m[x2]^[[0m ^[[32mReloaded in 1ms^[[0m^[[2m:^[[0m styles.css
(pass)  DEV:css-1: css file with syntax error does not kill old styles [795.10ms]
^[[0;30mdev|^[[0m Started development server: http://localhost:37137
^[[0;30mdev|^[[0m ^[[39merror^[[0m^[[2m: ^[[0m^[[1mUnexpected end of input^[[0m
^[[0;30mdev|^[[0m     ^[[2mat ^[[0m^[[36m/tmp/bun-dev-test-uj1gYx/css2/styles.css^[[0m^[[2m:^[[0m^[[33m3^[[0m^[[2m:^[[0m^[[33m3^[[0m
^[[0;30mweb|^[[0m [I] [Bun] Hot-module-reloading socket connected, waiting for changes...
^[[0;30mdev|^[[0m ^[[32mReloaded in 1ms^[[0m^[[2m:^[[0m styles.css
^[[0;30mweb|^[[0m [I] [Bun] Ho
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/bake/dev/css.test.ts
bun test v1.4.3 (09bb54630)

test/bake/dev/css.test.ts:
Dev server testing directory: /tmp/bun-dev-test-6LZSfY
^[[0;30mdev|^[[0m Started development server: http://localhost:36403
^[[0;30mdev|^[[0m ^[[32mBundled page in 70ms^[[0m^[[2m:^[[0m index.html
^[[0;30mweb|^[[0m [I] [Bun] Hot-module-reloading socket connected, waiting for changes...
^[[0;30mdev|^[[0m ^[[39merror^[[0m^[[2m: ^[[0m^[[1mUnexpected end of input^[[0m
^[[0;30mdev|^[[0m     ^[[2mat ^[[0m^[[36m/tmp/bun-dev-test-6LZSfY/css1/styles.css^[[0m^[[2m:^[[0m^[[33m4^[[0m^[[2m:^[[0m^[[33m1^[[0m
^[[0;30mdev|^[[0m ^[[32mReloaded in 40ms^[[0m^[[2m:^[[0m styles.css
^[[0;30mdev|^[[0m ^[[36m[x2]^[[0m ^[[32mReloaded in 38ms^[[0m^[[2m:^[[0m styles.css
(pass)  DEV:css-1: css file with syntax error does not kill old styles [2858.84ms]
^[[0;30mdev|^[[0m Started development server: http://localhost:36051
^[[0;30mdev|^[[0m ^[[39merror^[[0m^[[2m: ^[[0m^[[1mUnexpected end of input^[[0m
^[[0;30mdev|^[[0m     ^[[2mat ^[[0m^[[36m/tmp/bun-dev-test-6LZSfY/css2/styles.css^[[0m^[[2m:^[[0m^[[33m3^[[0m^[[2m:^[[0m^[[33m3^[[0m
^[[0;30mweb|^[[0m [I] [Bun
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped)
  target       linux-x64-gnu
  build type   Release
  build dir    ./build/release
  revision     4554c37644
  features     lto, baseline

23 deps, 131 codegen, 1176 objects in 961ms

ninja: Entering directory `/workspace/bun/build/release'
[1/1254] mkdir stamps
[2/1254] mkdir codegen
[3/1254] mkdir pch
[4/1254] mkdir obj
[5/1254] install /workspace/bun
bun install v1.4.3-canary.1 (09bb54630)

Checked 22 installs across 61 packages (no changes) [21.00ms]
[6/1254] gen ErrorCode+*.h
[7/1254] install /workspace/bun/packages/bun-error
bun install v1.4.3-canary.1 (09bb54630)

Checked 1 install across 2 packages (no changes) [6.00ms]
[8/1254] install /workspace/bun/src/node-fallbacks
bun install v1.4.3-canary.1 (09bb54630)

Checked 111 installs across 104 packages (no changes) [7.00ms]
[9/1254] gen bindgenv2
[10/1254] gen node-fallbacks/react-refresh.js
Bundled 1 module in 5ms

  react-refresh.js  4.81 KB  (entry point)

[11/1254] esbuild bun-error

  ../../build/release/codegen/bun-error/index.js       34.9kb
  ../../build/release/codegen/bun-error/bun-error.css  12.8kb

⚡ Done in 33ms
... (truncated)
diff hotspot
src/runtime/bake/DevServer.rs               |  78 ++++++----
 src/runtime/bake/dev_server/route_bundle.rs |  13 +-
 test/bake/dev/css.test.ts                   | 225 +++++++++++++++++++++++++++-
 3 files changed, 287 insertions(+), 29 deletions(-)

gate history · 1 passed · 0 rejected · iteration 0

evidence per changed file
file                                         reads  edits  tests
src/runtime/bake/DevServer.rs                   10      1     39
src/runtime/bake/dev_server/route_bundle.rs      1      0     39
test/bake/dev/css.test.ts                        4      2     39

…change

finalize_bundle dropped a framework route's cached styles array only in
the code that writes the hot update payload. That code runs when a client
is subscribed to hot updates, the rebuild came from a file change, and no
file has a bundling failure. With no browser tab open, a route kept the
styles of its first request.

Collect the framework routes that a rebuild reached before the payload
code, and drop their cached styles array there. invalidate_client_bundle
drops it for the routes reached through a client component. The payload
loop no longer clears caches. The payload itself does not change.

A stylesheet that fails to build is not a CSS root, and its fix marks no
route. A styles array computed while the trace visits a failed client
file is therefore not cached.
@coderabbitai

coderabbitai Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

  • Run on-demand review

On-demand reviews are free for the next 5 days. After that, they cost $0.25 per reviewed file.

Or wait 2 minutes for your next included review.

Check out review usage here.

View limit details

Limit details: You’ve used all 10 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: df26d67c-29aa-4a69-b924-d526b590b431

📥 Commits

Reviewing files that changed from the base of the PR and between f5649a7 and 4554c37.

📒 Files selected for processing (3)
  • src/runtime/bake/DevServer.rs
  • src/runtime/bake/dev_server/route_bundle.rs
  • test/bake/dev/css.test.ts

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator Author

Status: fix pushed, waiting for CI.

Reproduction: the dev server test in the Notes block of the description, run on the 1.4.3 canary (09bb546). src/runtime/bake of that build is identical to main. After routes/index.ts gains import "../s.css", the route answers styles=0 v2 where styles=1 v2 is expected. The harness socket does not subscribe to hot updates, which is the condition for the bug.

Tests: USE_SYSTEM_BUN=1 bun test test/bake/dev/css.test.ts fails the three new tests. bun bd test test/bake/dev/css.test.ts passes 18 of 18.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline findings, I also checked the html.cached_response = None clear that was removed from the List 2 loop in finalize_bundle: every HTML route that reached that loop on the base branch (hard-affected with a subscriber via route_bits_client, soft-affected, and client-component-affected) still goes through RouteBundle::invalidate_client_bundle, which clears cached_response, so that removal is behavior-preserving.

Extended reasoning...

Two confirmed findings are already attached inline (the SAFETY comment at DevServer.rs:2524-2536 no longer describing the borrow pattern after the if complete change, and the uncovered case where an already-cached meta.styles keeps a stylesheet URL after that stylesheet's build breaks). The one additional thing worth recording is that the refactor's dropped HTML cached_response clearing was traced: the only ways an HTML route enters route_bits (DevServer.rs:4415-4417 under the subscriber-gated payload block, the soft-affected loop at 4480-4486, and the client-components block at 4451-4470) all also call invalidate_client_bundle on the same route, which sets cached_response = None in route_bundle.rs. Human review is still warranted given the inline findings and that this is dev-server cache-invalidation logic with *mut RouteBundle reborrows.

Comment thread src/runtime/bake/DevServer.rs Resolved
Comment thread src/runtime/bake/DevServer.rs Resolved
@robobun

robobun commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator Author
Updated 8:00 PM PT - Sep 15th, 2026

✅ @robobun, your commit 4554c3764459ce255d591cab2bac6efa585d8e3b passed in Build #116231! 🎉


🧪   To try this PR locally:

bunx bun-pr 42849

That installs a local version of the PR into your bun-42849 executable, so you can run:

bun-42849 --bun

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant