@motujs/cli 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/LICENSE +21 -0
  2. package/bundled/github/agents/archipelago-build.agent.md +298 -0
  3. package/bundled/github/agents/island-create.agent.md +261 -0
  4. package/bundled/github/agents/island-extract.agent.md +360 -0
  5. package/bundled/github/agents/island-locate.agent.md +208 -0
  6. package/bundled/github/agents/motu-checks.agent.md +158 -0
  7. package/bundled/github/agents/motu-evidence.agent.md +267 -0
  8. package/bundled/github/agents/motu-regions.agent.md +304 -0
  9. package/bundled/github/host-rules.md +43 -0
  10. package/bundled/github/prompts/archipelago-build.prompt.md +19 -0
  11. package/bundled/github/prompts/island-create.prompt.md +37 -0
  12. package/bundled/github/prompts/island-extract.prompt.md +59 -0
  13. package/bundled/github/prompts/island-locate.prompt.md +43 -0
  14. package/bundled/packages/adapters/angularjs/vite.mjs +43 -0
  15. package/bundled/packages/adapters/next/lagoon/next-stubs.tsx +108 -0
  16. package/bundled/packages/adapters/next/vite.mjs +121 -0
  17. package/bundled/packages/adapters/vite/vite.mjs +179 -0
  18. package/package.json +64 -3
  19. package/src/alias-loader.mjs +63 -0
  20. package/src/alias-register.mjs +28 -0
  21. package/src/asset-stub.cjs +3 -0
  22. package/src/asset-stub.mjs +5 -0
  23. package/src/cli.mjs +78 -0
  24. package/src/commands/adopt-root.mjs +194 -0
  25. package/src/commands/archipelago-init.mjs +314 -0
  26. package/src/commands/archipelago.mjs +138 -0
  27. package/src/commands/assurance.mjs +40 -0
  28. package/src/commands/check.mjs +346 -0
  29. package/src/commands/codegen.mjs +29 -0
  30. package/src/commands/coverage.mjs +358 -0
  31. package/src/commands/create.mjs +277 -0
  32. package/src/commands/defaults.mjs +129 -0
  33. package/src/commands/diff.mjs +595 -0
  34. package/src/commands/fixtures.mjs +153 -0
  35. package/src/commands/init.mjs +473 -0
  36. package/src/commands/integrate.mjs +208 -0
  37. package/src/commands/integration.mjs +1349 -0
  38. package/src/commands/lagoon-try.mjs +272 -0
  39. package/src/commands/lagoon.mjs +1419 -0
  40. package/src/commands/link.mjs +110 -0
  41. package/src/commands/region-coverage.mjs +604 -0
  42. package/src/commands/removal-check.mjs +878 -0
  43. package/src/commands/skills.mjs +156 -0
  44. package/src/commands/snapshot.mjs +456 -0
  45. package/src/commands/sync.mjs +38 -0
  46. package/src/commands/verify/island-runtime.mjs +581 -0
  47. package/src/commands/verify/island-static.mjs +586 -0
  48. package/src/commands/verify/island.mjs +135 -0
  49. package/src/commands/verify/reach.mjs +426 -0
  50. package/src/commands/verify/region-composition.mjs +391 -0
  51. package/src/commands/verify/region-config.mjs +330 -0
  52. package/src/commands/verify/region-flows.mjs +760 -0
  53. package/src/commands/verify/region-sources.mjs +516 -0
  54. package/src/commands/verify/region.mjs +271 -0
  55. package/src/commands/verify/report.mjs +338 -0
  56. package/src/commands/verify/screens-runtime.mjs +123 -0
  57. package/src/commands/verify.mjs +18 -0
  58. package/src/lagoon-build.mjs +25 -0
  59. package/src/lagoon-dev.mjs +32 -0
  60. package/src/lib/alias-core.mjs +49 -0
  61. package/src/lib/archipelagos.mjs +111 -0
  62. package/src/lib/assurance.mjs +144 -0
  63. package/src/lib/baselines.mjs +107 -0
  64. package/src/lib/bundlability.mjs +123 -0
  65. package/src/lib/changed.mjs +157 -0
  66. package/src/lib/component-props.mjs +145 -0
  67. package/src/lib/config.mjs +451 -0
  68. package/src/lib/contracts.mjs +244 -0
  69. package/src/lib/data-flow.mjs +47 -0
  70. package/src/lib/declared-sources.mjs +101 -0
  71. package/src/lib/dock-inject.mjs +47 -0
  72. package/src/lib/effects.mjs +97 -0
  73. package/src/lib/eject.mjs +715 -0
  74. package/src/lib/evidence-json.mjs +74 -0
  75. package/src/lib/expect-calls.mjs +53 -0
  76. package/src/lib/finding-memory.mjs +0 -0
  77. package/src/lib/flow-calls.mjs +53 -0
  78. package/src/lib/framework.mjs +98 -0
  79. package/src/lib/host-rules.mjs +55 -0
  80. package/src/lib/host-sources.mjs +121 -0
  81. package/src/lib/island-placement.mjs +64 -0
  82. package/src/lib/island-render.mjs +105 -0
  83. package/src/lib/islands.mjs +112 -0
  84. package/src/lib/lagoon-bundle.mjs +138 -0
  85. package/src/lib/lagoon-declares.mjs +553 -0
  86. package/src/lib/lagoon-materialize.mjs +217 -0
  87. package/src/lib/lagoon-vite.mjs +788 -0
  88. package/src/lib/live-url.mjs +53 -0
  89. package/src/lib/node-aliases.mjs +73 -0
  90. package/src/lib/operations.mjs +145 -0
  91. package/src/lib/provenance-plugin.mjs +74 -0
  92. package/src/lib/region-refs.mjs +40 -0
  93. package/src/lib/remote.mjs +171 -0
  94. package/src/lib/scaffold.mjs +606 -0
  95. package/src/lib/screens.mjs +266 -0
  96. package/src/lib/snapshots.mjs +85 -0
  97. package/src/lib/stubs.mjs +114 -0
  98. package/src/lib/ts-project.mjs +42 -0
  99. package/src/lib/util.mjs +626 -0
  100. package/src/lib/worlds.mjs +236 -0
  101. package/src/lib/write-if-changed.mjs +35 -0
  102. package/src/lib/writes-coverage.mjs +32 -0
  103. package/src/playwright-lagoon.mjs +2305 -0
  104. package/src/run.mjs +385 -0
  105. package/src/runtime-harness.mjs +225 -0
  106. package/README.md +0 -3
@@ -0,0 +1,158 @@
1
+ ---
2
+ name: motu-checks
3
+ description: The motu rules for RUNNING and READING checks, in full: which check when and what it costs (motu check, --runtime, --fast, --changed, --audit, snapshots), the three exit codes (0 holds · 1 contradicted · 2 could not run — retry, do not repair), a check that looked at nothing, `motu integrate check` as the last mile, placed vs rendered, what motu does not check, and the lagoon as part of the handover. Load before running checks to decide one is done, when reading a verdict you do not understand, and before handing work over.
4
+ ---
5
+
6
+ # motu-checks — Running and reading checks
7
+
8
+ The rules for running and reading motu's checks — the detail behind the short `motu:rules` block in the project's instructions. Read the part you need; each section stands alone.
9
+
10
+ ## The rules
11
+
12
+ WHAT MOTU DOES NOT CHECK: unit tests and `tsc` are deliberately outside every motu check. motu is
13
+ integration-shaped — regions, coupling, what renders from a declared seed. Logic inside a source or a
14
+ helper needs the host's OWN test runner, and motu will never tell you it is missing. Run the host's
15
+ typecheck and tests yourself; a green `motu check` says nothing about either.
16
+
17
+ THE LAGOON IS PART OF THE HANDOVER, not an optional step. Before you say UI work is done, a person
18
+ must be able to OPEN it:
19
+
20
+ motu lagoon serve --watch --detach # survives your session; stop it with --stop
21
+
22
+ It announces itself to the shared host and appears in the gallery under a slug scoped to your branch,
23
+ so several agents working the same repository do not overwrite each other's preview. Every `motu
24
+ check` verdict prints either the live URL or how to start one. **End your report with that URL and
25
+ the specific state you want looked at** (`motu lagoon states`) — a summary of a passing check is not
26
+ a handover, because the thing the work is for is a rendered region and nobody has seen it.
27
+
28
+ - `motu integrate check` is the LAST MILE, and the lagoon cannot answer it: does the HOST compose the
29
+ region (`createRegion`), place every declared slot, read it back (`useRegion`), and — the one that
30
+ bites — has the page stopped keeping its own `useState` of a key an island produces? A region can
31
+ pass every other check while no browser has ever rendered it, because every other check looks at
32
+ motu's files. Run it before saying an integration is done.
33
+
34
+ - Before saying UI work is done: `motu check` (island + archipelago verify over everything, plus
35
+ removal-check), on top of the host's own build. It takes `--json`. Warnings are findings, not noise —
36
+ `props-match` and `ownership` exist because each caught real coupling. It is STATIC and fast: verify
37
+ answers "has this drifted from what it declares?", which is a question about source.
38
+
39
+ - `motu check --runtime` (or `motu island verify <name> --runtime`) adds what only a browser can
40
+ answer: the island mounts, its scenarios differ, it fits every declared viewport, axe finds nothing
41
+ in it, and every declared write reaches its key. It costs SECONDS PER ISLAND — a browser pass per
42
+ scenario × viewport — so it is a PUNCTUAL gate: before handing work over, in CI, or nightly across
43
+ the project. Not the dev loop. While you work, iterate in the lagoon and lean on the static checks;
44
+ `--verbose` names each runtime step with what it cost. The lane opens ONE lagoon page for the whole
45
+ run and re-aims it per island (data in, declared outputs fired), so the cost is the first boot, not
46
+ a boot per island — do not add checks that navigate or restart it. Viewports and the a11y severity policy are
47
+ declared once in `lagoon.config.json`.
48
+
49
+ ## A check that looked at nothing has not passed
50
+
51
+ `report.ok(check, msg, seen)` takes what it examined, and `seen: 0` becomes a `skip` automatically —
52
+ so a check cannot report success from an empty search whether or not its author thought about
53
+ emptiness. Pass a count (or the array) at every `ok` where the input set can be empty, and the report
54
+ prints it: `✓ islands-registered all 3 island tag(s) are registered · 3 declared tag(s)`.
55
+
56
+ This exists because `removal-check` printed "no motu references in the host application" over a fully
57
+ integrated app — it scanned Next's directories, found none of the host's, and called the emptiness a
58
+ pass. It now exits 1 with "scanned 0 files … nothing was examined, so nothing is proved".
59
+
60
+ When you add an escape hatch, TEST THE HATCH. `hostSources` was added for exactly that bug, with a
61
+ message telling users to set it, and did nothing for a day: `loadMotuConfig` returns a hand-built
62
+ whitelist and silently drops any key not in it. Add the key there, then reproduce the original bug and
63
+ watch the fix refuse it.
64
+
65
+ ## Three outcomes, three exit codes
66
+
67
+ A check that did not RUN is not a check that failed. `report.inconclusive` is for the environment
68
+ giving out — a port that never opened, Chromium missing, a dev server losing a race — and the verdict
69
+ line says INCONCLUSIVE rather than PASS or FAIL.
70
+
71
+ 0 the declarations hold
72
+ 1 something contradicted them -> repair
73
+ 2 a check could not run -> retry, do NOT repair
74
+
75
+ This exists for unattended loops. A human shrugs at a port timeout and re-runs; an agent reads `✗` and
76
+ repairs a bug that does not exist, and several agents produce several confident wrong repairs.
77
+
78
+ The classifier requires BOTH a known environmental signature AND the absence of any application cause
79
+ in the output — because a port that never opened *because the project does not build* is a finding,
80
+ whatever the headline says. Verified in both directions: a missing browser gives exit 2, a missing
81
+ import in the region's frame still gives exit 1.
82
+
83
+ ## Three tiers, and which one you are in
84
+
85
+ motu check STATIC 1.4s every change
86
+ motu island verify <n> --runtime ONE ISLAND ~15s while you work
87
+ motu archipelago verify <id> --runtime ONE REGION ~25s while you work
88
+ motu lagoon try <id> [step…] EXPLORE ~5s before you know what to assert
89
+ motu check --runtime --fast NO BROWSER 44.0s while you work (5.9s with --changed)
90
+ motu check --runtime DOES IT WORK 103.5s before handing work over
91
+ motu check --audit IS IT USABLE before integrating, and in CI
92
+ motu island snapshot --all --remote DID IT MOVE 89s before handing work over
93
+ motu archipelago snapshot --all --remote DID THE PAGE 18s before handing work over
94
+
95
+ (measured on a 16-island, 2-region project; the snapshots on a 20-island, 3-region one)
96
+
97
+ NAME WHAT YOU TOUCHED. While you are working on a region, the runtime check you run is that region and
98
+ its islands — `motu archipelago verify login --runtime`, `motu island verify login-form --runtime` —
99
+ not the whole project. The full `motu check --runtime` re-drives a browser for every island in the
100
+ repo to tell you about the one you changed; run it ONCE, before handing work over, and after that only
101
+ if you changed something shared.
102
+
103
+ `--changed` IS NOT THAT SCOPING, and reaching for it instead will cost you the run. It narrows only
104
+ while EVERY changed file maps to an island or a region, and widens back to everything the moment one
105
+ does not — loudly, which is the design: a check that quietly examined less than you think is the worse
106
+ failure. What does not map is most of a real session: the page, the screen that installs a source, the
107
+ app-side region type, `roots/lagoon/src/lagoon.tsx`, a shared evidence module, the generated barrels.
108
+
109
+ Measured on this project, mid-session: 16 changed files unattributable, so a full run. Files that
110
+ provably cannot reach a render (`.claude/`, `.vscode/`, `.idea/`, `.github/`, `*.md`) no longer widen
111
+ it and the lagoon's per-region module now maps to its region — that took it to 14 unattributable, and
112
+ it STILL ran everything, because `app/dashboard/profile/page.tsx` and four app-side files remained.
113
+
114
+ So: `--changed` is worth passing, and it is not a plan. The plan is naming the region.
115
+
116
+ THE LAST THING YOU DO, both of them, with `--changed`. Scoped to one touched island that is 11s and
117
+ 15s — cheaper than the `--fast` loop — because `--changed` maps a changed island to the regions that
118
+ declare it, and a region's picture is its members composed. Unscoped they cost about what
119
+ `check --runtime` costs, which is why they belong in the same slot: the punctual gate, not the loop,
120
+ and not a nightly job — drift detected a day later has a dozen changes behind it and can no longer be
121
+ attributed to any of them.
122
+
123
+ `--changed` widens back to everything, loudly, when a changed file belongs to no single island; and a
124
+ scoped sweep that pictured nothing exits 2 with NOTHING TO PICTURE rather than green.
125
+
126
+ `--fast` means NO BROWSER: islands mount under happy-dom in node. It used to be an island-only flag
127
+ whose REGIONS still booted chromium — 43s of an 87s run — because a region's flows, mutation and render
128
+ are browser-only. They are now skipped and said (`– region-runtime`), so the loop is genuinely
129
+ browser-free and the browser work happens once, at the end.
130
+
131
+ What the browser still owns, and why it cannot move: layout (`responsive`), accessibility (`a11y`), and
132
+ everything about a REGION — a flow only exists where islands are mounted together. Those are the last
133
+ step before handover, not the loop.
134
+
135
+ `--runtime` answers whether the region does what it declares: islands mount, scenarios differ, declared
136
+ writes reach their key, flows end as promised, and every step could have failed. `--audit` adds
137
+ `responsive` and `a11y` — the two most expensive per-island checks, whose answer changes when the
138
+
139
+ RENDERING changes, not when a key moves. Running them on every edit buys nothing and costs half the
140
+ run; running them never is how a region ships unusable on a phone. So they are a gate, not a loop, and
141
+ a run without them says so (`– audit responsive + a11y not run`) rather than staying quiet.
142
+
143
+ `--audit` implies `--runtime`: asking whether a UI is usable at every viewport only means something
144
+ against something that rendered.
145
+
146
+ ## Placed is not the same as rendered
147
+
148
+ `integrate check` reads the host's SOURCE: it can see `<X.Island slot="y">` and cannot see whether the
149
+ branch containing it ever runs. A slot inside `{isOpen && …}`, a ternary or a `.map()` callback now
150
+ reports as conditionally placed — a WARNING, because a drawer or a permission gate is often exactly
151
+ right, and what is not right is not knowing. acme's actions page places eight islands inside
152
+ `{weeksLoaded ? (availableWeeks.length > 0 …)}`, which is the same branch that hid a crash on the empty
153
+ list for months.
154
+
155
+ The lagoon renders every declared slot unconditionally, so it cannot catch this either. Between the two
156
+ of them: the lagoon proves the island works, `integrate check` proves the page names it, and NOTHING
157
+ proves the page reaches it. That gap is the honest boundary of static integration checking — closing it
158
+ needs the page rendered, which is the host's own test runner, not motu's.
@@ -0,0 +1,267 @@
1
+ ---
2
+ name: motu-evidence
3
+ description: The motu rules for EVIDENCE and LOOKING, in full: island scenarios and region flows (seed, emit, click, provide, expect, expectRender, expectCalls), where evidence lives and how it is typed, the lagoon override map (layout, seed, channels, wire), worlds, state addresses (`motu lagoon states`), exploring with `motu lagoon try`, flows that can fail (flow-mutation, null-island, render coverage), undeclared calls, `motu diff`, the fresh-agent final look, visual baselines, the lens, publishing. Load before writing or changing any *.evidence.ts, lagoon override, world or flow, and before the final look.
4
+ ---
5
+
6
+ # motu-evidence — Evidence and looking
7
+
8
+ The rules for evidence and for looking at what you built — the detail behind the short `motu:rules` block in the project's instructions. Read the part you need; each section stands alone.
9
+
10
+ ## The rules
11
+
12
+ - NEW UI content starts as an island with at least one scenario in its `*.evidence.ts`, and is looked
13
+ at in the lagoon (`motu lagoon serve --watch`, or `motu island verify <name>`) BEFORE it is placed
14
+ on a page. An island must render from defaults alone — every input optional, with a default.
15
+
16
+ - AND LOOK AT IT AGAIN AT THE END, whether or not anything failed. Every other instruction to look is
17
+ conditioned on a DIFFERENCE, and new work has none by construction: the snapshot run reports every
18
+ shot as `new`, goes green, and asks for nothing. That is the one moment looking matters most. A
19
+ run with `new` shots says `LOOK` instead of `PASS` for this reason — open the region view, read
20
+ the screen, and only then accept. Twice this has caught what no check could: a fixture inventing a
21
+ vocabulary the application does not use, and an island stylesheet that was bundled and never
22
+ applied. Both passed every check; both were found by opening the page.
23
+
24
+ - WHO LOOKS IS PART OF THE CHECK, and it is not you. The look is an AGENT's — a rendered state read
25
+ by something that can perceive it — but the agent that BUILT the region is the worst possible
26
+ reader of it. Both catches above were an INVENTION rather than a drift, and an invention sits in
27
+ its author's context as a PREMISE: a fixture's vocabulary looks right to whoever chose it, every
28
+ time. So spawn a FRESH agent for the final look, and hand it exactly three things — the state's
29
+ URL (`motu lagoon states`), where the application's own vocabulary lives (the types, the modules
30
+ the region names), and the question. Do NOT hand it the diff, the plan, the transcript or your
31
+ reasoning: those are the contamination you are spending a subagent to avoid. Ask it: *does this
32
+ screen belong to THIS application — does anything render a word, a state or a shape the app never
33
+ uses?* One subagent, and it is the only check in the set that can catch something invented whole.
34
+ Two tiers, and they catch different things: ASSERTIONS catch drift — an artifact contradicting
35
+ what it declares, which is why `tsc` is the first and cheapest of them, the app's own types
36
+ failing the moment a rename lands on a region that no longer matches. PERCEPTION catches
37
+ invention — an artifact that contradicts nothing, passes everything, and describes an application
38
+ that does not exist. No mechanical check reaches the second, because every mechanical check
39
+ compares the artifact to itself; rendering is what forces it to be compared to the world.
40
+
41
+ - THE SAME GOES FOR THE DECLARATION. Before islands are built on a new or changed archipelago, a fresh
42
+ agent reads it back: given the archipelago, where the app's vocabulary lives, and the question
43
+ *what does this region promise, and what does it permit that a reasonable person would not
44
+ expect?* — a key with no producer, a key nobody reads, an owner its name does not suggest. Never the
45
+ survey, the plan or the transcript. An invented ownership hides in its author's context exactly as
46
+ an invented vocabulary does, and everything built afterwards is proved AGAINST it. The survey marks
47
+ every key, owner and slot as stated, inferred or default; the inferred and default ones, with the
48
+ read-back, are what a person reviews.
49
+
50
+ - CHANGED UI starts with the scenario that shows the state you are about to change. Then change it,
51
+ look at it, and keep the scenario as the regression. A scenario set whose members render identically
52
+ fails `data-flow` — that check exists because fake evidence is worse than none.
53
+
54
+ - A region declares its FLOWS in `<id>.evidence.ts` beside the archipelago: a seed, an island's
55
+ declared `emit`, and the keys the region must hold afterwards. That is where a coupling becomes
56
+ something that runs — `motu archipelago verify --runtime` fails when a flow no longer ends as
57
+ declared. A flow may only fire a declared output; if you reach for a selector, you have left the
58
+ harness and written a browser test.
59
+
60
+ - EVIDENCE lives in evidence files, and the lagoon is not one. An island's scenarios go in
61
+ `<name>.evidence.ts`, a region's flows in `<id>.evidence.ts`, and anything BOTH need goes in one
62
+ module they import with a RELATIVE specifier — `@/` does not resolve in the loaders that read
63
+ these files, and the failure is silent (the island keeps its scenarios in one check and loses them
64
+ in another). Type that module against the APP's own types with `import type`: it erases at
65
+ runtime, so the loaders are unaffected, and a renamed field then fails the build there instead of
66
+ quietly previewing last month's shape. Invented data in a lagoon frame is a third copy nobody
67
+ diffs.
68
+
69
+ - The lagoon override file is a MAP, not a page. `layout` points at the APPLICATION's own layout
70
+ component — never a second JSX copy of the arrangement, which drifts for the same reason a second
71
+ copy of the region's vocabulary does. `seed` is data. Anything that REACTS has one of TWO homes,
72
+ and neither is the frame. A `channel` installs the application's own SOURCE over a port, which is
73
+ what answers an island's intent. The WIRE — `wireFrom({ to, appRoutes, tables, fixtures })`, a
74
+ `wire` field beside `channels` — answers HTTP BENEATH the app's real service, so the service itself
75
+ runs instead of being replaced. Reach for the wire when the app's own client code is what you want
76
+ exercised, and for a channel when the region needs a key fed; a region commonly has both. Both are
77
+ installed for every view, which is the property that matters: the checks that drive the region see
78
+ the same answers a human does. What fired is legible either way — the lens shows a channel, and
79
+ `data-reach` / `provenance` show what went over the wire. Behaviour written inside the frame runs
80
+ only in the region view, where those checks are not.
81
+
82
+ - VISUAL BASELINES live on the lagoon host, not in the repository: `motu island snapshot --all
83
+ --remote` pictures every island (scenario × viewport) and `motu archipelago snapshot --all --remote`
84
+ pictures the COMPOSED page — the arrangement, which is not declared and which no island shot can
85
+ see. A difference writes `.actual.png` and `.diff.png` under `.motu/snapshots/` — LOOK at them, then
86
+ `motu island snapshot --accept <name>` only when the change is what you intended. Accepting is a
87
+ DECISION, which is why it is its own command: `--update` overwriting files is what made "the
88
+ baseline is stale" and "you broke something" the same red. Never use `--update` on a project whose
89
+ baselines are on the host — it starts a second, drifting copy. A region diff names which member
90
+ islands also changed; when none did, the arrangement moved.
91
+
92
+ - EVERY DECLARED STATE IS AN ADDRESS — do not hand-drive the store to look at one. `motu lagoon
93
+ states` lists them (`--json`, `--base <url>`): an island's scenario is
94
+ `/?target=island:<tag>&scenario=<name-or-slug>`, a region's flow is
95
+ `/?region=<id>&flow=<name>&step=<n>`, with `step` stopping the replay early so the states BETWEEN
96
+ the steps are reachable too. A WHAT-IF — an island's scenario played INSIDE its region, the rest
97
+ live — is `/?region=<id>&state=<id>.scenario.<tag>.<slug>`, the same string the dock's Share
98
+ copies, and a screen is `/?screen=<id>`. ALL are the gallery — the entry `serve` and `publish`
99
+ build and the one a person opens. An island opens ALONE there, through the same one-slot mount `island verify`
100
+ drives, so a standalone island (in no region) is addressable too. That is how you
101
+ look at the state you are about to change, and how you hand someone a link to it. A name that
102
+ resolves to nothing REFUSES to render — banner, console error, `window.__motuLagoonState.ok:
103
+ false` — because being handed the default state while believing it is the one you named is the
104
+ failure worth engineering against. Read `window.__motuLagoonState` before believing a screenshot.
105
+
106
+ - EXPLORE BEFORE YOU ASSERT, with `motu lagoon try <region> [step…]` — not a hand-written
107
+ `page.evaluate` against `window.__motuLagoon`, and not a flow you run for minutes to read its failure.
108
+ It replays the steps you give it (the verbs a flow has: `in <world>`, `seed <key>=<json>`, `click <name>`,
109
+ `emit <slot>:<event> [json]`, `provide <key>=<json>`) in about five seconds and prints what EACH step
110
+ changed: the region's keys (`old → new`), what each slot shows, the calls that landed; `--shot <dir>`
111
+ pictures every step. Reach for it before writing a flow, and whenever the screen and the state may
112
+ disagree — a state the screen MASKS (the right page drawn over the wrong keys) is invisible to every
113
+ check and to a screenshot, and shows here as a key that moved. When you know what should hold, add
114
+ YOUR assertions (`shows <slot>: <text>`, `hides …`, `holds <key>=<json>`) — never copied from the
115
+ output, which agrees with itself by construction — and `--keep "<name>"` appends the flow to
116
+ `<id>.evidence.ts` only if every step asserts something and all of it held. No selectors and no eval:
117
+ if you need either, you have left the harness. Exit 2 means it could not run — retry, do not repair.
118
+
119
+ - The lens is the DOCK's Seams tab — **Ctrl/Cmd-K**, then `Seams` — and it opens on the REGION SHEET:
120
+ one row per key, who owns it, who reads it, what it holds, whether it has moved, and a flag where a
121
+ declared write has never fired or the host answered an island. Read it before reading the
122
+ archipelago; it is the same declaration, proved by the region that is running. Below the sheet,
123
+ FEEDS is the channels and ASKED FOR is every outbound ask through all three doors — contract,
124
+ traced host module, wire — each row naming the door and the owner (`island:<tag>` / `source:<id>`).
125
+ WHERE IT EXISTS MATTERS: the dock is drawn by whoever HOSTS the lagoon — `motu lagoon serve`, or the
126
+ motu host, which wraps every published lagoon in a shell — so the GALLERY and
127
+ any hosted lagoon URL have it, while the focused standalone entry (`lagoon.html`, what
128
+ `motu island verify` drives) does not, and neither does `/__motu_frame`, the artifact by itself.
129
+ Ctrl/Cmd-Shift-G is the OLD standalone debug overlay (`mountDebugOverlay`), which the gallery does
130
+ not mount; pressing it there does nothing.
131
+
132
+ - SHOW YOUR WORK on the shared lagoon host, don't stand up another server. `motu lagoon publish
133
+ --remote` (no argument — the URL and token live in `~/.config/motu/host.json`) uploads the
134
+ self-contained page and prints two URLs: `latest`, which follows every publish, and an immutable
135
+ one keyed by the commit. That is how a human looks at what you built without your process staying
136
+ alive. The rule is ONE long-running host plus occasional spawns: `motu lagoon serve --watch` /
137
+ `lagoon serve --watch` while you iterate, killed when you stop. A second permanent preview
138
+ server is what the host replaces.
139
+ Absolute asset paths (`/images/…`) work on a local lagoon and 404 once hosted — the publish output
140
+ warns about them, and the warning is a finding.
141
+
142
+ ## A flow the host drives, asserted on what renders
143
+
144
+ A region whose islands only READ has no `emit` to declare — and it still has a promise. Use a
145
+ `provide` step (the app moving its own state) and end on `expectRender`, not on `expect`.
146
+
147
+ `expect` names region keys, so a step whose `expect` names the key its own `provide`/`emit` targets
148
+ cannot fail unless the lagoon itself is broken. The assertion worth writing is what ANOTHER island
149
+ shows: Twenty's record page proves `editingWidgetId` moves exactly one of two widgets, in both
150
+ directions, and back to neither. Negate one clause and re-run before believing a green flow — a step
151
+ that cannot fail is not a check.
152
+
153
+ ## A call nothing declared fails the flow
154
+
155
+ Every backend call a flow step causes is held to a declaration (`flow-calls`): one no fixture, table or
156
+ route answered fails the step and names what to declare. Never make an island swallow the error to pass
157
+ it. A failure a fixture asks for (`status: 500`) does not count. `expectCalls` on a step is the other
158
+ half — the calls that state may make, and only those (`[]`: it asks for nothing), answered or not. Screens are held to it too, since the
159
+ app's layout makes calls no region's flow does, and a screen can declare flows of its own
160
+ (`src/screens/screens.evidence.ts`: `click` the app's link or `navigate`, then `expectScreen` /
161
+ `expectRender`). `motu screen verify [id]`; `motu check --runtime` includes it.
162
+
163
+ With coverage on, prove production reports arrive: `motu coverage doctor --url <production origin>`
164
+ after a deploy names the failing hop. `motu coverage init` writes the route (`--mode log` for a server
165
+ with no outbound network, collected later by `motu coverage pull --from <log>`).
166
+
167
+ ## Say what a change did to the flows
168
+
169
+ Before handing over, `motu diff --base <branch point> --json` replays the touched regions' flows at the
170
+ base and now and lists every change to keys, renders, calls and declarations. Fix each finding or
171
+ explain it (`--explain file.json`, `{ "<id>": "<why it was intended>" }`) until it exits 0, and quote
172
+ the explanations in your report. A `locked` finding cannot be explained: behaviour moved in a region
173
+ whose archipelago and evidence did not, so fix it or change the declaration. List every SPEC CHANGE
174
+ in your report, and every `weakens` one with the reason — never weaken a flow to make it pass.
175
+
176
+ ## A flow step must be able to fail
177
+
178
+ `flow-mutation` runs with the region's flows and asks whether they could have failed. Two rules:
179
+
180
+ - **By construction** — a step whose `expect` names only keys that same step `provide`d asserts that
181
+ the lagoon stored what it was handed. Decidable without a browser, and always an error.
182
+ - **By mutation** — each assertion-bearing step is re-run with its stimulus changed (a value the
183
+ region cannot mistake for the real one). If the assertion still holds, it does not depend on the
184
+ input and is asserting a constant.
185
+
186
+ Write flows that survive both: end on a key another island produces, or on `expectRender` of an
187
+ island that is NOT the one being driven.
188
+
189
+ Know the limit. A stand-in's invented vocabulary passes both rules — an assertion on text you wrote
190
+ into a stub is perfectly sensitive to its stimulus. Only rendering the application's own component
191
+ makes that visible, which is why an island on a host that owns the component IS that component.
192
+
193
+ ## An island ships with evidence that asserts its OWN render
194
+
195
+ Two agents added an island to one region in parallel. Both wrote correct islands; the merge is where
196
+ it went wrong, and no check caught it.
197
+
198
+ Both had extended the lagoon frame's `rowFor` ternary — whose fallback was the NOTES row — so taking
199
+ either side of that conflict alone makes the other agent's widget render as Notes. Wrong, and
200
+ rendering. `archipelago verify` was byte-identical between the correct and the naive resolution: the
201
+ frame is ARRANGEMENT, it is not declared, and nothing checks it. The only thing that would have
202
+ distinguished them is a flow asserting on the new island's own rendered content — which neither agent
203
+ wrote, because nothing required it.
204
+
205
+ So: adding an island means adding a step to `<id>.evidence.ts` that asserts what THAT island renders.
206
+ Not the region's existing steps — its own. `render-coverage` names the slots no flow looks at.
207
+
208
+ `expectRender` asserts what a person can PERCEIVE in that slot, not one DOM property: rendered text,
209
+ a control's accessible name (`aria-label`/`alt`/`title`), a field's current value, a checkbox's
210
+ `checked`/`unchecked` state, and a control's announced state as `<name> pressed|selected|current`
211
+ (`aria-pressed`, `aria-selected`, `aria-current` — a toggle group's value is not text otherwise). `innerText` alone could not see a passenger's name in an input or a
212
+ seat's identity in an aria-label, which left an island made of form controls with almost nothing
213
+ assertable. Widening the haystack does not weaken the check — a wrong value and another island's text
214
+ both still fail — and the failure message prints the whole surface, so what WAS there is visible.
215
+
216
+ A step may assert with NO stimulus. Some islands take no input — Twenty's tasks and timeline widgets
217
+ ignore the row they are bound to and render from the record context — so `provide`ing something just
218
+ to have a stimulus produces a constant, and `flow-mutation` will (correctly) reject it. Write
219
+ `{ expectRender: { '<slot>': '<text>' } }` alone: the claim is "this slot renders THIS island", which
220
+ is not a data flow and is still worth making.
221
+
222
+ Two smaller ones from the same run. Prefer an append-only LOOKUP over a ternary chain in a frame two
223
+ people may extend. And if you use git worktrees, do not symlink `node_modules` into them: `git add -A`
224
+ commits the symlink, the merge replaces the real directory with it, and the next build dies on ELOOP.
225
+
226
+ ## A state no flow can reach is a WORLD, not a seed
227
+
228
+ When the state you need depends on what the app is ANSWERED — a paused member, a slow or empty backend,
229
+ no week generated yet — do not seed the keys it shows. A key a running source produces is PINNED once
230
+ seeded, and the source can never move it: the flow then proves the lagoon stored what you wrote. Declare
231
+ a world (`defineWorlds` in the lagoon override module, exported as `worlds`) that re-answers the source's
232
+
233
+ PORT (`regions.<id>.channels.<source>: [args]`) or a stand-in's FACT (`worldFact(name, fallback)`), and
234
+ name it on the flow (`world: '<id>'`). `world-pins` refuses the seed; `world-differs` replays the flow
235
+ without its world and warns when nothing changes; `world-used` flags a world no flow names. A world never
236
+ touches the app's code or the arrangement. If the state still cannot be reached because the PAGE derives
237
+ it in an effect, that is a finding about the page — say so; do not grow the world to imitate the effect.
238
+
239
+ ## Where an island's input came from
240
+
241
+ The lagoon replaces a host module so completely that nothing shows a fetch happened: no request, no
242
+ network row, and the lens shows the KEYS that resulted, never the question that produced them. Looking
243
+ at a region and seeing no HTTP at all is accurate and tells you nothing.
244
+
245
+ ONE LEDGER, THREE DOORS. An ask leaves by the CONTRACT (a `call()` the transport answers), by a
246
+
247
+ TRACED HOST MODULE (a stub wrapping its exports in `traced(module, fn, impl)`), or by the WIRE (a fake
248
+ `fetch` beneath the app's own client). All three record to `recordOutbound` in `@motujs/core`, and both
249
+ readers — `provenance` and the lens's ASKED FOR block — group by door and name who asked:
250
+
251
+ ✓ provenance asked for: contract: shots.list() ×2 · wire: table:shots(select) — by island:shot-list, source:shots
252
+
253
+ `contract.effects` says which host modules an island IMPORTS; this says which it CALLED, with what arguments,
254
+ and how often. Two things become visible that nothing else catches: an island that renders content
255
+ while calling NOTHING (its data came from somewhere it never declared), and a module it imports but
256
+ never reaches (a stale `effects` entry, or a stub standing in for something unused).
257
+
258
+ WHO ASKED IS PART OF THE ANSWER, and leaving it out cost a lens that lied. A declared SOURCE reads
259
+ inside a channel, at region level, so its ambient island is null — and the lens filtered its
260
+
261
+ ASKED FOR list on "has an island", which is not a filter but a deletion. On a region fed by a source
262
+ it printed 0 asks and *"everything on screen came from the seed"* directly under a FEEDS row saying
263
+ that source had fired ×44 a second earlier. An empty list is survivable; an empty list with a
264
+ confident wrong explanation is what makes people stop reading the surface built to answer this.
265
+
266
+ It is also the integration list. The calls recorded here are exactly what the real page has to answer,
267
+ which is the closest thing motu has to confronting the lagoon with the page it targets.