Aether
Sign inGet started
All posts
Release Notes · 5 min read

Aether v0.5.0: two ways to build a mod interface

Native views let a mod use Aether's controls without shipping a UI runtime. Sandbox apps give a custom panel its own HTML, CSS and JavaScript while Aether keeps the frame.

Aether Team
Aether Team
The team
A running Aether session with a Fireball card in chat, an attack roll made with the mod's saved bonus, and a custom 3D die inside a labelled panel.

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.

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.

The native D&D 5e character sheet with large ability modifiers, proficiency-marked saves and skills, distinct armor class, initiative and speed facts, a hit point meter, and player-facing tabs.

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.

A Fireball card posted by a mod with Attack roll and Damage buttons, followed by server-produced rolls beside the custom dice panel.

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 custom 3D dice app in Aether's dark theme, enclosed by the host-owned Custom interface strip and 3D Dice panel frame.

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.

Aether Team
Aether Team
The team at Aether

Everyone who ships Aether, writing together. Release notes and anything that does not belong to one person.

Keep reading