@beastjs/devtools
v0.1.21
Published
In-page devtools for Beast (BTSX) and Octane apps on Vite, Rspack and Rsbuild: live component state, BTSX → TSRX inspection, and automatic component extraction for deeply nested templates.
Maintainers
Readme
Beast Devtools | @beastjs/devtools
See the live tree. Read the compiled output. Flatten deep templates in one click.
Install · Quick start · Using the overlay · Automatic refactors · Configuration · Security · Troubleshooting
Beast DevTools adds a panel to your app while it runs on the Vite, Rspack or
Rsbuild dev server. It shows the live Octane component tree with hook and
context values, puts every .btsx file next to the TSRX it compiles to, and
finds templates nested too deeply. It can then extract those sections into
components for you, with typed props, after showing you the diff.
Production builds are untouched. The plugin runs on the dev server only.
At a glance
| Capability | What it does | Why it matters |
| --- | --- | --- |
| Components | Live component tree with hooks, context and effects | Debug state without logging |
| Element Picker | Hover HTML or SVG elements to see spacing, size, ID and type | Inspect layout without source tags |
| Component Finder | Hover the page to see a component and its .btsx line | Go from pixels to source |
| BTSX → TSRX | Source and compiled output, linked line by line | See what Beast generates |
| Refactor | Finds deep nesting, repeated markup and sibling runs | Keeps templates readable |
| Auto-refactor | Writes the component, props interface and imports | Refactors in one reviewed step |
| Undo | Restores the files a refactor touched | Makes changes low-risk |
Requirements
| Dependency | Version |
| --- | --- |
| beast-tsrx | ^0.4.3 |
| octane | ^0.4.3 |
| Node.js | >=22.22.2 |
| typescript (optional) | >=5.0.0, for typed props in refactors |
Plus one bundler:
| Bundler | Packages | Version |
| --- | --- | --- |
| Vite | vite | ^8.0.16 |
| Rspack | @rspack/core and @rspack/dev-server | ^2.0.0 |
| Rsbuild | @rsbuild/core | ^2.0.0 |
Installation
bun add -d @beastjs/devtoolsnpm install -D @beastjs/devtoolsQuick start
Add the plugin next to beastOctane(), importing it from your bundler's entry
point. Octane's profile option compiles in the runtime inspection hook that
the Components panel reads. Enable it for development only.
Vite
// vite.config.ts
import { beastOctane } from 'beast-tsrx/vite'
import { beastDevtools } from '@beastjs/devtools/vite'
import { defineConfig } from 'vite'
export default defineConfig({
plugins: [
// `profile: 'auto'` enables the inspection hook in dev builds only.
beastOctane({ octane: { profile: 'auto' } }),
beastDevtools(),
],
})@beastjs/devtools without a subpath is also the Vite plugin.
Rspack
// rspack.config.ts
import { beastOctane } from 'beast-tsrx/rspack'
import { beastDevtools } from '@beastjs/devtools/rspack'
export default {
module: { rules: [{ test: /\.css$/, type: 'css' }] },
plugins: [
// Rspack's `profile` is a boolean; the CLI sets NODE_ENV before loading this file.
beastOctane({ octane: { profile: process.env.NODE_ENV !== 'production' } }),
beastDevtools(),
],
}[!IMPORTANT] The overlay imports a stylesheet, so an Rspack config needs a rule for
.cssfiles.
The plugin hooks into devServer.setupMiddlewares, so it runs under
rspack serve. If you start RspackDevServer yourself, pass it
compiler.options.devServer. In a multi-compiler config, add the plugin to the
browser config that has devServer.
Rsbuild
// rsbuild.config.ts
import { defineConfig } from '@rsbuild/core'
import { beastOctane } from 'beast-tsrx/rsbuild'
import { beastDevtools } from '@beastjs/devtools/rsbuild'
export default defineConfig({
plugins: [
...beastOctane({ octane: { profile: process.env.NODE_ENV !== 'production' } }),
beastDevtools(),
],
})The overlay loads in every web environment. Server environments are left
alone.
Start the dev server and press Alt+Shift+D, or click the Beast button in the bottom-right corner.
Using the overlay
Keyboard and mouse
| Action | How | | --- | --- | | Open or close the panel | Alt+Shift+D, or the Beast button | | Start or stop the Component Finder | Alt+Shift+C, or the crosshair button | | Start or stop Element Picker | Alt+Shift+E, or the ruler button | | Exit Component Finder or Element Picker | Esc | | Resize the panel | Drag its top edge | | Resize a pane | Drag the edge between two panes | | Reset a pane's width | Double-click that edge |
The panel remembers its height, pane widths, open tab and analyzer settings per browser. It slides in and out, and all motion becomes near-instant when the system asks for reduced motion.
Component Finder
Turn on Component Finder and hover any element of your app. An outline follows the
pointer, labeled with the component that renders the element and its .btsx
file and line. Click to open that line in your editor. That also ends
Component Finder.
While Component Finder is on, app clicks select a component to open in the editor. The overlay's own controls keep working.
Element Picker
Click the ruler button in the launcher or toolbar, or press Alt+Shift+E. Hover an element for a compact preview of its tag, ID and dimensions. Click it to open Elements in DevTools.
- Styles shows common layout and appearance properties. Enable All computed styles or search to inspect the full computed list. Editing a value creates an inline override; remove it to return to the stylesheet value.
- Attributes shows every attribute and lets you edit, add or remove values.
- DOM properties includes inherited properties and runtime values such as
value,checkedanddisabled. Editable primitive values can be changed; browser-owned objects and methods are displayed as read-only summaries.
Press Enter or leave a value field to apply it. Undo reverses each edit and remains available when switching panels or reselecting the same element. These are live page edits only: they do not modify BTSX files, and reloading or an app rerender can replace them. Removed elements are marked as disconnected; use Pick another to select their replacement.
The preview appears after 200 ms without pointer movement and sits outside the element. Element Picker works on HTML and SVG without source tags or a runtime connection. Escape cancels picking. Only one tool is active at a time, and selection clicks are consumed so they do not activate the underlying app. Elements inside iframes and closed shadow roots are inspected at their container.
Components
The live Octane component tree, including the each and if scopes that
Beast templates create, in one of two views:
- Blocks (the default) stacks each component as a one-line card. Shades
alternate by layer and a colored stripe marks each depth. Indentation stops
growing after six levels, and deeper blocks show their depth instead, so
large apps stay readable. The view starts at your app's entry component
(the
.btsxcomponent thatmain.tsimports), and folds the providers above it into a single wrappers hidden bar you can expand. Anything else those providers render, such as a toaster, stays visible. - Tree is the classic indented tree.
Click a caret, or double-click a block, to collapse or expand it. A collapsed block shows how many children it holds. Selecting a component shows:
- its hook values, named after the
setupbindings that declare them, for exampleactiveId: "language"; - its context values and number of effect slots;
- the
.btsxline that declares it, with buttons to open it in your editor or view its compiled TSRX.
BTSX → TSRX
Any .btsx file beside the TSRX that Beast generates for it. Hovering a line
highlights its counterpart through Beast's source map, and clicking pins it. A
file that fails to compile shows its BEAST#### diagnostic and the failing
line. Both panes update when you save.
Refactor
The nesting depth of every template line, with totals and a per-depth chart, plus three kinds of suggestions:
| Suggestion | Finds | Becomes |
| --- | --- | --- |
| Extract | A section whose descendants exceed the depth limit measured from its starting line | A component, with props inferred from the bindings it uses |
| Shared shape | Blocks with the same markup | One component. Differing attribute values, text and conditions become props |
| Repeated | Three or more same-shape siblings, or two larger ones | An each over an array, keyed by a unique field or the index |
For example, two copy buttons that differ only in their handler become calls to one component:
CopyNote(onClick={() => copyNote('left', note)} text='Copy')A suggestion's name is editable on its card before you apply it. That's the
component name (which also names its NameProps interface and file), or the
array or loop variable for a repeated run. Names are checked as you type and
again on the dev server, which refuses names already used in the file.
The toolbar sets the depth limit, the smallest section worth extracting, and the size at which a section defaults to its own file.
Manual extraction
In Refactor, click the first source line (or its line number) of the block you want to extract. The overlay highlights the entire parsed block and adds a manual card with an inferred component name and props. This also works for shallow blocks that do not meet the automatic suggestion thresholds.
Rename the component on the card, choose hoist or create, review the
diff, and apply. Undo works the same as for automatic refactors. Selecting an
if, each, or switch includes the complete block and its branches;
continuation lines, declarations, and existing component calls are not
starting points. Editing the source clears the selection.
Automatic refactors
Every suggestion card can apply itself in one of two ways:
- Hoist in file adds a local
componentabove the host component'spropsandsetup, and replaces the section with a call. - Move to
Name.btsxwrites the section to a new file beside the source. The new file gets the imports it needs. Module-level types and values the section uses are exported from the source and imported back (type-only where possible), and the source imports the new component.
flowchart LR
A[Pick a suggestion] --> B[Choose hoist or move]
B --> C[Dev server builds the change<br/>from the file on disk]
C --> D{Every file compiles<br/>through Beast and Octane?}
D -->|No| E[Refused, with the reason]
D -->|Yes| F[Review the diff]
F -->|Apply| G[Files written]
G --> H[Undo available]Typed props
The extracted component declares its props as a NameProps interface,
exported when the section moves to its own file. Each prop's type comes from
your project's TypeScript, read at the section itself, so loop variables and
narrowing from if and switch branches are accounted for:
| The type is… | It is written as… |
| --- | --- |
| Already nameable in the file (globals, local types, existing imports) | That name |
| A named type exported from another module | That name, with an import type added |
| Not exported anywhere, such as Octane's internal setter type | The full type, for example (next: PanelId \| ((prev: PanelId) => PanelId)) => void |
If TypeScript can't be loaded from your project, types are estimated from the source and the card is marked estimated types.
Review and undo
Sections of at least New file at lines (30 by default) default to their own file. Clicking a target first shows a diff of every file it will touch, and nothing is written until you confirm. After applying, Undo restores the files unless they've been edited since.
[!NOTE] Undo history lives in the dev server's memory, so it's lost when the server restarts.
When a refactor is refused
A card offers no automatic refactor when it can't be done safely. The card says why, and you can still copy the code by hand. That happens for:
- copies that differ in more than values (tags, selectors, loop headers), whose differing values use a variable bound inside the block, or that live in different components;
- components with scoped
styleblocks; - moving a section that uses a file-local
componentinto a new file.
Open another project folder
Use the Open project folder icon beside the pickers and enter an absolute folder path on the
machine running the dev server, or click Browse… to use its native folder
chooser (macOS, Windows, or Linux with Zenity/KDialog). The source inspector and refactor panel scan
that folder for .btsx files, skipping generated and dependency directories.
Changes in the opened folder refresh the overlay automatically. Empty folders
can be opened too; they show an empty file list.
The selection belongs to the current browser tab and resets on reload. Use Back to running app to return to the configured project. Live components and Component Finder always inspect the running app; following a component's source link switches back to that project. Opening a folder does not start its dev server.
Configuration
beastDevtools({
include: ['src'],
analyzer: { depthLimit: 5, minLines: 8, fileLines: 30 },
componentFinder: true,
})| Option | Default | Description |
| --- | --- | --- |
| include | ['src'] | Directories, relative to the project root, scanned for .btsx files |
| analyzer.depthLimit | 5 | Nesting depth (0 = component root) above which a line counts as deep |
| analyzer.minLines | 8 | Smallest section, in lines, worth extracting |
| analyzer.fileLines | 30 | Sections at least this long move to their own file by default |
| componentFinder | true | Tag elements with their component and source line for Component Finder |
elementPicker remains a deprecated alias for componentFinder. When both are
provided, componentFinder takes precedence. Element Picker works without source tags.
The project root is Vite's root, Rspack's context, or Rsbuild's root path.
Analyzer settings changed in the panel override these defaults for that
browser.
How it works
flowchart LR
subgraph Server[Dev server]
T[Source tagger] --> B[Beast and Octane compile]
API[JSON API] --> X[Analyze, refactor, undo]
W[File watcher] --> S[Server-sent events]
end
subgraph Page[Browser]
O[Overlay] --> H[Octane inspection hook]
O --> API
S --> O
P[Component Finder] --> E[Editor endpoint]
end
B --> Page- Injection. Vite gets a script tag in
index.html, Rspack a global entry, and Rsbuild asource.preEntry. - API. A small JSON API under
/__beast-devtools/apicompiles, source-maps, analyzes and refactors.btsxfiles withbeast-tsrx. Changes reach the overlay as server-sent events, so every dev server behaves the same. Vite's watcher feeds them; under Rspack and Rsbuild the plugin watches theincludedirectories itself. - Overlay. The overlay is written in BTSX and ships as source, so your
app's own Beast and Octane compile it. It reads the component tree from
Octane's
__OCTANE_DEVTOOLS__hook, whichprofileenables. The app and the overlay share one Octane runtime. - Component Finder. Before Beast compiles a project
.btsxfile, the plugin addsdata-beast-src="path:line:column"anddata-beast-componentto each of its HTML elements. The attributes are static, so Octane builds them into its templates at no runtime cost. Component calls and files innode_modulesare not tagged. - Editor. Opening a file goes through the dev server's own launch-editor
endpoint, which honors the
LAUNCH_EDITORenvironment variable.
Security model
The devtools can write to your source files, so their API only trusts requests from your own page.
- The plugin runs only on the dev server.
vite build,rspack buildandrsbuild buildnever include the overlay, its API or the source tags. - Write endpoints accept only same-origin
application/jsonrequests. Requests that a browser marks as cross-site, or whoseOriginis a different host, are refused. - The browser only names a suggestion. The dev server rebuilds the change from the file on disk, and refuses it if the file changed since it was analyzed.
- Refactors only touch
.btsxfiles inside the configuredincludedirectories, or inside a folder explicitly opened through Open project…. Every resulting file must compile before anything is written. - Request bodies are capped at 64 KiB, and component names at 80 characters.
[!WARNING] Like any dev server, it is meant for your machine. Don't expose a dev server running Beast DevTools to an untrusted network.
Troubleshooting
| Symptom | Cause and fix |
| --- | --- |
| Components shows Runtime off | Octane's inspection hook is missing. Enable profile in beastOctane() for dev builds, as in Quick start. |
| The overlay is unstyled under Rspack | Add a rule for .css files: { test: /\.css$/, type: 'css' }. |
| Component Finder outlines nothing | componentFinder is false, or the element comes from a package in node_modules, which isn't tagged. |
| Open in editor does nothing | Set LAUNCH_EDITOR to your editor's command (for example code or cursor) and restart the dev server. |
| A file is missing from the file list | It sits outside the include directories. Add its directory to include. |
| Panel sizes or settings look wrong | Clear the beast-devtools:preferences and beast-devtools:layout keys from the page's local storage. |
Limitations
- Hook values are matched to
setupbindings by position and kind. When they don't line up (with custom hooks, for example), the panel shows positions such as#0instead of guessing. - A prop is only as well typed as its binding. A host prop declared as
anystaysany, because types come from declarations, not from call sites. - Moving a section that uses module-level values makes the two files import each other. That's safe, because the values are read at render time, but the import cycle is worth knowing about.
- Imports that only the moved section used are left in the source file.
- Component Finder attributes are inserted into tagged lines, so dev-server error columns on those lines can point slightly past the real position. Line numbers are exact.
- Component Finder names the component whose template holds an element. Markup passed in as children belongs to the file that wrote it.
Repository structure
@beastjs/devtools/
├── vite.ts, rspack.ts, rsbuild.ts # Bundler plugins
├── server/ # Node side, built to dist/
│ ├── devtools.ts # JSON API, change events, editor redirect
│ ├── project.ts # Project scan, compile cache, apply and undo
│ ├── analyze.ts # Nesting depth and refactor suggestions
│ ├── refactor.ts # Plans the edits for a suggestion
│ ├── types.ts # Prop types from the project's TypeScript
│ ├── slots.ts, source-scan.ts # BTSX and TypeScript source scanning
│ ├── diff.ts, line-map.ts # Diff previews and source-map line links
│ └── source-tags*.ts # Component Finder tagging and Rspack loader
├── client/ # The overlay, shipped as BTSX source
│ ├── BeastDevtools.btsx # Shell: dock, launcher, panel switching
│ ├── Topbar.btsx, Icons.btsx # Top bar with tabs and tool buttons; shared icons
│ ├── *Panel.btsx # Components, BTSX → TSRX, Refactor
│ ├── element-tools.ts, layout.ts # Component Finder, Element Picker and resizable panes
│ ├── runtime.ts, api.ts # Octane hook store and API client
│ └── devtools.css # Scoped styles (every class is bdt-*)
├── shared/types.ts # Wire types shared by both sides
└── test/ # End-to-end dev-server harness and fixtureDevelopment
Requirements: Bun and Node.js 22.22.2 or newer.
bun install
bun run check # type check, tests, and plugin build
bun run pack:check # list the files npm would publishvite.test.ts, rspack.test.ts and rsbuild.test.ts start each real dev
server on a throwaway app. They check the overlay bundle, the source tags, the
API, change events and the editor redirect.
To try changes in an app, link the package and add it to the app's bundler config:
bun link # in this repository
bun add -d link:@beastjs/devtools # in the appEdits under client/ hot-reload in the linked app. Changes to the plugins or
server/ need bun run build and a dev-server restart.
Contributions should keep production builds untouched, refactors conservative, and write endpoints same-origin only.
Releases
Releases are automated through a Release Please PR on main. Merging the PR
creates the version tag and GitHub release, then publishes to npm with trusted
publishing. See release setup, commit conventions, and retries.
License
Released under the ISC License.
Built for Beast and Octane.
Continue inline props
In BTSX → TSRX, click a component or element header with inline props, then click Continue props with ~. Each prop moves onto its own continuation line, preserving expressions, inline text and children:
Button(
~ label="Save"
~ onClick={save}
~ )The change is saved after Beast and Octane validate it. Undo restores the previous source unless the file has since been edited.
The Refactor panel automatically suggests continuation for components and elements with 5 or more inline props. Adjust Continue at props to change that minimum; it is saved with your browser preferences. Select Continue props with ~ on a suggestion to review its diff, then Apply changes to save it. Already continued headers are excluded from automatic suggestions.
--automode
