# onboarding

> Guided console tour — step content, per-operator progress in state, and an injected page that spotlights the console element each step talks about.

| field | value |
|-------|-------|
| version | 0.1.14 |
| type | bundle |
| license | Apache-2.0 |
| repo | https://github.com/iii-hq/workers |
| author | iii |

## installation

```sh
iii trigger compose::add worker=onboarding@0.1.14
```

## dependencies

- `ade` @ `latest`
- `queue` @ `latest`
- `state` @ `latest`

## 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. Pass `fired` when a trigger closed it; its type and payload are kept as evidence. The first time a step closes, it is also announced on the `onboarding:steps:complete` topic (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:

```json
{
  "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_<id>_completed`, 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

```sh
pnpm install          # worker deps (iii-sdk)
pnpm build            # ui/dist assets, then dist/bundle
pnpm test
```

`pnpm 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": []
}
```
