@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.
- package/LICENSE +21 -0
- package/bundled/github/agents/archipelago-build.agent.md +298 -0
- package/bundled/github/agents/island-create.agent.md +261 -0
- package/bundled/github/agents/island-extract.agent.md +360 -0
- package/bundled/github/agents/island-locate.agent.md +208 -0
- package/bundled/github/agents/motu-checks.agent.md +158 -0
- package/bundled/github/agents/motu-evidence.agent.md +267 -0
- package/bundled/github/agents/motu-regions.agent.md +304 -0
- package/bundled/github/host-rules.md +43 -0
- package/bundled/github/prompts/archipelago-build.prompt.md +19 -0
- package/bundled/github/prompts/island-create.prompt.md +37 -0
- package/bundled/github/prompts/island-extract.prompt.md +59 -0
- package/bundled/github/prompts/island-locate.prompt.md +43 -0
- package/bundled/packages/adapters/angularjs/vite.mjs +43 -0
- package/bundled/packages/adapters/next/lagoon/next-stubs.tsx +108 -0
- package/bundled/packages/adapters/next/vite.mjs +121 -0
- package/bundled/packages/adapters/vite/vite.mjs +179 -0
- package/package.json +64 -3
- package/src/alias-loader.mjs +63 -0
- package/src/alias-register.mjs +28 -0
- package/src/asset-stub.cjs +3 -0
- package/src/asset-stub.mjs +5 -0
- package/src/cli.mjs +78 -0
- package/src/commands/adopt-root.mjs +194 -0
- package/src/commands/archipelago-init.mjs +314 -0
- package/src/commands/archipelago.mjs +138 -0
- package/src/commands/assurance.mjs +40 -0
- package/src/commands/check.mjs +346 -0
- package/src/commands/codegen.mjs +29 -0
- package/src/commands/coverage.mjs +358 -0
- package/src/commands/create.mjs +277 -0
- package/src/commands/defaults.mjs +129 -0
- package/src/commands/diff.mjs +595 -0
- package/src/commands/fixtures.mjs +153 -0
- package/src/commands/init.mjs +473 -0
- package/src/commands/integrate.mjs +208 -0
- package/src/commands/integration.mjs +1349 -0
- package/src/commands/lagoon-try.mjs +272 -0
- package/src/commands/lagoon.mjs +1419 -0
- package/src/commands/link.mjs +110 -0
- package/src/commands/region-coverage.mjs +604 -0
- package/src/commands/removal-check.mjs +878 -0
- package/src/commands/skills.mjs +156 -0
- package/src/commands/snapshot.mjs +456 -0
- package/src/commands/sync.mjs +38 -0
- package/src/commands/verify/island-runtime.mjs +581 -0
- package/src/commands/verify/island-static.mjs +586 -0
- package/src/commands/verify/island.mjs +135 -0
- package/src/commands/verify/reach.mjs +426 -0
- package/src/commands/verify/region-composition.mjs +391 -0
- package/src/commands/verify/region-config.mjs +330 -0
- package/src/commands/verify/region-flows.mjs +760 -0
- package/src/commands/verify/region-sources.mjs +516 -0
- package/src/commands/verify/region.mjs +271 -0
- package/src/commands/verify/report.mjs +338 -0
- package/src/commands/verify/screens-runtime.mjs +123 -0
- package/src/commands/verify.mjs +18 -0
- package/src/lagoon-build.mjs +25 -0
- package/src/lagoon-dev.mjs +32 -0
- package/src/lib/alias-core.mjs +49 -0
- package/src/lib/archipelagos.mjs +111 -0
- package/src/lib/assurance.mjs +144 -0
- package/src/lib/baselines.mjs +107 -0
- package/src/lib/bundlability.mjs +123 -0
- package/src/lib/changed.mjs +157 -0
- package/src/lib/component-props.mjs +145 -0
- package/src/lib/config.mjs +451 -0
- package/src/lib/contracts.mjs +244 -0
- package/src/lib/data-flow.mjs +47 -0
- package/src/lib/declared-sources.mjs +101 -0
- package/src/lib/dock-inject.mjs +47 -0
- package/src/lib/effects.mjs +97 -0
- package/src/lib/eject.mjs +715 -0
- package/src/lib/evidence-json.mjs +74 -0
- package/src/lib/expect-calls.mjs +53 -0
- package/src/lib/finding-memory.mjs +0 -0
- package/src/lib/flow-calls.mjs +53 -0
- package/src/lib/framework.mjs +98 -0
- package/src/lib/host-rules.mjs +55 -0
- package/src/lib/host-sources.mjs +121 -0
- package/src/lib/island-placement.mjs +64 -0
- package/src/lib/island-render.mjs +105 -0
- package/src/lib/islands.mjs +112 -0
- package/src/lib/lagoon-bundle.mjs +138 -0
- package/src/lib/lagoon-declares.mjs +553 -0
- package/src/lib/lagoon-materialize.mjs +217 -0
- package/src/lib/lagoon-vite.mjs +788 -0
- package/src/lib/live-url.mjs +53 -0
- package/src/lib/node-aliases.mjs +73 -0
- package/src/lib/operations.mjs +145 -0
- package/src/lib/provenance-plugin.mjs +74 -0
- package/src/lib/region-refs.mjs +40 -0
- package/src/lib/remote.mjs +171 -0
- package/src/lib/scaffold.mjs +606 -0
- package/src/lib/screens.mjs +266 -0
- package/src/lib/snapshots.mjs +85 -0
- package/src/lib/stubs.mjs +114 -0
- package/src/lib/ts-project.mjs +42 -0
- package/src/lib/util.mjs +626 -0
- package/src/lib/worlds.mjs +236 -0
- package/src/lib/write-if-changed.mjs +35 -0
- package/src/lib/writes-coverage.mjs +32 -0
- package/src/playwright-lagoon.mjs +2305 -0
- package/src/run.mjs +385 -0
- package/src/runtime-harness.mjs +225 -0
- 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.
|