@orkestrel/scaffold 0.0.81 → 0.0.83
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/dist/agents/skills/orkestrel-publish/scripts/wave.js +14 -3
- package/dist/bin/main.js +295 -13
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +5 -3
- package/dist/host/agents/orchestration.md +1 -0
- package/dist/host/agents/skills/orkestrel-harden/references/centralization.md +2 -2
- package/dist/host/agents/skills/orkestrel-journey/SKILL.md +43 -39
- package/dist/host/agents/skills/orkestrel-journey/references/captures.md +3 -3
- package/dist/host/agents/skills/orkestrel-journey/references/decide.md +10 -9
- package/dist/host/agents/skills/orkestrel-journey/references/layer.md +5 -5
- package/dist/host/agents/skills/orkestrel-journey/references/recorded.md +77 -0
- package/dist/host/agents/skills/orkestrel-journey/references/statechart.md +3 -3
- package/dist/host/agents/skills/orkestrel-journey/references/styles.md +9 -9
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +4 -0
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +22 -4
- package/dist/host/claude/agents/orkestrel.md +10 -10
- package/dist/host/claude/rules/application.md +20 -6
- package/dist/host/claude/rules/architecture.md +2 -2
- package/dist/host/claude/rules/browser.md +9 -0
- package/dist/host/claude/rules/documentation.md +2 -1
- package/dist/host/claude/rules/quality.md +5 -0
- package/dist/host/claude/rules/styles.md +35 -12
- package/dist/host/claude/rules/tests.md +15 -10
- package/dist/host/claude/rules/workspace.md +128 -98
- package/dist/host/claude/skills/orkestrel-journey/SKILL.md +1 -1
- package/dist/host/configs/helpers.ts +157 -9
- package/dist/host/configs/policy.ts +64 -61
- package/dist/host/dotfiles/oxlintrc.json +132 -16
- package/dist/host/dotfiles/prettierignore +1 -1
- package/dist/host/guides/README.md +9 -5
- package/dist/host/guides/guide.md +4 -1
- package/dist/host/guides/scaffold.md +482 -115
- package/dist/host/manifest.json +38 -32
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/host/tests/config.test.ts +996 -39
- package/dist/host/tests/policy.test.ts +11 -0
- package/dist/host/tests/setupPolicy.ts +396 -22
- package/dist/src/core/index.cjs +1289 -164
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +313 -35
- package/dist/src/core/index.d.ts +313 -35
- package/dist/src/core/index.js +1268 -165
- package/dist/src/core/index.js.map +1 -1
- package/package.json +7 -6
package/dist/host/AGENTS.md
CHANGED
|
@@ -16,14 +16,16 @@
|
|
|
16
16
|
## Project model
|
|
17
17
|
|
|
18
18
|
```text
|
|
19
|
-
src/ published library: core, browser, server, optional styles
|
|
20
|
-
app/ application: core, browser, server
|
|
19
|
+
src/ published library: core, browser, server, optional styles, extension faces
|
|
20
|
+
app/ application: core, browser, server, extension faces
|
|
21
21
|
tests/ mirrors source; setup*.ts owns shared test infrastructure
|
|
22
22
|
configs/ thin target wrappers around root Vite/TypeScript configuration
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
- `core` is host-independent. Browser and server may import core; core imports neither; browser and server never import each other.
|
|
26
26
|
- `app/core` is host-independent. `app/server` may import `app/core`, `src/core`, and `src/server`, never browser code. `app/browser` may import `app/core`, `src/core`, and `src/browser`, and reaches server behavior through shared contracts and transports only.
|
|
27
|
+
- The browser surface is `src/browser`, `app/browser`, the journey, and the showcase; the styles surface is `src/styles` and its themes. An extension adds a face to a surface: the `vue` browser extension adds `src/vue` and `app/vue`, and a named styles extension adds `src/<name>`.
|
|
28
|
+
- `src/vue` and each `src/<name>` may import `src/core` and `src/browser`. `app/vue` may import what `app/browser` may import, plus `app/browser` and `src/vue`. No extension face imports server code or another extension's face.
|
|
27
29
|
- Published source never imports private app code.
|
|
28
30
|
- Enforce boundaries with the toolchain (Oxlint import restrictions, scoped TypeScript projects, Vite graphs). Add no second parser for TypeScript, Oxlint, Vue, HTML, CSS, or Vite.
|
|
29
31
|
- `tsconfig.json`, `vite.config.ts`, and each `*/types.ts` are their sources of truth. Keep structural files even when empty.
|
|
@@ -57,7 +59,7 @@ configs/ thin target wrappers around root Vite/TypeScript configuration
|
|
|
57
59
|
- **Named discriminants.** Name the axis (`relationship`, `command`, `category`), never `kind` or `type`.
|
|
58
60
|
- **Centralize by kind.** Types, constants, helpers, validators, parsers, factories, and errors live in their kind file. An implementation file holds one class plus imports.
|
|
59
61
|
- **Export and test reusable logic.** Fold a trivial one-use helper into its caller or export it from its kind file and test it.
|
|
60
|
-
- **No nested functions**, except
|
|
62
|
+
- **No nested functions**, except a callback passed as an argument or returned as the result; `.claude/rules/architecture.md` § Functions and orchestration bounds the literal positions it climbs.
|
|
61
63
|
- **Functional core, imperative shell.** Pure exported leaves; stateful orchestration as class methods. A method never forwards 1:1 to a helper.
|
|
62
64
|
- **No superfluous wrappers.** A wrapper adds a boundary, invariant, composition, translation, lifecycle, or materially narrower contract, or it goes.
|
|
63
65
|
- **Minimal public API.** Create or substantively expand a capability with its first real consumer; this gate applies at creation, never later. Expose an existing reusable capability through its environment barrel regardless of consumer count. Remove a symbol only when the capability itself must not exist. Prefer one minimal interface and one shared engine, with a native backend override only for a faster path.
|
|
@@ -105,6 +105,7 @@ Every working file lives under the checkout's gitignored `tmp/`, in the director
|
|
|
105
105
|
| `tmp/probes/` | runtime probes the `probe` Vitest project collects and the `probe` MCP server arms |
|
|
106
106
|
| `tmp/type/` | the type stage's workspace mirror, owned by `@orkestrel/probe` |
|
|
107
107
|
| `tmp/captures/` | screenshots, resolved-style snapshots, and other capture portfolios |
|
|
108
|
+
| `tmp/browsers/` | the journeys a `browse` server saves, their runs and captures, and its profiles under `.profiles/` |
|
|
108
109
|
| `tmp/worktrees/` | worktrees the Orchestrator creates for a parallel writer |
|
|
109
110
|
|
|
110
111
|
## .orkestrel layout
|
|
@@ -28,8 +28,8 @@ execution and must explain why sibling imports cannot work.
|
|
|
28
28
|
| Defining recursive or compositional spine | Class method, after extracting its pure leaves |
|
|
29
29
|
| Trivial and genuinely one-use | Inline it into the caller |
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
Apply `.claude/rules/architecture.md` § Functions and orchestration to nested functions and
|
|
32
|
+
callback positions.
|
|
33
33
|
|
|
34
34
|
## Hunt the wrapper
|
|
35
35
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: orkestrel-journey
|
|
3
|
-
description: Prove a browser application the way a person uses it — real keystrokes, clicks, and Tab/Enter against only what is visible and reachable — through the journey layer @orkestrel/test/browser publishes, and generate the capture portfolio, the resolved-style matrix, and the statechart outcome from those same journeys. Use when accepting a UI build, proving an application end to end, deciding whether a
|
|
3
|
+
description: Prove a browser application the way a person uses it — real keystrokes, clicks, and Tab/Enter against only what is visible and reachable — through the journey layer @orkestrel/test/browser publishes, and generate the capture portfolio, the resolved-style matrix, and the statechart outcome from those same journeys. Use when accepting a UI build, proving an application end to end, deciding whether a screen is reachable by keyboard alone, proving what a screen refuses as well as what it does, proving the styles a browser actually resolved under each theme and viewport, driving a transition table through the interface and watching it run, auditing whether the interface speaks the user's vocabulary rather than the engine's, producing the screenshots a design review judges, routing a rendered question to an artifact a model can read, or whenever the only evidence a screen works is a test that drove it through JavaScript instead of through the interface.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Prove an application through human journeys
|
|
@@ -14,7 +14,8 @@ description: Prove a browser application the way a person uses it — real keyst
|
|
|
14
14
|
3. [styles.md](references/styles.md) before asserting anything the browser resolved.
|
|
15
15
|
4. [statechart.md](references/statechart.md) before declaring a transition or mounting the harness.
|
|
16
16
|
5. [decide.md](references/decide.md) before routing a question to an instrument.
|
|
17
|
-
6.
|
|
17
|
+
6. [recorded.md](references/recorded.md) before judging a browser recording's run as evidence.
|
|
18
|
+
7. The `*/types.ts` of every environment the journeys drive, plus the application's root component,
|
|
18
19
|
route entry, and store contract.
|
|
19
20
|
|
|
20
21
|
Treat a retained readiness verdict as evidence to re-verify against the current tip, never as a plan
|
|
@@ -23,25 +24,27 @@ each ruling was taken at.
|
|
|
23
24
|
|
|
24
25
|
## Declare the families
|
|
25
26
|
|
|
26
|
-
Declare in the browser environment's `integration.test.ts` which families that
|
|
27
|
+
Declare in the browser environment's `integration.test.ts` which families that screen carries, and
|
|
27
28
|
assert in the always-on proofs that every declared family is present. A declaration names which
|
|
28
|
-
families a
|
|
29
|
+
families a screen owes. It never switches what a declared family proves.
|
|
29
30
|
|
|
30
31
|
| Family | Declared | Proves |
|
|
31
32
|
| ---------- | -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
32
33
|
| Journey | Always | Each user intent reaches its outcome through the interface |
|
|
33
|
-
| Refusal | Always | Each control the
|
|
34
|
-
| Matrix | Where the
|
|
34
|
+
| Refusal | Always | Each control the screen withholds, through one exact failure voice |
|
|
35
|
+
| Matrix | Where the journey wrapper declares more than one variant | The values the browser resolved under each declared variant |
|
|
35
36
|
| Statechart | Where a journey drives a transition of an entity carrying its own state and event vocabulary | Each declared transition, driven through the interface where it can be |
|
|
36
|
-
| Transport | Where the
|
|
37
|
+
| Transport | Where the screen persists or restarts | Persistence, restart, and storage failure through real implementations |
|
|
37
38
|
| Capture | Under the capture flag | The registry times the variants, each registered file written to disk |
|
|
38
39
|
|
|
39
|
-
-
|
|
40
|
+
- Prove Matrix in the arrival journey `scaffold new` seeds while its wrapper declares more than one
|
|
41
|
+
variant: read one resolved value under each declared variant.
|
|
42
|
+
- Refuse a declaration that omits a family whose trigger the screen meets. Report the omission as a
|
|
40
43
|
scope finding and stop; never prove the remaining families around it.
|
|
41
44
|
- Assert the declaration itself: a family listed with no proof, and a proof belonging to no listed
|
|
42
45
|
family, each fail the run.
|
|
43
46
|
- Bind the journey laws to every declared family, not to the journey family alone. A matrix reading
|
|
44
|
-
and a transport assertion reach their
|
|
47
|
+
and a transport assertion reach their screen through the same verbs a journey reaches it through.
|
|
45
48
|
- Change route and theme through the interface in the matrix family and the transport family. A
|
|
46
49
|
family that navigates by calling the application's own router proves the router, and says nothing
|
|
47
50
|
about the screen it reads afterwards.
|
|
@@ -92,9 +95,10 @@ start, and let it choose the capture destination, the matrix row, and the statec
|
|
|
92
95
|
- Run the axis with `npm run test:journey`, which runs that wrapper and joins the `test` chain. Set
|
|
93
96
|
`CAPTURE` to `1` in your own shell and run `npm run test:journey` again to write the frames; the
|
|
94
97
|
root configuration reads that variable and provides it as `capture`.
|
|
95
|
-
- Keep the journeys in `tests/app/browser/integration.test.ts
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
- Keep the journeys in `tests/app/browser/integration.test.ts`, and the journeys of the `vue`
|
|
99
|
+
extension's application in `tests/app/vue/integration.test.ts`, which `npm run test:journey:vue`
|
|
100
|
+
runs. Each variant project of a mode collects that file alone, and the application's ordinary
|
|
101
|
+
project excludes it while the axis is on, so a journey written anywhere else runs in no variant.
|
|
98
102
|
- Loop every declared variant inside one run for the matrix family
|
|
99
103
|
([styles.md](references/styles.md) → Run per variant).
|
|
100
104
|
- Render exactly one variant per run for the capture family
|
|
@@ -120,7 +124,7 @@ whose text names `vitest`, so a script naming another runner's configuration rai
|
|
|
120
124
|
to `createPortfolio` when capture needs no additional synchronous document change. Never attach
|
|
121
125
|
an asynchronous action to `CaptureVariant.apply` or re-resolve a covered theme control at capture
|
|
122
126
|
time.
|
|
123
|
-
- Where the
|
|
127
|
+
- Where the screen has no theme control, prepare the theme through the attribute the screen
|
|
124
128
|
reads. Use the optional `CaptureVariant.apply` hook only for a synchronous document change,
|
|
125
129
|
such as setting that attribute. Treat its `() => void` contract as synchronous: `createPortfolio`
|
|
126
130
|
invokes the hook without awaiting a returned promise.
|
|
@@ -194,15 +198,15 @@ await PORTFOLIO.place('home')
|
|
|
194
198
|
5. **Generate the portfolio from the acceptance journeys.** Place each registered state inside the
|
|
195
199
|
journey that reaches it, and never register a state no journey reaches.
|
|
196
200
|
6. **Commit a value through an act a person performs:** `pressKeys('{Enter}')` on the focused
|
|
197
|
-
control, a Tab away, or a named button. Report a
|
|
198
|
-
unpredictable event, or only after work the person cannot observe as a
|
|
201
|
+
control, a Tab away, or a named button. Report a screen that commits on a timer, on an
|
|
202
|
+
unpredictable event, or only after work the person cannot observe as a screen finding, and never
|
|
199
203
|
work around it in the layer.
|
|
200
204
|
7. **Type only what a person would.** Journeys carry trusted input; adversarial payloads belong to
|
|
201
205
|
the transport family and the parser suites.
|
|
202
206
|
8. **Perform every interaction step unconditionally.** Never gate a step on whether the control it
|
|
203
207
|
is about to drive exists or is reachable, and never branch a journey on `readRefusal`. Let the
|
|
204
208
|
resolver's failure voice name what the interface withheld. A guarded step passes whether or not
|
|
205
|
-
the control was there, so the run goes green on a
|
|
209
|
+
the control was there, so the run goes green on a screen that removed the control.
|
|
206
210
|
|
|
207
211
|
## Import the journey layer
|
|
208
212
|
|
|
@@ -231,7 +235,7 @@ placement and scope `.claude/rules/tests.md` fixes.
|
|
|
231
235
|
|
|
232
236
|
- Enter through the real entry: mount the shipped root component with a real store and the route a
|
|
233
237
|
person lands on, and let the application load itself.
|
|
234
|
-
- Reach each
|
|
238
|
+
- Reach each screen's own controls through forward Tab traversal in at least one journey.
|
|
235
239
|
- Type keystroke by keystroke where the keystrokes are the subject; fill in one operation where the
|
|
236
240
|
text is only a payload the person pastes.
|
|
237
241
|
- Poll every fact the application produces asynchronously until it converges. Never assert one from
|
|
@@ -243,38 +247,38 @@ placement and scope `.claude/rules/tests.md` fixes.
|
|
|
243
247
|
sentence and carry the replaced one in `absent`, so the reading that resolves carries one and not
|
|
244
248
|
the other ([layer.md](references/layer.md) → The waits).
|
|
245
249
|
- Assert the state a control announces beside every drive that sets it, and on an unselected
|
|
246
|
-
sibling. A control announcing state owes this assertion whether or not the
|
|
250
|
+
sibling. A control announcing state owes this assertion whether or not the screen carries the
|
|
247
251
|
statechart family.
|
|
248
252
|
- After a confirmed destructive action, assert through trusted input that focus landed on a visible,
|
|
249
253
|
announced location.
|
|
250
254
|
- Assert the whole page's perception never matches the vocabulary the product does not speak —
|
|
251
255
|
engine, schema, and implementation words the interface is supposed to translate.
|
|
252
|
-
- Report a bare accessible name that answers for more than one reachable element on one screen as a
|
|
253
|
-
finding, and target through role or region until the
|
|
256
|
+
- Report a bare accessible name that answers for more than one reachable element on one screen as a screen
|
|
257
|
+
finding, and target through role or region until the screen is fixed.
|
|
254
258
|
|
|
255
|
-
### The intents every
|
|
259
|
+
### The intents every screen owes
|
|
256
260
|
|
|
257
|
-
Write a journey for each of the following wherever the
|
|
261
|
+
Write a journey for each of the following wherever the screen has that state. Take the expected
|
|
258
262
|
outcome from the product guide; this skill supplies the mechanism and invents no copy, no redirect,
|
|
259
263
|
and no title scheme.
|
|
260
264
|
|
|
261
|
-
| Intent | The journey proves
|
|
262
|
-
| ----------------- |
|
|
263
|
-
| Arrival | The entry route renders its own screen, read through a named region
|
|
264
|
-
| An unknown route | What the application does with a route it does not carry, and what it says
|
|
265
|
-
| An empty result | What a query matching nothing renders, in the product's own words
|
|
266
|
-
| The document name | The title each screen publishes, asserted per screen
|
|
267
|
-
| A render failure | What a person reads when a component throws, rather than an unexplained
|
|
265
|
+
| Intent | The journey proves |
|
|
266
|
+
| ----------------- | ------------------------------------------------------------------------------ |
|
|
267
|
+
| Arrival | The entry route renders its own screen, read through a named region |
|
|
268
|
+
| An unknown route | What the application does with a route it does not carry, and what it says |
|
|
269
|
+
| An empty result | What a query matching nothing renders, in the product's own words |
|
|
270
|
+
| The document name | The title each screen publishes, asserted per screen |
|
|
271
|
+
| A render failure | What a person reads when a component throws, rather than an unexplained screen |
|
|
268
272
|
|
|
269
273
|
- Report a missing outcome as a product finding, with its evidence site, rather than inventing the
|
|
270
|
-
copy the
|
|
274
|
+
copy the screen owes.
|
|
271
275
|
- Assert the title from `document.title` per screen, against the title the product guide names for
|
|
272
276
|
that screen. Report a screen the guide gives no title as a product finding.
|
|
273
277
|
|
|
274
278
|
## Prove the refusals
|
|
275
279
|
|
|
276
|
-
- Give every
|
|
277
|
-
journey has put the
|
|
280
|
+
- Give every screen a refusal family: the controls a person must not reach in the state the
|
|
281
|
+
journey has put the screen in.
|
|
278
282
|
- Assert the exact failure voice the case means. Never write an assertion that accepts more than
|
|
279
283
|
one voice ([layer.md](references/layer.md) → The failure voices).
|
|
280
284
|
- Cover the restrictions the interface imposes on itself: a collapsed panel's field, a verb
|
|
@@ -296,11 +300,11 @@ and no title scheme.
|
|
|
296
300
|
`QuotaExceededError`.
|
|
297
301
|
- Prove the visible half in a journey: the failure sentence a person reads, and the retry control
|
|
298
302
|
that clears it. A storage failure whose visible half is a control that silently does nothing is a
|
|
299
|
-
|
|
303
|
+
screen finding.
|
|
300
304
|
- Assert restart by starting a second session over the same store and polling the restored value.
|
|
301
305
|
- Take a stalled read to the application's own asynchronous store contract, never to `Storage`.
|
|
302
306
|
`Storage` is synchronous, so a hanging read is not expressible against it and a fixture that fakes
|
|
303
|
-
one is proving a
|
|
307
|
+
one is proving a store contract the application does not have.
|
|
304
308
|
|
|
305
309
|
## Prove the styles
|
|
306
310
|
|
|
@@ -350,16 +354,16 @@ Completion requires all of:
|
|
|
350
354
|
|
|
351
355
|
- every in-scope user intent reaching its outcome through the interface, with no step that reaches
|
|
352
356
|
past it;
|
|
353
|
-
- the intents every
|
|
357
|
+
- the intents every screen owes present wherever the screen has the state, each outcome taken from
|
|
354
358
|
the product guide and every missing one reported as a product finding;
|
|
355
|
-
- keyboard-only reachability proven on every
|
|
356
|
-
- a refusal family per
|
|
359
|
+
- keyboard-only reachability proven on every screen the journeys cover;
|
|
360
|
+
- a refusal family per screen, each asserting one exact failure voice;
|
|
357
361
|
- the transport family declared separately, driven through real implementations, and convergent;
|
|
358
362
|
- the declared families each proven, and the declaration itself asserted;
|
|
359
363
|
- the matrix family read once per declared variant, each style reading carrying its published
|
|
360
364
|
control from [styles.md](references/styles.md) → The published controls in the same run, and the
|
|
361
365
|
contrast reading's control straddling its declared bar;
|
|
362
|
-
- the authored-class census and the `extractStyles` reading taken on the mounted
|
|
366
|
+
- the authored-class census and the `extractStyles` reading taken on the mounted screen, each
|
|
363
367
|
reporting the population it walked;
|
|
364
368
|
- the statechart table driven to a terminal status with no failed row, and the harness tally read
|
|
365
369
|
from the object and from its markup;
|
|
@@ -378,5 +382,5 @@ block is the home of that limit and of the condition that reopens it. Cite that
|
|
|
378
382
|
neither into a verdict.
|
|
379
383
|
|
|
380
384
|
Report each journey by the intent it proves, the refusals it establishes, the states it placed, the
|
|
381
|
-
variants it read, the statechart outcome it reached, and every
|
|
385
|
+
variants it read, the statechart outcome it reached, and every screen finding the layer's refusals
|
|
382
386
|
exposed.
|
|
@@ -51,12 +51,12 @@ The package already refuses these, so assert none of them again:
|
|
|
51
51
|
|
|
52
52
|
Declare the state names once in the journey file, and build the portfolio from that declaration.
|
|
53
53
|
|
|
54
|
-
- Name a state for its
|
|
54
|
+
- Name a state for its screen and its condition — `answer-partial`, `start-storage-failure`,
|
|
55
55
|
`case-delete-confirmation`.
|
|
56
56
|
- Register the states the design work actually needs, and place every registered one. Never leave a
|
|
57
57
|
registered state unplaced.
|
|
58
58
|
- Place a capture state from inside the journey that reaches it, immediately after the assertion
|
|
59
|
-
that proves the
|
|
59
|
+
that proves the screen is in the condition that state names.
|
|
60
60
|
- Record each placed name in the suite's own set in the same step that calls `place`. That set, not
|
|
61
61
|
`placements`, is what the always-on placement proof reads.
|
|
62
62
|
|
|
@@ -117,7 +117,7 @@ after the click returns.
|
|
|
117
117
|
- Take each frame after the paint settles. Reach for `waitForAnimations` on the element whose
|
|
118
118
|
transition was running ([layer.md](layer.md) → The waits); a frame shot mid-transition pictures a
|
|
119
119
|
state the interface never rests in.
|
|
120
|
-
- Regenerate the whole matrix from the journeys after any
|
|
120
|
+
- Regenerate the whole matrix from the journeys after any screen change. Never judge a round
|
|
121
121
|
against a portfolio that is part old and part new.
|
|
122
122
|
- Route review of the portfolio to the `orkestrel-polish` campaign, which owns preflight,
|
|
123
123
|
verdicts, and reconciliation. This reference owns only how the journeys generate it.
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
Route a question by what judges the claim, before spending a round on it. Report a question no
|
|
4
4
|
instrument here answers as open, and never answer it with the nearest instrument instead.
|
|
5
5
|
|
|
6
|
-
| The claim is judged by
|
|
7
|
-
|
|
|
8
|
-
| A compiler, a linter, or a Node runner
|
|
9
|
-
| A person's eye
|
|
10
|
-
| A person watching a widget move
|
|
11
|
-
| The browser's own resolved value
|
|
6
|
+
| The claim is judged by | Route it to |
|
|
7
|
+
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
8
|
+
| A compiler, a linter, or a Node runner | The `prove` tool, with its negative control |
|
|
9
|
+
| A person's eye | The run's written artifact, named by variant |
|
|
10
|
+
| A person watching a widget move | The harness run's frames and its artifact |
|
|
11
|
+
| The browser's own resolved value | The matrix family ([styles.md](styles.md)) |
|
|
12
|
+
| A flow a model drove through the browser toolset | A recorded journey replayed through `@orkestrel/browser`, judged by the run's steps ([recorded.md](recorded.md)), never by the transcript |
|
|
12
13
|
|
|
13
14
|
Never ask `prove` about pixels, and never ask a screenshot about types.
|
|
14
15
|
|
|
@@ -53,8 +54,8 @@ so a decision cites the exact file and reads it in one call.
|
|
|
53
54
|
|
|
54
55
|
Compose each file from what the run already holds:
|
|
55
56
|
|
|
56
|
-
- `describeTree` of the mounted
|
|
57
|
-
- `describeFocus` of the mounted
|
|
57
|
+
- `describeTree` of the mounted screen, for the roles, names, and states a person meets.
|
|
58
|
+
- `describeFocus` of the mounted screen, for the focus order the keyboard walk took.
|
|
58
59
|
- The resolved-style rows the matrix family read for that variant — the property, the element, and
|
|
59
60
|
the value the browser returned.
|
|
60
61
|
- The journal's `steps` and `output`, for what the run did and what the page said while it did it.
|
|
@@ -66,7 +67,7 @@ Rules the artifact obeys:
|
|
|
66
67
|
combination nobody can reproduce.
|
|
67
68
|
- Write one file per variant, never one file per journey. A decision is taken per variant, and a
|
|
68
69
|
reader opening one file per journey pays a round trip per journey.
|
|
69
|
-
- Regenerate the whole set after any
|
|
70
|
+
- Regenerate the whole set after any screen change. Never judge a round against a set that is part
|
|
70
71
|
old and part new.
|
|
71
72
|
- Keep it out of version control.
|
|
72
73
|
|
|
@@ -185,13 +185,13 @@ the capture voices in [captures.md](captures.md).
|
|
|
185
185
|
|
|
186
186
|
- Report an absent control and a present-but-unreachable one as different findings: absence names
|
|
187
187
|
a missing control, and unreachability names the interface gating one that exists.
|
|
188
|
-
- Report ambiguity as a finding about the
|
|
188
|
+
- Report ambiguity as a finding about the screen. Quote the match count from the message, and
|
|
189
189
|
re-target the journey by role or region.
|
|
190
190
|
- Assert a `DOMException` on its `name` as well as its message. A withheld storage operation raises
|
|
191
191
|
`SecurityError` and a write past the quota raises `QuotaExceededError`, which is the pair a denied
|
|
192
192
|
or full origin raises.
|
|
193
193
|
- Never assert a `could not be resolved` sentence. Each one is narrowing the resolver needs under
|
|
194
|
-
`noUncheckedIndexedAccess`, and no
|
|
194
|
+
`noUncheckedIndexedAccess`, and no screen reaches it.
|
|
195
195
|
- Read a refused bound as a test defect rather than a finding. The wait family validates its budget
|
|
196
196
|
and its interval and names the argument the caller passed, under the subject `Wait` or
|
|
197
197
|
`Animation`.
|
|
@@ -211,7 +211,7 @@ the capture voices in [captures.md](captures.md).
|
|
|
211
211
|
`fillAccessible` where the text is only a payload the person pastes.
|
|
212
212
|
- Bring focus about first — through `traverseAccessible`, `clickAccessible`, or `typeAccessible` —
|
|
213
213
|
and then press. The refusal is what `pressKeys` adds over the provider's own keyboard call: a key
|
|
214
|
-
sent to the body reaches no control, and every assertion after it reads a
|
|
214
|
+
sent to the body reaches no control, and every assertion after it reads a screen the key never
|
|
215
215
|
touched.
|
|
216
216
|
- Escape the sequence yourself where the sequence is the subject. Reach for `typeAccessible` where
|
|
217
217
|
the text is the subject and the key syntax is in the way.
|
|
@@ -245,7 +245,7 @@ the capture voices in [captures.md](captures.md).
|
|
|
245
245
|
- Pass `absent: true` to `waitForState` where the claim is that the state went away, and read the
|
|
246
246
|
returned states rather than taking a second reading afterwards.
|
|
247
247
|
- Take `waitForState` as the published replacement for a settle keyed to a framework's own class
|
|
248
|
-
names. Where the control announces nothing, the finding is the
|
|
248
|
+
names. Where the control announces nothing, the finding is the screen's: give it `aria-expanded`,
|
|
249
249
|
`aria-pressed`, or `aria-busy` rather than reading the classes a stylesheet happens to use.
|
|
250
250
|
- Take every style, contrast, and capture reading after `waitForAnimations` on the element whose
|
|
251
251
|
paint was moving. A reading taken mid-transition reports an interpolated frame no state of the
|
|
@@ -274,7 +274,7 @@ the capture voices in [captures.md](captures.md).
|
|
|
274
274
|
locators do not, because the platform exposes a native disclosure rather than a role.
|
|
275
275
|
- Drive an ARIA disclosure as what it is: a button. Click it with `clickAccessible`, then settle it
|
|
276
276
|
with `waitForState` on the state it announces.
|
|
277
|
-
- Report a control that opens a panel and announces no state as a
|
|
277
|
+
- Report a control that opens a panel and announces no state as a screen finding. Author
|
|
278
278
|
`aria-expanded` on it rather than settling on the classes its framework toggles.
|
|
279
279
|
- Read a native summary's expansion with `readStates`, which reads the parent `details` element's
|
|
280
280
|
own `open` where the summary declares no `aria-expanded`.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Recorded browser journeys
|
|
2
|
+
|
|
3
|
+
Judge a journey a model recorded through `@orkestrel/browser` by the steps its replay ran, never by
|
|
4
|
+
the transcript of the session that recorded it. Translate those steps into the journey layer before
|
|
5
|
+
a run of them counts toward a family this skill declares.
|
|
6
|
+
|
|
7
|
+
## The vocabulary
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { waitForText } from '@orkestrel/test'
|
|
11
|
+
import {
|
|
12
|
+
clickAccessible,
|
|
13
|
+
fillAccessible,
|
|
14
|
+
pressKeys,
|
|
15
|
+
readPage,
|
|
16
|
+
readPerception,
|
|
17
|
+
} from '@orkestrel/test/browser'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## What a recording holds
|
|
21
|
+
|
|
22
|
+
- Read a recorded journey as one user intent kept as JSON: a `name`, a one-sentence `description`,
|
|
23
|
+
the `parameters` it declares, and `steps` that mirror the browser toolset's own tool calls.
|
|
24
|
+
- Take an acting step's target from its role and exact accessible name. Read its record-time
|
|
25
|
+
`reference` and its `css` selector only as evidence for a refused resolution; a replay reads
|
|
26
|
+
neither, and a translated step carries neither.
|
|
27
|
+
- Name each journey tool for its act: `record` starts a recording, `save` keeps it under its name,
|
|
28
|
+
`journeys` reads the listing, `edit` changes steps by id, `replay` runs, and `forget` discards the
|
|
29
|
+
journey with its runs.
|
|
30
|
+
- Find a `browse` server's files under `tmp/browsers/`: the journey at `NAME/journey.json`, each
|
|
31
|
+
run at `NAME/runs/ID/run.json` beside that run's step captures, and the server's browser profile
|
|
32
|
+
under `.profiles/`. `NAME` is the journey's name and `ID` is the run id the store minted.
|
|
33
|
+
- Register a `browse` server per `.claude/rules/quality.md` § Instruments.
|
|
34
|
+
|
|
35
|
+
## Translate a step
|
|
36
|
+
|
|
37
|
+
Translate each step through the following table. [layer.md](layer.md) owns each verb's contract.
|
|
38
|
+
|
|
39
|
+
| Recorded step | Journey layer |
|
|
40
|
+
| --------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
41
|
+
| `click` | `clickAccessible(role, name)` with the step's role and name |
|
|
42
|
+
| `type` | `fillAccessible(name, text)`, refused for a `combobox` or a `listbox` target |
|
|
43
|
+
| `press` | `pressKeys(keys)`, with the chord translated |
|
|
44
|
+
| `wait` | `waitForText(description, read, text)` |
|
|
45
|
+
| `navigate`, `switch`, `dialog`, a page tool, `unresolved` | Refused |
|
|
46
|
+
|
|
47
|
+
- Refuse a `type` step whose target is a `combobox` or a `listbox`. The toolset selected an option
|
|
48
|
+
there, and `fillAccessible` replaces a field's text.
|
|
49
|
+
- When a `type` step carries `submit`, follow the fill with `pressKeys('{Enter}')` on the same field.
|
|
50
|
+
- Translate a `press` chord into the provider's key syntax: hold each modifier with `{Name>}`, send
|
|
51
|
+
the terminal key, and release the modifiers with `{/Name}` in reverse order. `Enter` becomes
|
|
52
|
+
`{Enter}`, and `Control+Shift+P` becomes `{Control>}{Shift>}P{/Shift}{/Control}`.
|
|
53
|
+
- Scope the reader a `wait` step passes to `waitForText` per [layer.md](layer.md) § The waits:
|
|
54
|
+
`readPerception` over the region the sentence lands in, and `readPage` only where the claim is
|
|
55
|
+
about the whole page.
|
|
56
|
+
- Refuse `navigate`: reach the page through the visible link or control that navigates, per
|
|
57
|
+
[layer.md](layer.md) § The named bans. Refuse `switch`, `dialog`, a page tool, and an `unresolved`
|
|
58
|
+
step, and report each as a step the layer has no verb for.
|
|
59
|
+
|
|
60
|
+
## Judge a run
|
|
61
|
+
|
|
62
|
+
- Feed a run's `steps` and `output` into the variant artifact ([decide.md](decide.md) § The
|
|
63
|
+
rendered artifact) as automation evidence, labelled with the journey name and the run id.
|
|
64
|
+
- Discharge none of this skill's laws with a run until the workspace holds a trusted-input adapter
|
|
65
|
+
that drives the steps through the published verbs. A family stays open until a journey test
|
|
66
|
+
drives the translated steps.
|
|
67
|
+
- Read a run whose `outcome` is `stopped` or `aborted` as the step it stopped at, and never as the
|
|
68
|
+
journey's outcome.
|
|
69
|
+
|
|
70
|
+
## Review a recording
|
|
71
|
+
|
|
72
|
+
Review a model's recording before a replay or a translation. The recorder keeps every action that
|
|
73
|
+
completed, so the recording carries each detour and each repeated submission the model made.
|
|
74
|
+
|
|
75
|
+
1. Read the listing `journeys` returns for the journey.
|
|
76
|
+
2. Remove each detour and each repeated step through `edit` with the `remove` operation, by step id.
|
|
77
|
+
3. Discard a recording that does not carry the intent through `forget`, and record it again.
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
`assert`. Each phase receives the context and the part of the transition it owns.
|
|
37
37
|
- Put the scenarios and the table in the workspace's browser test setup module, and import them from
|
|
38
38
|
there.
|
|
39
|
-
- Declare a transition for every event the
|
|
39
|
+
- Declare a transition for every event the screen accepts in every state it accepts it, including
|
|
40
40
|
the event that leaves the state unchanged. A table that lists only the moves the happy path takes
|
|
41
41
|
proves the happy path.
|
|
42
42
|
- Write the phases as module functions the whole table shares. Each phase reads its subject from its
|
|
@@ -184,7 +184,7 @@ it('walks the disclosure statechart', async () => {
|
|
|
184
184
|
- Drive `act` through the journey verbs — `clickAccessible`, `clickDisclosure`, `typeAccessible`,
|
|
185
185
|
`traverseAccessible`, `pressKeys` — for every transition a person can cause.
|
|
186
186
|
- Drive `act` through the entity's own API only where the transition is the entity's rather than the
|
|
187
|
-
person's: a lifecycle event, a transport reply, a timer the
|
|
187
|
+
person's: a lifecycle event, a transport reply, a timer the screen owns.
|
|
188
188
|
- Say which door each row used, in the row's `name`. A table that mixes the doors silently reads as
|
|
189
189
|
a set of user transitions and proves something else.
|
|
190
190
|
|
|
@@ -286,7 +286,7 @@ import it.
|
|
|
286
286
|
|
|
287
287
|
- Name only the attribute contract such a page must honour: the names in `STATECHART_ATTRIBUTES`,
|
|
288
288
|
the readings in `STATECHART_STATUSES`, and the tally on one root node.
|
|
289
|
-
- Report the page itself as the repository owner's decision — which transitions a
|
|
289
|
+
- Report the page itself as the repository owner's decision — which transitions a screen owes,
|
|
290
290
|
where the page is linked, and whether it ships at all.
|
|
291
291
|
- Route a person who must watch the widget move to the harness run's own frames and its written
|
|
292
292
|
artifact ([decide.md](decide.md) → The harness run), and name a deep link only where the workspace
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Proving what the browser resolved
|
|
2
2
|
|
|
3
|
-
Prove a style from what the browser resolved on the mounted
|
|
3
|
+
Prove a style from what the browser resolved on the mounted screen. The `enterprise-bootstrap`
|
|
4
4
|
skill's [instruments reference](../../enterprise-bootstrap/references/inspection.md) names each
|
|
5
5
|
instrument's property, its population, and its coverage. Take those from there, the reading from
|
|
6
6
|
here, and the control from the builder this layer publishes for it.
|
|
@@ -75,12 +75,12 @@ readings follow.
|
|
|
75
75
|
reads the whole matrix, where the capture family renders one variant per run.
|
|
76
76
|
- Compose each variant's `apply` in the test, and apply it with the variant's `width` and `height`
|
|
77
77
|
before the readings. Take every reading for that variant before moving to the next.
|
|
78
|
-
- Name the attribute the
|
|
78
|
+
- Name the attribute the screen actually reads in `apply`; a Bootstrap screen switches on
|
|
79
79
|
`data-bs-theme`. An `apply` that sets another attribute leaves the run in the default theme, where
|
|
80
80
|
every reading passes.
|
|
81
|
-
- Reach for the application's own theme control where the
|
|
81
|
+
- Reach for the application's own theme control where the screen ships one, and assert the state it
|
|
82
82
|
announces. Setting the attribute directly proves the stylesheet; driving the control proves the
|
|
83
|
-
|
|
83
|
+
screen.
|
|
84
84
|
- Assert that the run read every declared variant. A matrix that silently walked one variant reports
|
|
85
85
|
a pass for the theme nobody exercised.
|
|
86
86
|
- Report which variants a result covers beside it. A pairing that appears only in a state the run
|
|
@@ -102,7 +102,7 @@ provided.
|
|
|
102
102
|
`pressKeys`, or a real click. Pass `worn` where the chrome is painted onto a second element such
|
|
103
103
|
as a label. It reports `undefined` for a control not matching `:focus-visible`, for the browser's
|
|
104
104
|
own automatic ring, and for a focus style that only repaints the fill — treat each as a finding
|
|
105
|
-
about the
|
|
105
|
+
about the screen rather than as a pass.
|
|
106
106
|
- Reach for `measureContrast`, `measureLuminance`, `blendColor`, `readLayers`, and `readBackdrop`
|
|
107
107
|
only where the composite itself is the subject. Never re-derive `readContrast` from them.
|
|
108
108
|
|
|
@@ -117,7 +117,7 @@ Take each reading's control from the builder this layer publishes for it.
|
|
|
117
117
|
| `extractStyles` | `buildEscapes(permitted)` | An inline declaration, an embedded `<style>` element, and the sheet the id exempts |
|
|
118
118
|
| `readCensus` | `buildCensus()` | An HTML token and an SVG token, neither declared by any loaded stylesheet |
|
|
119
119
|
|
|
120
|
-
- Append each control's `root` to the same
|
|
120
|
+
- Append each control's `root` to the same screen root the reading walks, take the reading, and
|
|
121
121
|
remove it afterwards. Every builder returns detached nodes and mounts nothing, so where the
|
|
122
122
|
control is read is the caller's decision.
|
|
123
123
|
- Assert on the fields the builder returns rather than on a token or a selector written down in the
|
|
@@ -134,12 +134,12 @@ Take each reading's control from the builder this layer publishes for it.
|
|
|
134
134
|
Take the property, the population, and the coverage from the instruments reference → Authored class
|
|
135
135
|
in the shipped cascade. This is the reading.
|
|
136
136
|
|
|
137
|
-
- Read the census with `readCensus(root)`, which walks the mounted
|
|
137
|
+
- Read the census with `readCensus(root)`, which walks the mounted screen, reports `elements` as
|
|
138
138
|
the population it read, lists every `tokens` value the markup carries, and lists as `undeclared`
|
|
139
139
|
the tokens no loaded stylesheet declares. It refuses a walk that read no element.
|
|
140
140
|
- Assert on `elements` as well as on `undeclared`. An empty walk reports no undeclared token, and so
|
|
141
141
|
does markup whose every class the cascade declares.
|
|
142
|
-
- Take `root` from the mounted
|
|
142
|
+
- Take `root` from the mounted screen, so the census covers what rendered rather than what a
|
|
143
143
|
template file spells.
|
|
144
144
|
- Read `readClasses` and `readCascade` directly only where one side of the difference is the
|
|
145
145
|
subject. `readCensus` is the reading, and re-deriving it drops the population it reports.
|
|
@@ -152,7 +152,7 @@ Take the property, the population, the named exemptions, and the coverage from t
|
|
|
152
152
|
reference → Style escapes. This is the reading.
|
|
153
153
|
|
|
154
154
|
- Read escapes with `extractStyles(root)`, which returns the markup of every hit it found.
|
|
155
|
-
- Take the reading before any journey drives the
|
|
155
|
+
- Take the reading before any journey drives the screen, because the population is the undriven
|
|
156
156
|
tree.
|
|
157
157
|
- Append `buildEscapes(permitted).root` to that same `root`, so the control reaches the reading
|
|
158
158
|
through `extractStyles`.
|
|
@@ -57,6 +57,10 @@ online audit reports the floor-restored files as stale until the release. The `-
|
|
|
57
57
|
skips the catalog step and exits `1` with a note naming that refusal, so run the full
|
|
58
58
|
`scaffold overwrite` after the release.
|
|
59
59
|
|
|
60
|
+
When the target is the `@orkestrel/scaffold` package, expect the runner to record the overwrite step
|
|
61
|
+
as skipped and to read the verify audit's foreign-path findings as expected; never run the `scaffold repair` or `scaffold overwrite` command
|
|
62
|
+
against that checkout.
|
|
63
|
+
|
|
60
64
|
Run visits in parallel slices of disjoint repositories, each slice strictly serial inside itself,
|
|
61
65
|
reporting per target. Refuse a failed target, name it, repair it, and re-run it alone.
|
|
62
66
|
|
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
// re-pin every @orkestrel range to the registry caret and install), commit (`git commit --only` of
|
|
7
7
|
// the manifest and lockfile as the preparation commit), overwrite (`scaffold overwrite --json`, then
|
|
8
8
|
// `scaffold audit` exiting 0; offline, an exit of 1 whose note names the catalog refusal is
|
|
9
|
-
// accepted
|
|
10
|
-
//
|
|
9
|
+
// accepted; on @orkestrel/scaffold itself both are skipped with a note, because the vendored host
|
|
10
|
+
// is staged from that checkout), verify (`scaffold audit --json` prints the releases envelope and
|
|
11
|
+
// every declared range matches the registry; on @orkestrel/scaffold itself an audit exit of 1 is
|
|
12
|
+
// read as the expected foreign-path findings of its own canonical sources), install,
|
|
13
|
+
// pins (the self-pin sweep with --prior or the manifest version and every range the visit moved; hits are reported, not
|
|
11
14
|
// fatal), format, gates (format:check, lint:check, check, build, test), and compare (the rebuilt
|
|
12
15
|
// dist against the published tarball, and the final runtime dependency set against the published
|
|
13
16
|
// manifest `npm view NAME --json` serves; both readings are reported, not fatal). The summary
|
|
@@ -46,6 +49,9 @@ const RUNTIME_FIELDS: readonly string[] = ['dependencies', 'peerDependencies']
|
|
|
46
49
|
const RANGE_FIELDS: readonly string[] = [...RUNTIME_FIELDS, 'devDependencies']
|
|
47
50
|
const CATALOG = '.claude/agents/orkestrel.md'
|
|
48
51
|
const OFFLINE_REFUSAL = "'catalog' does not take --offline"
|
|
52
|
+
// The vendored host is staged from this package's checkout, so overwriting it from the host deletes
|
|
53
|
+
// and replaces its own canon.
|
|
54
|
+
const SCAFFOLD = '@orkestrel/scaffold'
|
|
49
55
|
const VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u
|
|
50
56
|
// The checkout runs the `.ts` sources and a built twin under `dist/agents` runs `.js` siblings.
|
|
51
57
|
const EXTENSION = extname(fileURLToPath(import.meta.url))
|
|
@@ -358,6 +364,7 @@ function runVerify(
|
|
|
358
364
|
scaffoldFile: string,
|
|
359
365
|
scaffoldPrefix: readonly string[],
|
|
360
366
|
offline: readonly string[],
|
|
367
|
+
own: boolean,
|
|
361
368
|
): void {
|
|
362
369
|
const audit = runStep(
|
|
363
370
|
runner,
|
|
@@ -367,6 +374,10 @@ function runVerify(
|
|
|
367
374
|
runner.target,
|
|
368
375
|
)
|
|
369
376
|
if (audit === undefined) return
|
|
377
|
+
// The audit reads the canonical sources of scaffold's own checkout as foreign paths and exits 1.
|
|
378
|
+
if (own && audit.status === 1) {
|
|
379
|
+
replaceLastStep(runner, 0, 'foreign-path findings are expected on the scaffold checkout')
|
|
380
|
+
}
|
|
370
381
|
const releases = readReleases(audit.stdout)
|
|
371
382
|
if (releases === undefined) {
|
|
372
383
|
replaceLastStep(
|
|
@@ -490,10 +501,17 @@ function runVisit(argv: readonly string[]): Visit | undefined {
|
|
|
490
501
|
if (selected.has('pin')) runPin(runner, scaffoldFile, scaffoldPrefix, offline)
|
|
491
502
|
if (selected.has('commit') && !failed(runner)) runCommit(runner)
|
|
492
503
|
if (selected.has('overwrite') && !failed(runner)) {
|
|
493
|
-
|
|
504
|
+
if (name === SCAFFOLD) {
|
|
505
|
+
recordStep(
|
|
506
|
+
runner,
|
|
507
|
+
'overwrite',
|
|
508
|
+
describeCommand(scaffoldFile, [...scaffoldPrefix, 'overwrite', '--json', ...offline]),
|
|
509
|
+
`skipped with its audit: ${SCAFFOLD} is the package whose checkout the vendored host is staged from`,
|
|
510
|
+
)
|
|
511
|
+
} else runOverwrite(runner, scaffoldFile, scaffoldPrefix, offline)
|
|
494
512
|
}
|
|
495
513
|
if (selected.has('verify') && !failed(runner)) {
|
|
496
|
-
runVerify(runner, scaffoldFile, scaffoldPrefix, offline)
|
|
514
|
+
runVerify(runner, scaffoldFile, scaffoldPrefix, offline, name === SCAFFOLD)
|
|
497
515
|
}
|
|
498
516
|
if (selected.has('install') && !failed(runner)) runNpmStep(runner, 'install', ['install'])
|
|
499
517
|
if (selected.has('pins') && !failed(runner)) {
|