Skip to content

bake: restore the dev server's visualizer pages, incremental graph feed and .bake-debug dumps - #38664

Open
robobun wants to merge 1 commit into
mainfrom
farm/64d65d37/bake-debugging-features
Open

bake: restore the dev server's visualizer pages, incremental graph feed and .bake-debug dumps#38664
robobun wants to merge 1 commit into
mainfrom
farm/64d65d37/bake-debugging-features

Conversation

@robobun

@robobun robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Problem

  • /_bun/incremental_visualizer and /_bun/memory_visualizer are not routed: src/runtime/bake/incremental_visualizer.html and memory_visualizer.html are served by nothing (an app with a catch-all HTML route answers these URLs with its own page).
  • Subscribing to the incremental visualizer topic (sv on /_bun/hmr) never produces a v frame: hmr_socket.rs counts the subscriber and calls DevServer::emit_visualizer_message_if_needed, which is an empty function (DevServer.rs:5444 before this change). The call after every bundle in start_next_bundle_if_present hits the same empty function.
  • Debug builds no longer write the .bake-debug/ dumps of what was bundled. Leftovers of the feature are still there: the .gitignore entry, the start offset take_js_bundle_to_list computes and discards, the dev parameter of render_json that only the dump used.
  • Cause: the port gated all of this on #[cfg(feature = "bake_debugging_features")], a cargo feature nothing enabled (scripts/build/rust.ts passes --features only for the Windows shim), so it was always compiled out, and Narrow crate-internal Rust visibility across all targets and delete the code it proves dead #36184 deleted it as provably dead. The rest of the debugging features (CLI flags, the socket hooks) are gated on the runtime const feature_flags::BAKE_DEBUGGING_FEATURES and survived.

Fix

  • set_routes registers the two pages (read with runtime_embed_file!, so embedded at build time in release builds) and the /_bun/iv and /_bun/mv redirects the Zig version had, behind BAKE_DEBUGGING_FEATURES. They use the existing dev_route_tramp, so the Host check covers them like every other /_bun/ route.
  • emit_visualizer_message_if_needed serializes both graphs and publishes them on the IncrementalVisualizer topic while a socket is subscribed, then hands over to the memory visualizer as before.
  • The serializer (write_visualizer_message and its helpers) is the same code Restore BUN_DUMP_STATE_ON_CRASH: dump the DevServer graph when bun crashes #38617 adds for the crash dump; whichever PR lands second drops its copy.
  • Debug builds open .bake-debug/ in the cwd when a DevServer is created (DUMP_SOURCES_DIR); in release builds dump_dir stays None. The directory is opened after the struct is fully initialized, so every error return closes it through Drop.
  • dump_bundle writes each module as it is received under <graph>/<path relative to the root> (.. segments become _.._, keeping the dump inside the directory), the latest client and server chunk, and the source maps served or loaded for them (ChunkKind::dump_file_name). A dump that cannot be written only warns.
  • Why this is right: it is the behavior of the Zig dev server this was ported from, and it is what the surviving half of the feature expects (the topic counters, the HTML files, the .gitignore entry). The gate is the const the other debugging features already use, so stable release builds still do none of this. The dump default stays debug-only and unconfigurable, as it was.
  • Verified with test/bake/dev/debugging-features.test.ts: the pages and redirects; the frame sent on subscribe, after bundling, after a hot update (a freed edge), after deleting a file (stale flag), and for a framework app with a "use client" component (server flags, both edge lists, the deleted client file once the directive is removed); the dumps of a client app (module files, latest_chunk.js identical to the served script after the header, latest_chunk.js.map identical to the served map) and of a framework app (server/ modules including the _.._ path of the framework entry point, latest_hmr.js and its map).
  • The visualizer tests run on canary and debug builds and fail on the current canary; the dump tests run on debug builds and fail on an unmodified debug build.
  • Also ran test/bake/dev/ and test/bake/*.test.ts with the debug build (only production.test.ts fails, by timing out at 5s in a debug build; it never creates a DevServer), test/internal/source-lints/, cargo clippy -p bun_runtime, cargo check -p bun_runtime --target x86_64-pc-windows-msvc.
  • Related open PRs: bake: re-arm the memory visualizer timer when it fires #37936 restores the memory visualizer timer (the emit_memory_visualizer_message_if_needed body this calls into), Restore BUN_DUMP_STATE_ON_CRASH: dump the DevServer graph when bun crashes #38617 restores the crash dump. Neither covers the routes, the feed or the dumps.

Background

  • The dev server keeps two IncrementalGraphs (client and server): a list of files plus Edges recording which file imports which. A deleted file keeps its slot with an empty key so indices stay valid; a removed edge goes to edges_free_list for reuse.
  • /_bun/hmr multiplexes topics; a client subscribes by sending s followed by topic bytes. v is the incremental visualizer topic and also the first byte (MessageId::Visualizer) of the frames incremental_visualizer.html decodes: both file lists, then both edge lists. M is the memory visualizer's topic and frame id; those frames are already produced on main.
  • BAKE_DEBUGGING_FEATURES (src/bun_core/feature_flags.rs) is IS_CANARY || IS_DEBUG. The visualizer pages exist in both kinds of builds, the source dumps only in debug builds.
  • A client component boundary is a "use client" file imported from server code: the server graph keeps a stub for it (is_client_component_boundary) and the client graph gets the real file. Removing the directive demotes it, which is the one path that deletes a file from a graph; the test uses it to cover the empty-name encoding.

…ed and .bake-debug dumps

The Rust port put these behind a cargo feature that the build never
enabled, so they were compiled out and later deleted as dead code. Gate
them on feature_flags::BAKE_DEBUGGING_FEATURES like the rest of the
debugging features instead:

- serve /_bun/incremental_visualizer and /_bun/memory_visualizer (plus
  the /_bun/iv and /_bun/mv shortcuts)
- publish the incremental graph on the "v" topic when a socket
  subscribes and after every bundle
- debug builds dump every bundled module, the latest chunks and their
  source maps below .bake-debug/ in the cwd again
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@robobun, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 15 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e95e76d5-be27-40f6-9152-3edefa0612d6

📥 Commits

Reviewing files that changed from the base of the PR and between 9cff2a1 and 7ce37bf.

📒 Files selected for processing (6)
  • src/runtime/bake/DevServer.rs
  • src/runtime/bake/dev_server/incremental_graph.rs
  • src/runtime/bake/dev_server/memory_cost.rs
  • src/runtime/bake/dev_server/mod.rs
  • src/runtime/bake/dev_server/source_map_store.rs
  • test/bake/dev/debugging-features.test.ts

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

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Status: ready for review.

Reproduced on the current canary (USE_SYSTEM_BUN=1 bun test test/bake/dev/debugging-features.test.ts): /_bun/incremental_visualizer is answered by the app's own HTML route and a socket that sends sv never receives a v frame. The .bake-debug tests fail the same way against a debug build of main (ENOENT on the directory). All five pass with this branch.

Overlap notes for whoever merges: the visualizer serializer is byte-identical to the one in #38617 (the crash dump), so the second of the two to land just drops its copy; #37936 fills in the memory visualizer function this PR calls after each bundle.

@github-actions

Copy link
Copy Markdown
Contributor

This PR may be a duplicate of:

  1. Restore BUN_DUMP_STATE_ON_CRASH: dump the DevServer graph when bun crashes #38617 - Restores the other half of the same #36184-deleted code and adds a byte-identical copy of the write_visualizer_message/write_visualizer_files/write_visualizer_edges serializer to DevServer.rs, so whichever lands second must drop its copy.
  2. bake: re-arm the memory visualizer timer when it fires #37936 - Restores the memory-visualizer half of the same deleted feature inside the same DevServer.rs hunk, filling in the emit_memory_visualizer_message_if_needed stub that this PR's new emit_visualizer_message_if_needed calls into.

🤖 Generated with Claude Code

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

Not duplicates, but they are adjacent pieces of the same deleted feature, so here is how they fit together:

This PR is the part none of them cover: the /_bun/incremental_visualizer and /_bun/memory_visualizer routes, publishing v frames to subscribers, and the .bake-debug dumps in debug builds.

@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.

I reviewed this PR and the automated bug hunt found no issues. Given the size (~300 lines of new Rust across DevServer routing, a new binary serialization path, several new unsafe sibling-field accesses, and debug-only file I/O) plus the stated overlap with #37936 and #38617, a human look is still worthwhile before landing.

What was reviewed:

  • dump_dir lifecycle — opened after the box is fully initialized; sys::Dir has Drop, and the intermediate Dir in dump_bundle's make_open_path().and_then(|dir| …) chain is dropped and closed after File::create.
  • The _.._ escaping in dump_bundle_for_chunk keeps dumps inside .bake-debug; debug-only, and join_abs_string_buf normalises the result before make_open_path.
  • Visualizer frame layout matches what incremental_visualizer.html / the test decoder read (u32 counts, 6 flag bytes, edges after both file lists); is_set_allow_out_of_bound(i, true) treats not-yet-sized stale bits as stale.
  • New routes go through dev_route_tramp, so the existing Host / DNS-rebinding guard covers them.
Extended reasoning...

Overview

This PR restores three DevServer debugging features that were dropped when the Zig→Rust port gated them behind a never-enabled cargo feature: the /_bun/incremental_visualizer and /_bun/memory_visualizer pages (plus /_bun/iv and /_bun/mv redirects), the MessageId::Visualizer frame published on the IncrementalVisualizer HMR topic, and the .bake-debug/ per-module and per-chunk source dumps in debug builds. It touches DevServer.rs (new dump_dir field, four new route handlers, ~150 lines of graph-serialization helpers, dump_bundle/dump_bundle_for_chunk), incremental_graph.rs (dump hooks in receive_chunk and both take_js_bundle_to_list*), source_map_store.rs (dump hook in render_json), memory_cost.rs (exhaustiveness match), mod.rs (ChunkKind::dump_file_name), and adds a 466-line test file exercising the pages, the frame contents across subscribe/bundle/hot-update/delete/SCB-demote, and the dump layout for both client-only and framework apps.

Security risks

Low. The new HTTP routes are gated on feature_flags::BAKE_DEBUGGING_FEATURES (canary/debug only) and are dispatched through the existing dev_route_tramp, so the DNS-rebinding Host check that guards every /_bun/* route applies. The pages serve static embedded HTML and the redirects target fixed constants. The .bake-debug writes are debug-build-only, rooted in the cwd, and dump_bundle_for_chunk rewrites ../ segments to _.._/ before joining under /<graph>/; the join goes through join_abs_string_buf, which normalises. A dump write failure only warns and never fails the bundle. No user-controlled input reaches these paths beyond project-relative source file names.

Level of scrutiny

Medium-high. While the surface area is developer-tooling only (nothing here runs in stable release builds except the always-None dump_dir field and the if feature_flags::BAKE_DEBUGGING_FEATURES early return), the change is not mechanical: it adds a struct field with Drop implications, several new unsafe { (*dev).field } sibling projections in incremental_graph.rs, a hand-rolled little-endian binary format that must stay byte-compatible with incremental_visualizer.html, and file I/O with path construction. The PR description also notes it duplicates serialization code from open PR #38617 and calls into emit_memory_visualizer_message_if_needed whose body is being restored in #37936 — a maintainer should confirm the intended landing order.

Other factors

The test coverage is unusually thorough for a debugging feature: it decodes the wire format independently, asserts trailing-byte exactness, sorts to avoid slot-order dependence, covers freed edges, deleted files, stale flags, server-side flags, and the _.._ path case, and byte-compares dumped chunks against what the browser would fetch. The tests correctly describe.skipIf on the build variant. Nothing in the diff triggered concerns from the automated review, and the specific things I checked (fd lifetimes on the make_open_path chain, sys::Dir Drop, the is_set_allow_out_of_bound semantics for out-of-range stale bits, route registration ordering vs. the app catch-all) all look correct. Deferring purely on size/complexity and the cross-PR coordination note, not on any identified defect.

@robobun

robobun commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

On the landing order question from the review: no particular order is needed.

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