Skip to content

Examples

Three examples ship with the repository, and together they cover a standalone application, a script backed by host state, and motion whose frames never enter JavaScript.

Runs asShows
Todo listA standalone applicationThe whole script surface: retained input, a dialog, a toast, gated storage, assets, types
WorkspaceA standalone applicationA dockable layout: panels that survive a restart, and every piece of its chrome drawn by script
Quote boardA panel inside the galleryThe host half: HostModule registrations, one entity read from two languages, live cost counters
Native motionA separate gallery script ViewPixel target transitions and springs retained and sampled by GPUI

A complete application

The examples here are each built to show one thing. For a whole product in one repository — OAuth, a live WebSocket quote feed, a virtualized watchlist, retained nested Views for the price chart, and its own Rust host binary — see longbridge/longbridge-lite. It is a read-only Longbridge desktop client of a few thousand lines of JavaScript, and it is the largest thing written against this runtime.

The todo list

bash
cargo run -p gpui-shell -- examples/js_todolist

examples/js_todolist/ exists to exercise the whole runtime rather than to be minimal — if something in gpui-shell is broken, this is where it shows first.

text
main.js                   the View: state, filtering, every handler
ui.js                     the presentation layer, exported as functions
storage.js                persistence, and what to do when it is not granted
confirm.js                the confirmation dialog, a View of its own
icons/                    four SVGs, resolved against the application root
gpui.d.ts                 generated; jsconfig.json and types.d.ts wire up typing

Four things in it are worth copying.

ui.js is a component library made of functions. It exports label, muted, title, button, iconButton, checkbox, field, row, surface, rule and emptyState, and main.js reads like it is using a component library:

js
export const label = (value, cx) =>
  div().text_size(12).line_height(1).text_color(cx.theme().colors.foreground).child(value);

export const surface = (cx) =>
  v_flex().flex_1().bg(cx.theme().colors.surface).border(1).border_color(cx.theme().colors.border).overflow_hidden();

main.js passes the current cx to these helpers, which read tokens directly through cx.theme(). That costs nothing, because a fresh description is exactly what a function call produces. It is also the answer to "the base layer ships no styled widgets" — you write the styled layer once, in your own file, and stop repeating it.

Storage absorbs a refusal instead of checking for permission. store throws when the host did not grant it, and that is a fact about the host rather than an error in the application:

js
export function load() {
  try {
    const saved = store.get(KEY);
    return Array.isArray(saved) ? saved : [];
  } catch (error) {
    console.warn(`todolist: storage unavailable, starting empty (${error.message})`);
    return [];
  }
}

save() returns whether the write landed, and the footer says so on screen — "Not saved — this host did not grant storage, so the list lasts for this run only". Absorb the refusal at the boundary, then tell the user the truth.

A dialog is a function, not an element. confirm.js default-exports a function that returns the content function; main.js opens it with window.open_dialog(confirmClear(count, onConfirm)). The count and the callback are closed over rather than handed across. See Overlays.

Types are set up, and it is three files. jsconfig.json turns on checkJs, gpui.d.ts is generated by gpui-shell types, and types.d.ts holds the application's own shapes — Todo, Filter. Editor completion and checkJs errors work from there with no build step.

The workspace

bash
cargo run -p gpui-shell -- examples/js_dock

examples/js_dock/ is a dockable workspace — a file list on the left, documents in the center, and a layout that comes back the way you left it.

text
main.js                   the workspace: panels, the dock, and persistence
ui.js                     the chrome: tabs, the dock frame, the drop hint

Three things in it are the point.

Base draws no chrome, so all of it is in ui.js. The tab bar, the dock's title strip, the collapse control, the resize handle and the drop hint are ordinary elements written with the ordinary style surface. An area with none of them still docks, drags, resizes and persists; it simply paints nothing but the panels.

A tab carries commands, not handlers. A chrome description is cached until its native state changes, so a script event handler inside it would have no sound lifetime. select_tab(group, tab.index) and close_panel(group, tab.id) carry no script value at all — they name a container and what to ask it.

A panel is a View with two extra methods. Document.serialize() returns its caption and its edit count; deserialize(data) takes them back after a restart. Everything else about the panel — where it sits, whether it is displayed — is the layout's business and never reaches the script.

See Dock and Panels for the whole surface.

The quote board

bash
cargo run -- shell

The gallery's Shell story runs two panels side by side: the left one drawn by shell_story.rs in Rust, the right one by crates/story/js/quotes/main.js in JavaScript, reading the same data.

The script owns no state at all. The board is a Rust Entity<Market>, imported from the HostModule the story registered before the runtime started:

text
import { quotes, ticks, watch, watch_all } from "market";

Theme values come from the call-scoped cx.theme() Snapshot, not a second HostModule.

Because both panels read one entity, any disagreement between them is visible immediately — which is what makes this a test rather than a demo. Editing main.js changes the right-hand panel with no cargo build in between; the story has a "Reload script" button next to the panel.

Underneath sits the counter readout this documentation quotes throughout: script runs a second against frames a second, with a feed selector to move one without the other. That is the performance claim made visible in a running window.

Native motion

crates/story/js/motion/main.js is intentionally a separate ScriptView from the quote benchmark, so animation activity cannot contaminate the render-frequency measurement. It lets you switch between .transition(...) and .spring(...), then retargets opacity and pixel-valued width, height, left, and top.

The script runs once to publish the new target. GPUI schedules and samples every following animation frame natively, with no JavaScript re-entry. The example uses only numeric pixel targets — no rem, percentages, or auto — and stable ids so retained channels survive description rebuilds.

Where to start

Copy examples/js_todolist into a directory of your own and run it — it is a complete application with types already wired. Strip main.js back to a View with an init and a render, keep ui.js, and build up from there.

For a host, crates/story/src/stories/shell_story.rs is a working reference for the other side: it builds a runtime, exports HostModule registrations, mounts a ScriptView, and reloads it on demand. Hosting walks through the same calls.