onboarding
v0.1.14Guided console tour — step content, per-operator progress in state, and an injected page that spotlights the console element each step talks about.
Install
iii trigger compose::add worker=onboarding@0.1.14Run in your project with the iii engine running. Exact versions are immutable and digest-pinned.
Full markdown
/workers/onboarding.md?version=0.1.14. Paste it into an LLM prompt or pipe it through curl from a worker.Install
iii trigger compose::add worker=onboarding@0.1.14Dependencies
Readme
onboarding
The guided console tour. The page and its tab are called onboarding; a "tour" is one unit of content inside it, so a second tour can be added without renaming anything.
Three parts:
| Part | Lives in | Owns |
|---|---|---|
| Content | src/tours.mjs |
which tours exist, their steps, and the console element each step points at |
| Progress | the state worker, scope onboarding |
how far each operator got, so a reload resumes mid-tour |
| Rendering | ui/page.tsx (injected into the console) |
the step list and the spotlight box |
Functions
onboarding::tours::list— every tour in curriculum order.onboarding::tours::get— one tour with all of its steps.onboarding::progress::get— the status of every step per tour, with the trigger evidence that closed it, plus the next tour to offer.onboarding::steps::complete— mark one step complete. Passfiredwhen a trigger closed it; its type and payload are kept as evidence. The first time a step closes, it is also announced on theonboarding:steps:completetopic (see Events).onboarding::steps::reset— forget one tour and start it again.onboarding::subscribe— add an email address to the iii product-update list (the last step's signup box; the POST happens here, never in the browser).
Progress is written with state::update and its ordered atomic ops, never
state::get then state::set: two invocations can interleave around an
await, and a read-then-replace write would drop the earlier one. merge
walks and creates the path it names, so one step lands under its own tour.
A reset nulls its tour (the op set reaches top-level keys only for remove)
and the read drops nulls on the way out.
Events
Each closed step is published to the onboarding:steps:complete topic through the
queue worker, so anything that follows a tour subscribes instead of reading
this worker's state. The payload:
{
"tour_id": "console-basics",
"tour_title": "Find your way around",
"step_number": 3,
"step_id": "coder",
"step_title": "CODER"
}The step number is the 1-based position in the tour, read from the ordered step list. A step announces itself once per subject: a reload, or a second report from the agent, writes the same progress again but publishes nothing.
Delivery is durable (iii::durable::publish, and subscribers bind the
durable:subscriber trigger type): the message waits in the queue and is
retried until a subscriber takes it, instead of being dropped when nothing is
listening at that instant. The publish itself is best effort — a project
without the queue worker logs the failure and still closes the step.
Conditions
A step can wait on a real engine trigger instead of a button. The page binds
it the way the console's own live pages do — a per-tab function id, then
registerTrigger pointed at it — so what the operator sees is the payload the
engine delivered, not a simulation.
The condition is its own row under its step, and stays there once it fires: the trigger type, its state, and the time it fired. Expanding it shows the binding config and the payload, the way the harness shows a function call. The row opens when the operator opens it: a trigger that fires does not throw its payload over whatever is being read.
The tour ships two kinds. The first step waits on harness::turn-completed,
so it closes when the agent answers. The four steps whose work the AGENT does
— Composability, Discoverability, Extensibility, Reactivity — each wait on a
state trigger bound to one key, step_, in the onboarding
scope; their prompts end by telling the agent to write it as it ends its turn.
The end of the turn is the boundary, not the agent's own sense of being
finished — asked for the latter it reports while it is still working. The
trigger matches the key, not the value: whatever the agent writes there, the
write is the report.
That is what keeps the open step and the step being worked on the same step. A long build used to read as finished the moment its prompt was sent, because the button closed the step. The page binds every reachable condition when the step is displayed, before any button is clicked, so the wait is already running when the prompt goes out.
Anchors
A step's anchors are selectors for the console element it talks about, best
first. The first is always a class the console carries for this tour: onboarding-menu-bar, onboarding-tabs, onboarding-palette,
onboarding-conversations, onboarding-composer, onboarding-traces. Each
one sits in ade/web/src beside a short comment that says it is a tour
anchor. pnpm test fails if a step points at a class the console no longer
has. The selectors after the first are the console's own stable hooks, so the
box still lands on a console build older than the anchor classes.
The spotlight is one fixed box in document.body that follows the anchor's
rectangle, animated with CSS transitions on the console's motion tokens (see
ui/src/spotlight.ts). No animation library: it is one moving rectangle.
Styling
The page is styled with the console's OWN CSS — its utility classes and the
uiClasses recipes (card, listItem, chip) — so it inherits the house
spacing, edges and hover states, and follows the theme. ui/styles.css holds
only what those cannot express: the spotlight box, the status dot, the
progress bar, the caret, and the payload block. The markup stays hand-rolled
rather than built from shared components, so the page renders on a console
build older than a component it would otherwise import.
The steps
The tour says that this is an engine with workers, not a chat app with plugins, and the steps together cover CODER: Composable (the harness is a worker calling functions other workers register), Observable (the traces step, anchored on the console's traces screen), Discoverable (one engine, one palette), Extensible (this page is a worker's page; install more from the package repo), Reactive (the trigger step). The last step, "Stay in touch", takes an email address for the roadmap and product updates, with icon links to Discord, GitHub, X and LinkedIn, and a link to the docs. After it, "Clean up" asks the agent to remove this worker and close the pane — the same composition move as adding one, run backwards. It carries no condition: the worker that would report the step done is the worker being removed.
The Observability step takes two clicks. The first places the traces panel
beside the tour; the button then becomes a green, briefly pulsing Continue,
which is what closes the step. Opening a panel and having read it are
different things, and one click could only say the first.
Build
pnpm install # worker deps (iii-sdk)
pnpm build # ui/dist assets, then dist/bundle
pnpm testpnpm build (= build:bundle, what the release runs) first runs build:ui:
from the repo root it installs ui's slice of the shared console-UI workspace
and builds ui/dist with the same builder every worker UI uses. It then
writes the publishable shape: dist/bundle/index.mjs with the worker and its
dependencies inlined, and the two page assets beside it.
onboarding::ui-content reads whichever layout has the page — beside the
bundle, or ui/dist in a checkout.
III_ONBOARDING_UI_WATCH=1 re-registers a changed asset, which hot-swaps the
page in every open console tab. III_ONBOARDING_UI_DIR overrides where the
assets are read from.
API reference (JSON)
{
"functions": [
{
"description": "Read an operator’s tour progress: per tour, the status of every step and the trigger evidence that closed it, plus which tour to offer next.",
"metadata": {},
"name": "onboarding::progress::get",
"request_schema": {
"properties": {
"subject": {
"type": "string"
}
},
"type": "object"
},
"response_schema": {
"properties": {
"next_tour_id": {
"type": [
"string",
"null"
]
},
"tours": {
"properties": {},
"type": "object"
}
},
"required": [
"tours",
"next_tour_id"
],
"type": "object"
}
},
{
"description": "Mark one tour step complete. Pass `fired` when a trigger closed it — its type and payload are kept as the evidence the tour shows back.",
"metadata": {},
"name": "onboarding::steps::complete",
"request_schema": {
"properties": {
"fired": {
"properties": {
"function_id": {
"type": "string"
},
"payload": {},
"trigger_type": {
"type": "string"
}
},
"type": "object"
},
"step_id": {
"type": "string"
},
"subject": {
"type": "string"
},
"tour_id": {
"type": "string"
}
},
"required": [
"tour_id",
"step_id"
],
"type": "object"
},
"response_schema": {
"properties": {
"step": {
"properties": {},
"type": "object"
}
},
"required": [
"step"
],
"type": "object"
}
},
{
"description": "Forget every step of one tour, so it starts from the beginning again.",
"metadata": {},
"name": "onboarding::steps::reset",
"request_schema": {
"properties": {
"subject": {
"type": "string"
},
"tour_id": {
"type": "string"
}
},
"required": [
"tour_id"
],
"type": "object"
},
"response_schema": {
"properties": {
"reset": {
"type": "boolean"
}
},
"required": [
"reset"
],
"type": "object"
}
},
{
"description": "Add an email address to the iii product-update list.",
"metadata": {},
"name": "onboarding::subscribe",
"request_schema": {
"properties": {
"email": {
"type": "string"
},
"source": {
"type": "string"
}
},
"required": [
"email"
],
"type": "object"
},
"response_schema": {
"properties": {
"subscribed": {
"type": "boolean"
}
},
"required": [
"subscribed"
],
"type": "object"
}
},
{
"description": "Fetch one tour by id with every step: id, title, body, and the CSS anchor the console page spotlights for that step.",
"metadata": {},
"name": "onboarding::tours::get",
"request_schema": {
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"response_schema": {
"properties": {
"tour": {
"properties": {},
"type": "object"
}
},
"required": [
"tour"
],
"type": "object"
}
},
{
"description": "List every available tour in curriculum order with its id, title, description, and step count. Call this to find out what onboarding content exists.",
"metadata": {},
"name": "onboarding::tours::list",
"request_schema": {
"properties": {},
"type": "object"
},
"response_schema": {
"properties": {
"tours": {
"type": "array"
}
},
"required": [
"tours"
],
"type": "object"
}
},
{
"description": "Serve the injectable onboarding console page assets.",
"metadata": {
"internal": true
},
"name": "onboarding::ui-content",
"request_schema": {
"properties": {
"path": {
"type": "string"
}
},
"required": [
"path"
],
"type": "object"
},
"response_schema": {
"properties": {
"content": {
"type": "string"
},
"content_type": {
"type": "string"
}
},
"required": [
"content",
"content_type"
],
"type": "object"
}
}
],
"triggers": []
}