Learn by example
A runnable catalog of sample modules — one capability each, plus full worker demos.
The fastest way to learn the module platform is to read (and run) the samples in examples/modules/. They're ordered here from "hello world" to a full native-compute worker, and every browser sample is one HTML file with no build step — copy it and start changing things.
How a module works (the 30-second version) #
A module's UI runs in a sandboxed, opaque-origin iframe. It can't touch the platform directly — it can only postMessage the parent window, which runs the trusted capability bridge. Every request is gated by the module's declared permissions. That's the whole security model: declared → (approved) → scoped → audited.
So every browser module starts with the same tiny client:
// The entire bridge client. `call(type, payload)` sends one request and resolves // with the reply (correlated by a request id). Copy this verbatim. const PROTOCOL = 1, pending = new Map(); let counter = 0; function call(type, payload = {}) { const rid = `r${++counter}`; return new Promise((resolve, reject) => { pending.set(rid, { resolve, reject }); parent.postMessage({ v: PROTOCOL, type, rid, ...payload }, '*'); setTimeout(() => { if (pending.has(rid)) { pending.delete(rid); reject(new Error('timed out')); } }, 15000); }); } window.addEventListener('message', (e) => { const m = e.data; if (!m || !m.rid || typeof m.type !== 'string') return; const s = pending.get(m.rid); if (!s) return; pending.delete(m.rid); m.ok ? s.resolve(m.data) : s.reject(new Error((m.error && m.error.message) || 'failed')); });
client.data.put(...), client.queue.enqueue(...)). Same protocol, nicer ergonomics — see capability-api.md.The samples, easiest first #
Foundations
| Module | Teaches | Build step? |
|---|---|---|
hello-world-webpage | a module is just a webpage in a window | none |
svelte-scratchpad | building a module UI with Svelte + the bundler | Svelte |
Single-task demos — one capability each
Each of these is focused on exactly one capability so you can read it in a minute and copy just the part you need. Every browser demo is one ui/index.html with the same inline bridge client above; each README also shows the SDK equivalent.
| Demo | Capability | Kind |
|---|---|---|
demo-files | files.read + files.write | browser |
desktop-integration-demo | files.manage + desktop.* (VFS management, place on Desktop, shortcut, theme) | browser |
demo-notifications | notifications (toast + badge) | browser |
demo-settings | settings.self (durable KV) | browser |
demo-storage | storage.read (usage/quota) | browser |
demo-media | openMedia (range streaming) | browser |
demo-office | readOffice / saveOffice (in-place .csv/.xlsx edit) | browser |
demo-inter-module | modules.use (discover + handoff) | browser |
inventory-tracker | data.store (collections of JSON docs) | browser |
demo-scheduler | jobs.schedule (interval/cron) | browser |
demo-client-webgl | client.webgl (+ the client.* flags) | browser |
demo-pixi | client.webgl (PixiJS renderer, bundled + inlined) | Svelte |
broad-reader | files.read.all (consent-gated broad read) | Svelte |
demo-worker-basic | worker.execute (the run() contract) | worker |
demo-streaming-worker | run_streaming (A3 — large files, by path) | worker |
demo-task-queue | jobs.queue (background enqueue/poll/cancel) | worker |
demo-fetch-worker | network.fetch (declared-origin egress) | worker |
media-toolkit-worker | worker.native (ffmpeg) + network.fetch.broad | worker |
worker.service (persistent services) is admin-only — there's no module-facing demo; see worker-guide.md and capability-api.md.Combined references (kitchen-sink)
If you'd rather see many capabilities exercised in one place: capability-demo (the core bridge) and advanced-capabilities-demo (the newer data/queue/scheduler messages, with their security gates shown).
A suggested path
- Read `hello-world-webpage`, then any one
demo-*— you now understand the iframe, the bridge, and one capability end to end. - Build something with `inventory-tracker` — the data store is the workhorse for app-like modules (inventory, libraries, task managers). Baseline — declare it and go.
- Reach for the worker demos (
demo-worker-basic→demo-task-queue→media-toolkit-worker) when you need real compute — that's where the admin-HIGH grant flow becomes concrete.
Package & install a sample #
A module package is just a zip with hanabi.module.json at its root.
# from the repo root npm --workspace packages/module-cli run hanabi -- module pack examples/modules/inventory-tracker --out inventory-tracker.zip # → a .zip
Then in the Toolbox: Module Store → Upload, choose the zip, install, and launch. First-party-style samples with only baseline/consent capabilities (1–5 above) run immediately. Samples that declare admin-approved or admin-HIGH capabilities (#6) must be published and granted first — that's the platform working as designed.
Where to read next #
converting-apps.md— turn an existing HTML or Python app into a module.packaging.md— package a module folder into an uploadable.zip.capability-api.md— every bridge message + SDK method, and the capability tier table (baseline / consent / admin / admin-HIGH).manifest-reference.md— everyhanabi.module.jsonfield.worker-guide.md— the server half (therun(inputs, options, ctx)contract).developer-guide.md— build, test, publish a module end-to-end.limitations.md— the honest boundary of what a module can and can't do.