brust 2.0.0-alpha.8
Guide / Release notes

Release notes

What changed in each published version of brust, newest first. Packages are published under @brust/* with the version shown.

2.0.0-alpha.8 · 14 October 2026

A fix release. Upgrade if your app uses 2.0.0-alpha.6 or alpha.7.

Fixed

  • A page no longer answers HTTP 500 when a React island gets plain HTML elements as children. In 2.0.0-alpha.6 and alpha.7, an island whose JSX children (or element-valued props) were only plain elements, such as <TooltipTrigger><span>…</span></TooltipTrigger>, made the server render fail with React error #31 and the whole page returned 500. Those children now render as React elements.

Security

  • Request data can no longer be turned into elements by the island revive. In 2.0.0-alpha.7, when one component was both a page or layout and a React island that received JSX children from another file, data returned by its loader could reach the code that rebuilds those children, so a loader value shaped like brust’s internal markers could add elements to the page. React’s escaping, the removal of dangerouslySetInnerHTML and the island’s own component list still applied. Now a page or layout is never such a host (the caller falls back to React with revive-host-route-root), only names the compiler declared are rebuilt, and a server render rejects reserved $ keys in a host’s inputs.

2.0.0-alpha.7 · 14 October 2026

Every page of the shadcn/ui fixture now builds, lists and conditions made of constants no longer force a React island, and the largest page is smaller. See shadcn/ui and React libraries for the current numbers.

Upgrade note

Rebuild your app after you upgrade. The build output (dist/manifest.json) changed format, from version 2 to 3. A build made with an earlier brust is refused when it starts:

brust start
[brust] manifest: manifest version 2 unsupported (want 3): rebuild the app with this brust version

Run brust build again with the new version; nothing else changes in your project.

Changes

  • A function or a changing value passed to a React island no longer fails the build. The component that passes it renders with React, like every other shape brust does not lower, and the build prints island-prop as a fallback. Before, such a page stopped with error island-prop.
  • Lists over a constant. A component rendered in a .map over a module constant or an inline array, whose props do not depend on the row, is rendered once per page instead of making the page a React island (instance-list-path). A table of rows built from a constant array is static.
  • A component fed by the row of a constant list. A component rendered in a .map over a module constant or inline array, whose props read the row, gets one job call per row from the literal instead of falling back. The served HTML of the started page equals the React build’s.
  • Constant lists, conditions and element props inside an island’s children. [1, 2, 3].map(n => <li>{n}</li>), Array.from({ length: 4 }, …), a constant condition, and an element passed as a prop (content={<Tooltip />}) are worked out at build time. The scroll-area, carousel, sidebar and chart pages stop being React pages, each keeping only its library island. A prop named $… on an island fails closed.
  • Smaller island bundles. A package imported by a staged island is now resolved the way your own source is, so it is tree-shaken: the dialog page goes from 114 to 102 KB gzip, and the chart page stays at 240 KB now that it is a static page (without this change it would have grown to 324 KB). One copy of React is bundled across island, runtime and job chunks, for the Bun build, the Cloudflare build and --target static.
  • forwardRef components. const X = React.forwardRef(function X(props, ref) { … }), an arrow callback, export default forwardRef(…) and alias exports (export { X, X as Root }) compile like any component. A forwarded ref={ref} on an HTML element is dropped; any other use of the ref (reading it, passing ref to a component, useImperativeHandle) keeps the component on React (forwardref-ref).
  • A hand-written jsx() call is no longer printed as an object. A call to jsx, jsxs or jsxDEV from react/jsx-runtime now falls back to React (jsx-runtime-call). Before, the page compiled static with the element object in its HTML.

2.0.0-alpha.6 · 14 October 2026

This release is about React code you did not write for brust. The compiler now reads what shadcn/ui components are made of, so more of them render as static markup or a native component instead of a React island. See shadcn/ui and React libraries for what compiles and what does not, with the numbers from the build.

You can now write

  • Rest props and JSX spread. function Card({ className, ...props }) followed by <div {...props} /> compiles. It used to be a React island (prop-pattern, spread-props).
  • A handler through a child’s rest spread. <Button onClick={add}> where Button spreads its props onto a <button>: the parent binds the handler on that element and becomes a native component, instead of the pair falling back to React. A handler that closes over the child’s own state, and render props, still keep the component a React island.
  • asChild and Slot. const Comp = asChild ? Slot.Root : 'button' is resolved where the component is used: <Button> is a button, <Button asChild><a href="/x">…</a></Button> is the link carrying the button’s props. A value that is not known when you build still makes the component an island.
  • <Pkg.Member> tags. <DialogPrimitive.Root> and a const Root = DialogPrimitive.Root alias resolve to the package’s export, which renders as an island like any package component. A member that the package does not have fails with brust: <package> has no export <path> when the island loads.
  • Constant children inside an island. Markup between the tags of a React island, when it is constant, is handed to the island as data. The page around it stays static; before, one such island made the whole page one island.
  • Components exported by name. A file that exports several components (Card, CardHeader, …) can have any of them rendered as an island. Before, building such a page failed with No matching export … for import "default".

What got smaller

  • A page loads only the browser runtime it uses. The runtime is a small core plus feature files (basics, if, for, model, html, island, store). The build records which a page needs and the page links exactly those, with modulepreload. Before, every page with a native component loaded the whole runtime. See Rendering tiers.

CSS

  • Your Tailwind compiles your CSS. The Tailwind version installed in your app builds your stylesheet, not a copy bundled with brust. Tailwind 4.1 is the minimum; an older version stops the build with an error that says so. See CSS.

Measuring shadcn/ui

  • A fixture app with the shadcn/ui components, built page by page, writes docs/shadcn-coverage.md and docs/shadcn-coverage.json: tier, islands and client JavaScript per page, and the ranked list of what still blocks. CI fails when the committed copy is stale.

Earlier versions

  • 2.0.0-alpha.5 · 13 October 2026. The documentation site on Tailwind (#191–#195), its stylesheet through the brust CSS pipeline (#186), create-app writes the current version (#187), and fixes for two flaky tests (server start-up logging #181, a 413 on chunked bodies #183).
  • 2.0.0-alpha.4 · 13 October 2026. Static export (--target static, #159), the CSS pipeline and Tailwind (#163, #167), components from packages as islands (#161), build-time evaluation of constant slots (#164, #174), lucide icons as inline SVG (#178), CSS Modules folded into class names (#177), standalone actions (#172), the dev tools panel (#166), and a blank template with Tailwind (#168, #180).
  • 2.0.0-alpha.3 · 12 October 2026. The Cloudflare Workers runtime (@brust/core-cf) and cache adapters: @brust/core/cache-adapter, @brust/cache-redis, @brust/cache-cloudflare.
  • 2.0.0-alpha.2 · 12 October 2026. Follow-ups to the first alpha: cache read stamps for the L1 and job caches, the cross-site guard for actions, large action bodies.
  • 2.0.0-alpha.1 · 12 October 2026. The first v2 release: the unified store, Actions served as an Elysia app behind the Rust HTTP server, bun create @brust/app, and the performance work with its benchmark suite.