Skip to main content

Module getting_started

Module getting_started 

Source
Expand description

§Getting started

This page takes a UI plugin and a companion daemon from nothing to a packed bundle. Every source file it refers to is a compiled example in the SDK’s examples/ directory; the scaffolds standard-plugin new writes are those examples with your plugin’s id.

§Tools

  • Rust stable with the wasm32-wasip2 target: rustup target add wasm32-wasip2.
  • The CLI: cargo install standard-plugin-cli --locked. It installs standard-plugin and cargo-standard-plugin, so cargo standard-plugin ... works too.
  • The SDK: every scaffold depends on standard-plugin-sdk = "0.1"; a daemon that uses WASI files, sockets or HTTP writes { version = "0.1", features = ["wasi"] } (daemon plugins).

Install the CLI from the same release line as the SDK a plugin depends on, so its WIT package matches the SDK’s. Never commit a Cargo.lock resolved with a local path patch (agent guide).

  • Optional: jco (npm install -g @bytecodealliance/jco) to inspect the component’s JavaScript form. Viewers do not run it. Without it build says it skipped that step.

standard-plugin new --sdk-path <dir> points a scaffold at a local SDK checkout instead of the published crate.

§A UI plugin

standard-plugin new my-card --kind ui
cd my-card

The scaffold is one crate:

FileWhat it is
Cargo.tomlA cdylib depending on standard-plugin-sdk, with size-tuned release settings
src/lib.rsThe plugin: examples/clock_card.rs
bundle/standard-plugin.jsonThe manifest: one sidebar.card surface in the cell model, no grants
.gitignoreIgnores target/ and what build writes into bundle/
AGENTS.md, CLAUDE.mdWhere a coding agent finds these guides for the SDK version in use

Read src/lib.rs top to bottom; it is short:

  • The crate is #![no_std]. A UI plugin runs inside every viewer with no WASI, and std would import it; alloc (String, Vec, format!) is available through standard_plugin::prelude.
  • #[standard_plugin::ui] on the type exports it as the component’s ui-plugin world. The type implements UiPlugin: activate creates the Surface<Cells> named in the manifest, frame paints and commits, and event hears about resizes.
  • frame repaints only when the second changes and asks for its next frame at the next second with frame.wake_at(...). The viewer calls frame only when a frame is due and the card is visible, so the card costs nothing while hidden. See rendering.

Build, check and pack:

standard-plugin build   # cargo build --release --target wasm32-wasip2, into bundle/, then check
standard-plugin check   # again, without building
standard-plugin pack    # target/standard-plugin/my-card-0.1.0.tar and its sha256
standard-plugin install # the same, then `standard plugin install` on your account

check parses the manifest with the same code the viewer’s host uses, checks every grant has a reason, and checks the component imports nothing outside the ui-plugin world; see grants for its warnings.

install uploads the package to your account’s plugin store, lists its grants with their reasons and asks before it installs; every viewer signed in to the account then runs the card (publishing). To try a build in one development viewer without installing it:

STANDARD_PLUGIN_DIRS=$PWD/bundle standard

To test it without a viewer, drive it with standard_plugin::testing from an ordinary #[test]; tests/testing_harness.rs is a complete example.

§A companion daemon

A UI plugin is side-effect free: it runs once per open viewer, so anything it did would happen once per viewer. Work that must happen once belongs to a companion: a daemon plugin with the same id that the UI half asks through calls and that publishes results the UI half reads. Working together explains the pattern.

standard-plugin new builds --kind companion
cd builds

The scaffold is a Cargo workspace with two crates, one plugin id:

PathWhat it is
ui/src/lib.rsThe UI half: examples/together_ui.rs
ui/bundle/standard-plugin.jsonKind ui, grant call:builds (with its reason)
companion/src/lib.rsThe daemon half: examples/together_companion.rs
companion/bundle/standard-plugin.jsonKind companion, grant process.exec:make

How the halves cooperate:

  1. The companion publishes the build status as a durable value, builds.status, and announces each change with the event builds.changed. Both are in the plugin’s own namespace, so neither needs a grant.
  2. The UI half reads builds.status in activate, listens to builds.*, and repaints its card when builds.changed arrives.
  3. A click on the card is an effect, so the UI half acts only when view::is_driving() says the user is controlling this viewer, and then inside claims().once(...) so exactly one request goes out. It calls the companion’s rebuild method (calls().call(..., Target::Singleton), grant call:builds).
  4. The companion’s call handler answers at once and spawns the build as a task on its executor, which runs when the host next drives the plugin: it runs make (grant process.exec:make) and publishes the new status, which step 2 shows in every viewer.

Build, check and pack from the workspace root; the CLI finds both halves:

standard-plugin build
standard-plugin pack

Both halves build, check, pack and install as one package, and both run under standard_plugin::testing. Once installed, standardd runs the companion on the machines its placement names (daemon plugins), and the UI half’s calls reach it there.

§Next