A mod author now chooses between two complete interface paths. Compile a native view when the result should look and behave like Aether, or ship a sandbox app when the interface itself is part of the mod.
What changed
- Three declarative panel nodes
- A compiled view with layout, text, fields, tabs, lists, meters and gestures
- A bare iframe with eleven CSS variables
- A normal sandbox app with versioned tokens and framework-neutral components
- Nine fixed card fields
- Bounded rich text, facts and up to four server-checked actions
- No place for mod settings or document actions
- Typed settings and actions placed by Aether in its own menus
The two paths are separate on purpose. A native view is inert JSON. The author can write ordinary TypeScript builders or optional TSX, but the build produces one versioned artifact that Aether validates and renders. There is no React component from the mod running in the product.
A sandbox app is an ordinary web app in an opaque-origin iframe. It may use any framework, its own
CSS and its own layout. The @aether/mod-ui package supplies custom elements for buttons, fields,
tabs, notices and meters, plus the same generated design tokens the host uses. Aether owns the title
strip, docking, floating, popout, size limits, focus, errors and permissions around it.
Why there are two
One universal tree would quietly become a second, smaller React. Every request for a link, bold text, local state, composition or a new kind of button would add another node and another runtime rule. It would still be too limited for an interface whose look is the point, while becoming too powerful to reason about in chat, menus and sheets.
The split puts the boundary where the pixels are actually controlled. Native views are authored by the mod and drawn by Aether. Sandbox apps draw only inside their labelled body. Custom styling is therefore not a capability grant: it does not reveal data or authorize an effect. The install screen discloses the custom interface, and every read or write still goes through its own capability and the server.
The frame is part of the security model
A sandbox app cannot cover the dock, copy Aether's title strip, escape its panel, restyle chat or make itself the loudest control on the screen. The GM sees which mod owns the custom pixels before granting the capabilities that let those pixels do anything.
Native when it belongs to the table
The D&D 5e system now expresses its character sheet as a native view. The engine still knows nothing about armor class, proficiency or spell slots. It receives generic score, rank, meter, facts, list and tab nodes after the system's build has validated them.

Values are resolved only from the document already projected for the reader. A missing or secret field produces no node-shaped clue that it exists. Local tab and disclosure state stays in the host, while a field change or button press becomes a serializable gesture that the server checks.
The same bounded language has profiles rather than pretending every surface is a canvas. A chat card accepts rich text, safe links, facts and four actions. A setting declares its type, scope and default. A semantic action names a host anchor such as a library document menu or the View menu, and Aether chooses its placement, collision handling and keyboard behavior.

The Fireball proof reads an existing srd.spell, posts the card in a real session and registers the
action payload on the server. Pressing Attack roll resolves against the person who pressed it. In the
captured session, changing the player-scoped attack bonus to 7, reopening settings, posting another
card and pressing its button produced 1d20+7 from the server.
A custom app when the interface is the feature
The 3D dice mod uses the other path. It imports no React from Aether. Its HTML loads the mod UI kit, uses custom elements for the controls and keeps Three.js and its canvas inside the iframe. Theme token updates arrive over the private message channel, so changing the host theme updates the app without a reload.

The app owns composition and local state inside its body. The bridge exposes capability-checked commands and projected context, not host objects. Its initial document is checked against its published integrity hash, its origin is opaque, and tenant code is never imported into the hosted process.
What it does not do
Native views do not accept CSS, colors, fonts, arbitrary event handlers or framework components. Sandbox apps do not get to restyle Aether outside their body. Neither path can bypass projection or turn a client gesture into authority.
Arbitrary scene drawing is also not hidden inside this contract. Fog, light, drawing and note layers remain semantic scene contributions. A future custom scene renderer needs its own lifecycle, hit-testing, accessibility, collaboration and denial-of-service decision before it can control map pixels.
Where to find it
Start with Build → Mods → Make an interface for your mod in the docs. aether create mod makes a
native TypeScript view by default; add --ui app for a sandbox app scaffold with the component kit.



