@orkestrel/scaffold 0.0.72 → 0.0.74

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.
@@ -19,7 +19,7 @@ Read the current files in this order:
19
19
  3. [layer.md](references/layer.md) before importing, extending, or debugging the journey layer.
20
20
  4. [captures.md](references/captures.md) before registering a state or placing a capture.
21
21
  5. [styles.md](references/styles.md) before asserting anything the browser resolved.
22
- 6. [statechart.md](references/statechart.md) before declaring a transition or building the harness.
22
+ 6. [statechart.md](references/statechart.md) before declaring a transition or mounting the harness.
23
23
  7. [decide.md](references/decide.md) before routing a question to an instrument.
24
24
  8. `guides/README.md`, the governing guide for the surface, and `ROADMAP.md` when present.
25
25
  9. The `*/types.ts` of every environment the journeys drive, plus the application's root component,
@@ -48,21 +48,105 @@ families a surface owes. It never switches what a declared family proves.
48
48
  scope finding and stop; never prove the remaining families around it.
49
49
  - Assert the declaration itself: a family listed with no proof, and a proof belonging to no listed
50
50
  family, each fail the run.
51
+ - Bind the journey laws to every declared family, not to the journey family alone. A matrix reading
52
+ and a transport assertion reach their surface through the same verbs a journey reaches it through.
53
+ - Change route and theme through the interface in the matrix family and the transport family. A
54
+ family that navigates by calling the application's own router proves the router, and says nothing
55
+ about the screen it reads afterwards.
56
+ - Let the transport family construct the store it hands the application, and drive the application
57
+ from the interface after that. The constructed store is the fixture; it is never the drive.
58
+ - Take an entity read or a storage read as corroboration beside a rendered assertion, never in place
59
+ of one.
60
+
61
+ ## Resolve the population
62
+
63
+ Resolve every target and every population by ARIA role and accessible name as rendered.
64
+
65
+ - Admit a selector only where the population carries no role at all — a paragraph set, a token
66
+ census, a style-escape walk. Declare that selector in the workspace's browser test setup module
67
+ with the reason beside it, and never in a test file.
68
+ - Read a painted population through the platform: `element.checkVisibility()` reports what the box
69
+ tree renders, and a non-zero `getBoundingClientRect()` reports what occupies space. The layer
70
+ publishes no painted-population predicate.
71
+ - Reach for `isRendered` and `isReachable` where the subject is one element's own reachability
72
+ ([layer.md](references/layer.md) → The resolver).
51
73
 
52
74
  ## Read the variant once
53
75
 
54
- Read one `variant` value at run start, and let it choose the capture destination, the matrix row,
55
- and the statechart run together. Never split the theme from the viewport; a split writes a filename
56
- naming a combination the run did not render.
57
-
58
- - Declare every variant once as a published `CaptureVariant` — its `name`, its `width`, its
59
- `height`, and the `apply` that switches the theme — and read that list from the capture family and
60
- the matrix family alike.
76
+ A browser application born by `scaffold new` carries the journey axis: the
77
+ `configs/app/vite.journey.config.ts` wrapper, the root `appJourney` factory, the `test:journey`
78
+ script, and that script's place in the `test` chain. That wiring fans the journey suite out into one
79
+ Vitest project per variant, and each project provides its own variant. Read the variant once at run
80
+ start, and let it choose the capture destination, the matrix row, and the statechart run together.
81
+
82
+ - Read `inject('variant')` for this run's variant name, `inject('variants')` for the declared list,
83
+ and `inject('capture')` for whether this run writes frames.
84
+ - Declare the provided types once, by augmenting Vitest's own `ProvidedContext` in the workspace's
85
+ browser test setup module, so `inject` is typed rather than narrowed at each call.
86
+ - Declare the variant list in `configs/app/vite.journey.config.ts`, the birth-owned wrapper a
87
+ browser application is born with. Its presence is the journey axis. Rename and extend the seeded
88
+ viewports for this application.
89
+ - Activate the axis in a workspace born before it: write `configs/app/vite.journey.config.ts`, run
90
+ `scaffold repair`, then add `npm run test:journey` to the `test` script after `npm run test:app`.
91
+ The repair defines `appJourney` in the root configuration, emits the `test:journey` script, and
92
+ excludes the journey suite from the ordinary `app:browser` project. It does not rewrite the `test`
93
+ chain, so the chain entry is yours to add. Add that entry before the repair instead where you
94
+ prefer: `test:journey` names a configuration rather than a project, so neither order blocks the
95
+ repair.
96
+ - Name each variant for the theme and the viewport it renders, such as `dark-390`. Never split the
97
+ theme from the viewport; a split writes a filename naming a combination the run did not render.
98
+ - Compose each variant's theme `apply` inside the test, from the variant's name. Vitest `provide`
99
+ carries serializable values, so `name`, `width`, and `height` cross that channel and a function
100
+ does not.
101
+ - Apply the theme through the application's own interface wherever the application ships a theme
102
+ control, and through the attribute the surface reads where it does not.
103
+ - Run the axis with `npm run test:journey`, which runs that wrapper and joins the `test` chain. Set
104
+ `CAPTURE` to `1` in your own shell and run `npm run test:journey` again to write the frames; the
105
+ root configuration reads that variable and provides it as `capture`.
106
+ - Keep the journeys in `tests/app/browser/integration.test.ts`. Each variant project collects that
107
+ file alone, and the ordinary `app:browser` project excludes it while the axis is on, so a journey
108
+ written anywhere else runs in no variant.
61
109
  - Loop every declared variant inside one run for the matrix family
62
110
  ([styles.md](references/styles.md) → Run per variant).
63
111
  - Render exactly one variant per run for the capture family
64
112
  ([captures.md](references/captures.md) → Variants).
65
113
 
114
+ `scaffold audit` reports a manifest and a wrapper that disagree as one of the following questions.
115
+ Settle the one it reports before trusting a green run.
116
+
117
+ | The question `scaffold audit` reports | Settle it by |
118
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
119
+ | `The manifest at <target> names a Vitest configuration the plan does not emit and the target does not hold: test:journey --config configs/app/vite.journey.config.ts. Add the configuration, or remove the script that names it and its invocation from the test chain.` | Writing the wrapper and running `scaffold repair`, or removing the `test:journey` script and its chain invocation |
120
+ | `The manifest at <target> does not invoke npm run test:journey from its test chain. Add npm run test:journey after npm run test:app.` | Adding `npm run test:journey` to the `test` script after `npm run test:app` |
121
+
122
+ `scaffold audit` reports the invocation question only when no chain from `test` reaches
123
+ `npm run test:journey` through literal `npm run` calls, so a chain reaching it through an
124
+ intermediate script raises none. It reports the configuration question only for a `test:*` script
125
+ whose text names `vitest`, so a script naming another runner's configuration raises none either.
126
+
127
+ ```ts
128
+ import type { JourneyVariant } from '@orkestrel/test'
129
+ import type { CaptureVariant } from '@orkestrel/test/browser'
130
+ import { inject } from 'vitest'
131
+ // `applyTheme` is the workspace's own setup-module export, named for the act it performs.
132
+ import { applyTheme } from '../../setupBrowser.js'
133
+
134
+ declare module 'vitest' {
135
+ interface ProvidedContext {
136
+ readonly variant: string
137
+ readonly variants: readonly JourneyVariant[]
138
+ readonly capture: boolean
139
+ }
140
+ }
141
+
142
+ const VARIANT = inject('variant')
143
+ const CAPTURE = inject('capture')
144
+ const VARIANTS: readonly CaptureVariant[] = inject('variants').map((variant) => ({
145
+ ...variant,
146
+ apply: () => applyTheme(variant.name),
147
+ }))
148
+ ```
149
+
66
150
  ## Apply the journey laws
67
151
 
68
152
  1. **Drive only what a person can see and reach.** Resolve every interactive target by its ARIA
@@ -80,9 +164,10 @@ naming a combination the run did not render.
80
164
  by an identity read of one frame.
81
165
  5. **Generate the portfolio from the acceptance journeys.** Place each registered state inside the
82
166
  journey that reaches it, and never register a state no journey reaches.
83
- 6. **Commit a value through an act a person performs:** Enter, Tab away, or a named button. Report a
84
- surface that commits on a timer, on an unpredictable event, or only after work the person cannot
85
- observe as a surface finding, and never work around it in the layer.
167
+ 6. **Commit a value through an act a person performs:** `pressKeys('{Enter}')` on the focused
168
+ control, a Tab away, or a named button. Report a surface that commits on a timer, on an
169
+ unpredictable event, or only after work the person cannot observe as a surface finding, and never
170
+ work around it in the layer.
86
171
  7. **Type only what a person would.** Journeys carry trusted input; adversarial payloads belong to
87
172
  the transport family and the parser suites.
88
173
  8. **Perform every interaction step unconditionally.** Never gate a step on whether the control it
@@ -92,15 +177,21 @@ naming a combination the run did not render.
92
177
 
93
178
  ## Import the journey layer
94
179
 
95
- - Import every journey verb, reader, and fixture builder from `@orkestrel/test/browser`. Write one
96
- of your own only where that package publishes none for the act
97
- ([layer.md](references/layer.md) → Import, never implement).
180
+ - Import every journey verb, reader, wait, and fixture builder from `@orkestrel/test/browser`, and
181
+ every host-independent wait and table type from `@orkestrel/test`. Write one of your own only
182
+ where those entries publish none for the act ([layer.md](references/layer.md) → Import, never
183
+ implement).
184
+ - Send every key sequence through `pressKeys`, which refuses a sequence sent while nothing but the
185
+ document body holds focus. Never reach past it to the provider's own keyboard function from a
186
+ journey, a matrix reading, or a statechart phase.
98
187
  - Place a helper you must write in the workspace's browser test setup module, name it for the act,
99
188
  and export it from there under `.claude/rules/tests.md`. Never declare a resolver inside a test
100
189
  file.
190
+ - Prove that setup module with `tests/setupBrowser.test.ts`, and activate its `setup:browser`
191
+ project in the order [layer.md](references/layer.md) → Import, never implement states.
101
192
  - Drive every step through the published verbs, and never dispatch a constructed event
102
193
  ([layer.md](references/layer.md) → What it drives).
103
- - Re-verify every target against what the application renders now whenever markup changes
194
+ - Re-verify every target against what the application renders after any markup change
104
195
  ([layer.md](references/layer.md) → Role vocabulary).
105
196
 
106
197
  ## Derive journeys from intents
@@ -119,8 +210,9 @@ placement and scope `.claude/rules/tests.md` fixes.
119
210
  action it observes; a predicate already true when the poll starts binds nothing.
120
211
  - Assert the state the flow must reach, never the transient path taken to it. A criterion that bans
121
212
  a harmless transient over-refuses and breaks on the next honest implementation.
122
- - Assert the negative beside the positive whenever a value replaces another: the new sentence is
123
- present **and** the old one is gone.
213
+ - Assert the negative beside the positive whenever a value replaces another: name the arriving
214
+ sentence and carry the replaced one in `absent`, so the reading that resolves carries one and not
215
+ the other ([layer.md](references/layer.md) → The waits).
124
216
  - Assert the state a control announces beside every drive that sets it, and on an unselected
125
217
  sibling. A control announcing state owes this assertion whether or not the surface carries the
126
218
  statechart family.
@@ -131,6 +223,25 @@ placement and scope `.claude/rules/tests.md` fixes.
131
223
  - Report a bare accessible name that answers for more than one reachable element on one screen as a surface
132
224
  finding, and target through role or region until the surface is fixed.
133
225
 
226
+ ### The intents every surface owes
227
+
228
+ Write a journey for each of the following wherever the surface has that state. Take the expected
229
+ outcome from the product guide; this skill supplies the mechanism and invents no copy, no redirect,
230
+ and no title scheme.
231
+
232
+ | Intent | The journey proves |
233
+ | ----------------- | ------------------------------------------------------------------------------- |
234
+ | Arrival | The entry route renders its own screen, read through a named region |
235
+ | An unknown route | What the application does with a route it does not carry, and what it says |
236
+ | An empty result | What a query matching nothing renders, in the product's own words |
237
+ | The document name | The title each screen publishes, asserted per screen |
238
+ | A render failure | What a person reads when a component throws, rather than an unexplained surface |
239
+
240
+ - Report a missing outcome as a product finding, with its evidence site, rather than inventing the
241
+ copy the surface owes.
242
+ - Assert the title from `document.title` per screen, against the title the product guide names for
243
+ that screen. Report a screen the guide gives no title as a product finding.
244
+
134
245
  ## Prove the refusals
135
246
 
136
247
  - Give every surface a refusal family: the controls a person must not reach in the state the
@@ -146,30 +257,38 @@ placement and scope `.claude/rules/tests.md` fixes.
146
257
  ## Declare the transport family
147
258
 
148
259
  - Name the block for what it proves — persistence, restart, storage failure.
149
- - Drive it through the application's real session and store contracts. Build a store that stalls a
150
- read, fails a fixed number of reads, or fails a write as an inert configurable implementation of
151
- the published interface, under the real-implementation law in `AGENTS.md`.
260
+ - Drive it through the application's real session and store contracts.
261
+ - Build the host's own storage conditions with `createStorage`: `reads: false` and `writes: false`
262
+ withhold what a browser with site data blocked withholds, `quota` caps the accepted `setItem`
263
+ calls, and `permit()` grants the withheld permissions the way a person allowing site data grants
264
+ them. It replenishes no quota, because room and permission are different refusals.
265
+ - Assert the withheld voice on its `name` as well as its message: a withheld operation raises a
266
+ `DOMException` named `SecurityError`, and a write past the quota raises one named
267
+ `QuotaExceededError`.
152
268
  - Prove the visible half in a journey: the failure sentence a person reads, and the retry control
153
- that clears it.
269
+ that clears it. A storage failure whose visible half is a control that silently does nothing is a
270
+ surface finding.
154
271
  - Assert restart by starting a second session over the same store and polling the restored value.
272
+ - Take a stalled read to the application's own asynchronous store contract, never to `Storage`.
273
+ `Storage` is synchronous, so a hanging read is not expressible against it and a fixture that fakes
274
+ one is proving a surface the application does not have.
155
275
 
156
276
  ## Prove the styles
157
277
 
158
278
  Follow [styles.md](references/styles.md) for the resolved-value law, the per-variant run, the
159
- composited contrast reading and its under-bar negative control, the authored-class census, the
279
+ composited contrast reading and its published control, the authored-class census, the
160
280
  `extractStyles` reading, and the token comparison.
161
281
 
162
282
  ## Prove the statechart
163
283
 
164
284
  Follow [statechart.md](references/statechart.md) for the transition table, the scenario per
165
- transition, the runner, the harness a person watches, and the gate that drives the harness through
166
- the interface.
285
+ transition, the runner, the mounted harness, and the gate that reads its tally.
167
286
 
168
287
  ## Generate the portfolio
169
288
 
170
289
  Follow [captures.md](references/captures.md) for the state registry and its placement rules, the
171
- theme-and-viewport variant matrix, the always-on filename proof, the capture-run membership proof,
172
- and how a state that exists only during an activation is captured.
290
+ variant matrix, the always-on filename proof, the capture-run membership proof, and how a state that
291
+ exists only during an activation is captured.
173
292
 
174
293
  Where a capture and a green suite disagree, take the capture as the evidence and the fixture as the
175
294
  defect.
@@ -181,27 +300,54 @@ Route review of the portfolio to the `orkestrel-polish-surface` campaign. Do not
181
300
  Follow [decide.md](references/decide.md) before spending a round on a question. It fixes which
182
301
  instrument judges which claim, what `prove` cannot serve, and what the run's written artifact holds.
183
302
 
303
+ ## Mutate each assertion class
304
+
305
+ Mutate each assertion class once, read the red, restore, and read the green.
306
+ `.claude/rules/quality.md` § Instruments owns the law this satisfies.
307
+
308
+ | Assertion class | The mutation | What must change |
309
+ | --------------- | -------------------------------------------------------------------- | --------------------------------- |
310
+ | Journey | Omit the act the journey performs, keeping collection valid | The destination assertion reddens |
311
+ | Refusal | Make the withheld control reachable, or present, without renaming it | The asserted voice changes |
312
+
313
+ - Omit the act rather than weakening the assertion. An assertion a missing act leaves green cannot
314
+ tell arrival from never having left.
315
+ - Change reachability rather than the name. A renamed control reddens on absence, which is a finding
316
+ the refusal family already carries.
317
+
184
318
  ## Accept
185
319
 
186
320
  Completion requires all of:
187
321
 
188
322
  - every in-scope user intent reaching its outcome through the interface, with no step that reaches
189
323
  past it;
324
+ - the intents every surface owes present wherever the surface has the state, each outcome taken from
325
+ the product guide and every missing one reported as a product finding;
190
326
  - keyboard-only reachability proven on every surface the journeys cover;
191
327
  - a refusal family per surface, each asserting one exact failure voice;
192
328
  - the transport family declared separately, driven through real implementations, and convergent;
193
329
  - the declared families each proven, and the declaration itself asserted;
194
- - the matrix family read once per declared variant, each style instrument carrying the negative
195
- control that must read under its bar in the same run;
330
+ - the matrix family read once per declared variant, each style reading carrying its published
331
+ control from [styles.md](references/styles.md) → The published controls in the same run, and the
332
+ contrast reading's control straddling its declared bar;
196
333
  - the authored-class census and the `extractStyles` reading taken on the mounted surface, each
197
334
  reporting the population it walked;
198
- - the statechart table driven to a terminal outcome with no failed row, and the harness gate green;
335
+ - the statechart table driven to a terminal status with no failed row, and the harness tally read
336
+ from the object and from its markup;
199
337
  - the registry-times-variants filename proof and the state-placement proof green in an ordinary run;
200
338
  - one capture run per variant writing every registered file, and the disk-membership proof green;
201
339
  - the written artifact produced for every variant the run rendered, named by that variant;
202
340
  - perception assertions quoting rendered text, and the vocabulary sweep green on the whole page;
341
+ - the browser test setup module proven by `tests/setupBrowser.test.ts` in the `setup:browser`
342
+ project `scaffold repair` registers;
343
+ - each assertion class mutated, with the red reading and the green reading recorded;
203
344
  - the repository gates green, under the independent-verification law in `.agents/orchestration.md`.
204
345
 
346
+ State the engine bound with the verdict. The gate renders one engine, so a claim about a second
347
+ engine is unproven until a reading on that engine records it. The emitted `configs/browsers.ts` doc
348
+ block is the home of that limit and of the condition that reopens it. Cite that doc block, and copy
349
+ neither into a verdict.
350
+
205
351
  Report each journey by the intent it proves, the refusals it establishes, the states it placed, the
206
352
  variants it read, the statechart outcome it reached, and every surface finding the layer's refusals
207
353
  exposed.
@@ -4,19 +4,38 @@ Take every screenshot from an acceptance journey, at the moment that journey is
4
4
  picture names. Never add a test whose only purpose is a screenshot, and never stage a state for the
5
5
  camera that a journey did not reach through the interface.
6
6
 
7
+ ## The vocabulary
8
+
9
+ ```ts
10
+ import type { JourneyVariant } from '@orkestrel/test'
11
+ import type {
12
+ CaptureVariant,
13
+ FrameOptions,
14
+ FrameReading,
15
+ PortfolioInterface,
16
+ PortfolioOptions,
17
+ } from '@orkestrel/test/browser'
18
+ import { captureFrame, createPortfolio, expandCaptures, readFrame } from '@orkestrel/test/browser'
19
+ import { inject } from 'vitest'
20
+ ```
21
+
7
22
  ## The hook
8
23
 
9
24
  `createPortfolio(options)` is the capture door, and `place(state, element?)` is the hook. Build the
10
25
  portfolio once per file from a `PortfolioOptions` value carrying the registry as `states`, every
11
- declared `CaptureVariant` as `variants`, this run's `variant`, the `directory` each file is written
12
- to, and the flag as `enabled`.
26
+ declared `CaptureVariant` as `variants`, this run's variant name as `variant`, the `directory` each
27
+ file is written to, and the flag as `enabled`.
13
28
 
14
29
  - Place a state with `place(state)` for the whole page, and `place(state, element)` where the
15
30
  picture is one element.
16
- - Leave `enabled` unset in an ordinary run. `place` then returns `undefined`, resizes nothing,
17
- writes nothing, and records nothing.
31
+ - Take `enabled` from `inject('capture')`, which the generated root configuration provides from the
32
+ `CAPTURE` environment variable set to `1` in the shell that runs the axis. An ordinary run leaves
33
+ it false, so `place` returns `undefined`, resizes nothing, writes nothing, and records nothing.
18
34
  - Read `files` for the registry expanded across every variant, `placements` for what this run placed,
19
35
  and `paths` for what it wrote. Each hands back a snapshot.
36
+ - Reach for `readFrame` where the written image itself is the subject — its decoded size, or its
37
+ pixels. `captureFrame` already reads each file back and compares its bytes against the frame it
38
+ shot.
20
39
 
21
40
  The package already refuses these, so assert none of them again:
22
41
 
@@ -30,8 +49,7 @@ The package already refuses these, so assert none of them again:
30
49
 
31
50
  ## The registry
32
51
 
33
- Declare the state names and the variants once in the journey file, and build the portfolio from
34
- that declaration.
52
+ Declare the state names once in the journey file, and build the portfolio from that declaration.
35
53
 
36
54
  - Name a state for its surface and its condition — `answer-partial`, `start-storage-failure`,
37
55
  `case-delete-confirmation`.
@@ -44,22 +62,28 @@ that declaration.
44
62
 
45
63
  ## Variants
46
64
 
47
- The run axis is fixed in [SKILL.md](../SKILL.md) → Read the variant once, and the theme switch each
48
- variant's `apply` performs is fixed in [styles.md](styles.md) → Run per variant. The capture family
49
- adds these.
65
+ The run axis is fixed in [SKILL.md](../SKILL.md) → Read the variant once. The capture family adds
66
+ these.
50
67
 
51
- - Produce the portfolio — the registry times the variants — by repeating the run once per variant.
68
+ - Declare the viewport list in the birth-owned `configs/app/vite.journey.config.ts` wrapper, as
69
+ `readonly JourneyVariant[]`. That file is the adopter's, so its names and its viewports are the
70
+ application's to set, and the presence of that file is the journey axis.
71
+ - Compose each variant's `apply` inside the test, from the name the project provided, and hand the
72
+ composed `CaptureVariant` list to `createPortfolio`. `JourneyVariant` carries the `name`, the
73
+ `width`, and the `height` that cross Vitest's `provide` channel; `CaptureVariant` adds the `apply`
74
+ that does not.
52
75
  - Name each variant for the theme and the viewport it renders, such as `dark-390`. The name is the
53
76
  second half of every filename the run writes, so a variant named for one alone produces a
54
77
  portfolio nobody can tell apart.
55
- - Pass the whole variant list as `variants` and this run's name as `variant`. `createPortfolio`
56
- refuses a `variant` no declared variant carries, and `files` expands the registry across the whole
57
- list rather than across the one being rendered.
78
+ - Pass the whole composed list as `variants` and `inject('variant')` as `variant`.
79
+ `createPortfolio` refuses a `variant` no declared variant carries, and `files` expands the
80
+ registry across the whole list rather than across the one being rendered.
81
+ - Produce the portfolio — the registry times the variants — by running the axis once per variant,
82
+ which `npm run test:journey` does in one command.
58
83
 
59
84
  ## The proofs the suite owes
60
85
 
61
- The package times the registry and refuses a bad placement. It asserts nothing about either, so the
62
- suite carries these.
86
+ Write each of the following proofs into the suite. The package asserts none of them.
63
87
 
64
88
  | Proof | Runs | Asserts |
65
89
  | -------------------- | -------------- | ---------------------------------------------------------------------------------- |
@@ -92,6 +116,9 @@ after the click returns.
92
116
  ## Hygiene
93
117
 
94
118
  - Keep the portfolio out of version control.
119
+ - Take each frame after the paint settles. Reach for `waitForAnimations` on the element whose
120
+ transition was running ([layer.md](layer.md) → The waits); a frame shot mid-transition pictures a
121
+ state the interface never rests in.
95
122
  - Regenerate the whole matrix from the journeys after any surface change. Never judge a round
96
123
  against a portfolio that is part old and part new.
97
124
  - Route review of the portfolio to the `orkestrel-polish-surface` campaign, which owns preflight,
@@ -7,7 +7,7 @@ instrument here answers as open, and never answer it with the nearest instrument
7
7
  | -------------------------------------- | -------------------------------------------- |
8
8
  | A compiler, a linter, or a Node runner | The `prove` tool, with its negative control |
9
9
  | A person's eye | The run's written artifact, named by variant |
10
- | A person watching a widget move | A statechart harness deep link |
10
+ | A person watching a widget move | The harness run's frames and its artifact |
11
11
  | The browser's own resolved value | The matrix family ([styles.md](styles.md)) |
12
12
 
13
13
  Never ask `prove` about pixels, and never ask a screenshot about types.
@@ -25,17 +25,26 @@ follow it there rather than restating it here.
25
25
 
26
26
  ## The limit that decides the split
27
27
 
28
- `prove` cannot serve a browser project in `@orkestrel/probe` 0.0.11. The following refusals are each
29
- reproduced:
28
+ `prove` cannot serve a browser project, and the limit belongs to the installed `@orkestrel/probe`
29
+ rather than to a version this file names. Confirm it against the copy this workspace holds before
30
+ routing a rendered question, and record the version you read beside the ruling.
30
31
 
31
- - The runtime stage looks a project up by the name it infers, and a browser project is instantiated
32
- under its browser-expanded name, so the lookup finds nothing and the claim is refused as missing.
33
- - The runtime stage pins the `threads` pool, so a browser project's specification runs in a Node
34
- worker. `@orkestrel/test/browser` imports `vitest/browser` at module scope, so the browser setup
35
- file throws before the case runs and the failure reads as the claim's.
32
+ Read the pool pin, the project lookup, and the guide's own statement. Any of them holding is the
33
+ limit holding:
36
34
 
37
- Never route a rendered question to `prove` while that holds. The pinned-pool refusal arrives as a
38
- case failure, which reads exactly like a broken claim.
35
+ - **The pool pin.** Read the runtime stage in the installed server entry. Where it pins the
36
+ `threads` pool, a browser project's specification runs in a Node worker,
37
+ `@orkestrel/test/browser` imports `vitest/browser` at module scope, and the browser setup file
38
+ throws before the case runs.
39
+ - **The project lookup.** The runtime stage looks a project up by the name it infers from the test
40
+ path, and a browser project is instantiated under its browser-expanded name, so the lookup finds
41
+ nothing and the claim is refused as missing.
42
+ - **The guide's statement.** Read what `@orkestrel/probe`'s own guide says the stages serve. Where
43
+ it names no browser project, no stage claims one.
44
+
45
+ Never route a rendered question to `prove` while any of them holds. The pinned-pool refusal arrives
46
+ as a case failure, which reads exactly like a broken claim. Record the version, the pool pin you
47
+ read, and what the guide says, so the next round re-reads rather than re-deriving.
39
48
 
40
49
  ## The rendered artifact
41
50
 
@@ -61,8 +70,18 @@ Rules the artifact obeys:
61
70
  old and part new.
62
71
  - Keep it out of version control.
63
72
 
64
- ## The harness link
65
-
66
- Send a look a person decides on to a statechart harness deep link rather than to a file. Name the
67
- exact link in the round, and let the person watch the widget move rather than read a still of it
68
- ([statechart.md](statechart.md) → Build the harness a person watches).
73
+ ## The harness run
74
+
75
+ Send a look a person decides on to what the harness run itself produced, rather than to a still of a
76
+ screen.
77
+
78
+ - Place a capture inside the harness run at each state the decision is about, and name the frames in
79
+ the round. The run drove the widget through the interface, so the frames are a record of movement
80
+ rather than a staged pose.
81
+ - Name the artifact beside them. Its accessible-tree lines and its journal say what the widget
82
+ announced while it moved, which a frame cannot carry.
83
+ - Give the harness a final row or a demo step that leaves the widget in its most legible state, so
84
+ the last frame is the one a person wants to look at.
85
+ - Name a deep link only where the workspace already ships a harness page
86
+ ([statechart.md](statechart.md) → A harness page is product). Never ask a round to open a link the
87
+ repository does not serve.