MoonWalk¶
Step 2 of Processes, described once: MoonWalk, the model every process is written in and the map that shows them, chosen against the inventory and approved on 2026-10-16. The schema is moonwalk/moonwalk.schema.json.
What it must do¶
- One model holds every process, the end user's and the contributor's, so the whole journey is one map.
- The map is navigable: on the website and in the app, zoomed out to the whole model, zoomed in by search and filters, and opened at the place the reader is, a docs page or the card open in the app.
- A process already done is not offered again: what the reader has done is known, and the map and the app's suggestions leave it out.
- The pages named for an activity are generated from it: building, testing, integrating, troubleshooting, installing on Linux, updating the firmware, logging an issue and the rest of the how-tos and tutorials. So a process carries its prose as well as its steps.
- Every runner reads the same steps: the docs, the app, the tests, MoonTube and the agents.
The three levels, one per textbook¶
| Level | What it is | Textbook | Zoom |
|---|---|---|---|
| The journey | stages, left to right: install, first light, everyday use, advanced use, maintain, plan and set up, develop, run on hardware, test, document and commit, review and merge, release | the user story map (Patton) | zoomed out |
| A process | a goal, its actor, the processes it depends on, its steps, its branches | an Arazzo workflow | middle |
| A step | a precondition, an action, an expectation | Given, When, Then (Gherkin) | zoomed in |
Arazzo-shaped, not Arazzo. Arazzo requires every step to call an API operation or another workflow (its step object's operationId, operationPath or workflowId), and most of the journey's steps are a click in the app, a command or a person plugging in a board. So the model keeps Arazzo's structure and names (dependsOn, onSuccess, onFailure, a workflow per process, several per document) with its own step shape. A process whose steps are all API calls can be exported to Arazzo later, once the REST API is in OpenAPI, which is where Redocly's Respect and Spectral would come in.
Files¶
moonwalk/
moonwalk.schema.json
02-install.yaml one file per journey stage: the stage, then its processes; the number orders them
...
15-release.yaml
The number orders the backbone, as the MoonTube clips are ordered. YAML because a process carries prose: multi-line text, Markdown and comments read well in YAML and badly in JSON strings. MkDocs already depends on PyYAML, so the docs build reads it at no cost. The schema validates YAML and JSON alike.
stage: First light
summary: The first effect on the first lights, on the desktop or a board.
processes:
- id: first-light-desktop
title: First light on the desktop
actor: user
goal: An effect runs on the desktop's preview.
dependsOn: [first-look]
page: tutorials/how-moonlight-works.md
intro: |
Markdown shown above the steps on the generated page.
steps:
- id: open-effects
say: Open the Effects card.
when: { open: { module: Effects } }
- id: add-rainbow
say: Add a Rainbow effect.
when: { add: { module: Effects, type: RainbowEffect } }
then:
- { module: RainbowEffect, exists: true }
- id: brighter
say: Set brightness low in Drivers.
when: { set: { module: Drivers, control: brightness, value: 20 } }
then:
- { module: Drivers, control: brightness, minimum: 1, maximum: 20 }
detail: |
Markdown for the page only: why, what to watch for.
outro: |
Markdown below the steps: what next, what to read.
A step¶
say: the instruction, one action, verb first, in at most 8 words: Power on the device. It is the docs line, the app's step, the film caption and the test's name, so a reader knows the step at a glance. What happens next goes in its detail.actor: who performs the step, when it is not the process's actor; the generator fills in the process's otherwise. BPMN's lane: the commit is the product owner's process, and the checks in it are the agent's.given: preconditions, of four kinds. A process done first (install); an expectation on the device; the change touching a path ({ changed: ["src/**"] }, the globs GitHub Actions'on.push.pathstakes, which the agent and CI read fromgit diff --name-only); and the reader's choice ({ input: platform, equals: Windows }, orin: [...]for several options). The scenarios'fixture,resetandtopand a clip'ssetupare all this.when: one action, by intent rather than gesture, so each runner picks its own gesture: the app points at the card's +, the test applies a state document, MoonTube clicks.
| Action | Target | Runs in |
|---|---|---|
open |
a module (its card), a page of the app, or a URL | app, film, docs |
set |
module, control, value | test, app, film, docs |
add, replace |
parent module, type | test, app, film, docs |
delete, clear |
module | test, app, film, docs |
apply |
a state document | test, docs |
call |
an HTTP method and path, an OpenAPI operation once step 12 lands | test, docs |
run |
a MoonDeck script with its arguments, or a shell command | agent, CI, docs |
read |
a docs page or a section of one | docs, app |
process |
another process, run as a subprocess | all |
manual |
free text: plug in a board, look at the strip | docs, app |
The runner list is derived from the action, never written down. A runner meeting an action it cannot perform describes it, as the scenario runner now prints SKIP.
- then: expectations, the step's success criteria. A control's value (equals, notEquals, the shape expect_control has, or a range with JSON Schema's minimum and maximum), a module that exists, an HTTP status, a file's content. The same expectation is a test's assertion, the app's "done, next", and the "already done" check below.
- onSuccess, onFailure: Arazzo's branches, goto a step, end or retry, on what the device shows. Troubleshooting's decision tree is written this way.
A process is a how-to, never a tutorial. Diátaxis keeps the two apart: a tutorial is a lesson with an outcome its teacher chose, a how-to serves the reader's own goal with the reader's own values. A step says what to decide or do toward that goal; an example value goes in its detail (a 16×16 panel: width 16, height 16), never in its check, and a check tests only what holds for any reader, such as brightness kept low. The lessons live in MoonTube's clips and in the tutorials, and a step may embed a clip as an illustration.
A choice is an input. What the reader picks, such as the platform, is a process's inputs, a JSON Schema as Arazzo's workflow inputs are, and a step runs only for the option its given names. The view shows the options as a row of buttons above the steps, defaulting to the reader's own platform, and the plain text names each step's option in bold; the app asks once and fills in what it knows already, such as running on a desktop.
- detail: Markdown for the generated page only.
- section: opens a numbered section of the generated page with this heading, as a tutorial groups its steps into chapters.
Text and shape have caps, as the help's cards do. The values are the constants at the top of gen_moonwalk.py. The generator refuses a say over its cap and a title or goal past theirs; check_docgen reports the rest as a ratchet. A word is one a reader reads, so an image's alt text and a link's target do not count. A process is measured as one reader reads it, per option of its choice.
| Text | Cap | Past it |
|---|---|---|
say |
8 words, one sentence | refused |
| title | 10 words | refused |
| goal | 25 words, one sentence | refused |
a step's detail |
120 words | reported |
| an intro or an outro | 150 words | reported |
| a process, per reader | 600 words | reported: split it, or move reference and explanation to their pages |
| a process's steps, per reader | 10 | reported: fold steps into a subprocess, drawn as one box |
| a stage's processes | 8 | reported: split the stage, so the map's columns stay balanced |
the map and moonwalk.js, gzipped |
40 KB | reported: the map carries less |
Text past a cap goes to its Diátaxis kind rather than being squeezed: a table to a reference page, a reason to an explanation, a symptom to Troubleshooting, a second route to its own process.
Targets use the keys help.json uses, a module's type or instance name and a control's name, so the reverse index into the help is a lookup.
What the device pilot taught (2026-10-16)¶
Step 4 put the map in the app (🗺️ in the status bar), with done read from the device's state. On the desktop bench:
- Done from the state works where a process says what done looks like. Two of 48 processes carry an expectation the device can answer, joining the WiFi (a known network) and the first light show (a grid, three modules, a brightness), so a device says little yet. It grows as steps gain their
then, which the migration (step 11) brings. - A list counts as its rows, so "a network is known" is
{ module: WiFi, control: known, notEquals: 0 }. Compared as text, an empty list read as done. - The running app proves the install, and nothing says so. The next list offers installing MoonLight to the device showing it. Decided: the installer vouches for what it installed (step 6 of the plan), since it knows the platform for certain, where a running device cannot tell a desktop from a Linux board or a container. A board run confirmed the rest: on a Waveshare ESP32-S3-RGB-Matrix the map marked joining its WiFi done from the board's own state.
- One consent covers the help and MoonWalk, since both come from the docs site, and
?help=localreads the local map through the installer preview, as it reads the local help.
What the hosts pilot taught (2026-10-16)¶
Step 6 put the map in the installer and MoonDeck, each a host passing drawMap what it knows: here, the processes or stages it is about; done, the processes it vouches for; runs, MoonDeck's last runs.
- The installer vouches for what a device cannot tell. After an install it marks "Install MoonLight" done in its own origin, which the docs site's map shares on Pages, and hands the fact to the device's map through
?walked=installon the device link. The running device then offers the next process rather than the install. - A run proves a step only while it is fresh. MoonDeck marks a process done when every script its steps run last passed, and on the bench those passes were ten days old. The textbook answer is the one
check_esp32_builtalready gives a firmware: a run counts only when it is newer than the sources it covers. - MoonBase's link waits for its target. Its help points at the getting-started section on MoonBase, which becomes a process when that page migrates; the link moves then, with the next MoonBase build.
Systems¶
A process names the MoonLight systems it touches, systems: [MoonInstaller, MoonCore], as the architecture's Part table names them: that table is the list's one home, and the generator reads it and refuses a name it lacks. The generator adds what the steps imply: a MoonDeck script, the web installer's address, a MoonLive module. On the map, system chips filter, and the columns switch between stages and systems, a process touching several standing in each of their columns.
Done, without a log¶
A step is done when its then holds now. The app reads its own state, so "WiFi is set up" is done the moment a network is known, however it got there: through MoonWalk, the installer or by hand. This is the onboarding checklist's textbook rule, completion observed rather than recorded, and it needs no storage. A process is done when every expectation the state can answer holds, and it has at least one. Its last step alone is not enough, since a last step is often one the state cannot see, such as "nothing to save".
What the state cannot show, such as a page read, is marked by the reader and kept in the browser: localStorage on the docs site and in the app, as the help's cache already is. Recorded processes, a commit gate or a release, are done on GitHub (step 14).
The website cannot read a device (an https page cannot call a device's http address), so there only the reader's marks count.
The map¶
- One generated file:
gen_moonwalk.py, besidegen_help.py, validates every stage file against the schema and writesmoonwalk.json: the stages, the processes, the steps with their derived runners, and three indexes, by module, by control and by docs page. It runs in the docs build. - One map, two hosts:
moonwalk.jsdrawsmoonwalk.jsonwith Cytoscape.js (MIT, from cdnjs, about 430 KB, so it never enters the firmware). Compound nodes give the three levels: a stage holds its processes, a process holds its steps. The layout is the story map's grid, stages as columns and processes stacked independsOnorder, computed in a few lines rather than by a layout library. - Semantic zoom: zoomed out shows stages and process titles, zooming in shows the steps and their
say. - Search and filters: text over titles, steps and targets; actor; system; hide what is done. Filters by runner (tested, filmed) wait for the runners' adapters.
- Where you are: the map opens on the reader's choices, Here and Next, with the map fitted to them. Here is the processes touching the card in view in the app (the module index) or the page the reader came from on the site (the page index); Next is the next to do. Neither lists what is done. A link naming one process (
?focus=commit) opens that process. - The process view:
moonwalk.jsrenders a process whole in the map's panel, with the help's renderer: its intro, its steps in their numbered sections with their detail and clips, a choice as a row of options, and its outro. One view on the site and in the app, atmoonwalk.html#<process>; the map carries the steps, and a process's intro, outro and details load frommoonwalk/text/<process>.jsonwhen its view first opens; a page MoonWalk takes over forwards there. The MoonWalk page carries every process's prose as plain text for search and readers without JavaScript, hidden once the view draws. The parts of a page that are reference rather than steps (a table of ports, a test strategy) stay on a reference page and are linked.
What it removes¶
The step lists duplicated across pages, CLAUDE.md, MoonDeck.md and memory, in time; the scenario's and the clip's separate step vocabularies, which become runners of one; hand-drawn process diagrams. Nothing is removed by this step itself: it adds the schema, and the pilot adds the first files.
Decided (2026-10-16)¶
- YAML for the authored files. The generator rejects a boolean where a name belongs, since YAML 1.1 reads an unquoted
on,off,yesornoas one. - Arazzo-shaped, with an export to Arazzo for API-only processes later.
- Done marks in the browser only: what the state cannot show is kept in
localStorage, on the site and in the app. - The pilot is the website's map; the app's map follows once the pilot has taught what it will.
What the pilot taught (2026-10-16)¶
The pilot of step 3 drew 48 processes over 11 stages, three of them with their steps (installing on the desktop, the first light show, the commit). Four findings, each decided and in the model above:
- A step has an actor of its own, defaulting to the process's.
- A step can wait on the change, through
given: { changed: [...] }. - A choice is a process input, with
given: { input, equals }on the steps of each option. - YAML quoting. Each stage file opens with
# yaml-language-server: $schema=moonwalk.schema.json, so an editor with the YAML language server validates and completes while typing. A sentence holding a colon, or a question mark inside{ }, is quoted or written as a|block; the generator names the file and line of a parse error and stops there.