@orkestrel/scaffold 0.0.103 → 0.0.104

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +84 -1
  2. package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +21 -6
  3. package/dist/agents/skills/orkestrel-harden/scripts/titles.js +338 -0
  4. package/dist/agents/skills/orkestrel-publish/scripts/swap.js +3 -39
  5. package/dist/agents/skills/orkestrel-publish/scripts/wave.js +433 -47
  6. package/dist/agents/skills/orkestrel-publish/scripts/window.js +2 -1
  7. package/dist/bin/main.js +16 -9
  8. package/dist/bin/main.js.map +1 -1
  9. package/dist/host/agents/orchestration.md +3 -3
  10. package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +2 -2
  11. package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +1 -1
  12. package/dist/host/agents/skills/orkestrel-build/SKILL.md +2 -2
  13. package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +2 -2
  14. package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +1 -1
  15. package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +3 -3
  16. package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +10 -10
  17. package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +1 -0
  18. package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +100 -1
  19. package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +27 -6
  20. package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +4 -4
  21. package/dist/host/agents/skills/orkestrel-falsify/agents/openai.yaml +1 -1
  22. package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +8 -7
  23. package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +6 -6
  24. package/dist/host/agents/skills/orkestrel-harden/SKILL.md +6 -1
  25. package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +1700 -0
  26. package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -2
  27. package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +48 -0
  28. package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +4 -2
  29. package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +425 -0
  30. package/dist/host/agents/skills/orkestrel-journey/SKILL.md +2 -2
  31. package/dist/host/agents/skills/orkestrel-journey/references/captures.md +9 -8
  32. package/dist/host/agents/skills/orkestrel-polish/SKILL.md +3 -3
  33. package/dist/host/agents/skills/orkestrel-publish/SKILL.md +2 -2
  34. package/dist/host/agents/skills/orkestrel-publish/references/release.md +1 -0
  35. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +56 -13
  36. package/dist/host/agents/skills/orkestrel-publish/references/window.md +6 -6
  37. package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +5 -49
  38. package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +646 -49
  39. package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +2 -1
  40. package/dist/host/agents/transports/codex.md +1 -1
  41. package/dist/host/claude/agents/orkestrel.md +54 -53
  42. package/dist/host/claude/agents/reviewer.md +4 -4
  43. package/dist/host/claude/rules/documentation.md +2 -1
  44. package/dist/host/claude/rules/patterns.md +1 -1
  45. package/dist/host/claude/rules/quality.md +2 -2
  46. package/dist/host/claude/rules/styles.md +1 -1
  47. package/dist/host/claude/rules/tests.md +34 -8
  48. package/dist/host/claude/rules/typescript.md +29 -0
  49. package/dist/host/claude/rules/workspace.md +32 -22
  50. package/dist/host/claude/rules/writing.md +34 -26
  51. package/dist/host/claude/settings.json +2 -0
  52. package/dist/host/claude/skills/orkestrel-debrief/SKILL.md +1 -1
  53. package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +1 -1
  54. package/dist/host/codex/agents/analyst.toml +3 -3
  55. package/dist/host/codex/agents/reviewer.toml +4 -4
  56. package/dist/host/configs/policy.ts +13 -2
  57. package/dist/host/dotfiles/editorconfig +0 -1
  58. package/dist/host/dotfiles/gitattributes +0 -1
  59. package/dist/host/dotfiles/gitignore +0 -1
  60. package/dist/host/dotfiles/oxlintrc.json +24 -0
  61. package/dist/host/guides/agent.md +1638 -598
  62. package/dist/host/guides/budget.md +89 -29
  63. package/dist/host/guides/form.md +477 -91
  64. package/dist/host/guides/mcp.md +14 -2
  65. package/dist/host/guides/ollama.md +97 -73
  66. package/dist/host/guides/probe.md +1 -1
  67. package/dist/host/guides/scaffold.md +158 -101
  68. package/dist/host/guides/study.md +2635 -0
  69. package/dist/host/guides/table.md +225 -15
  70. package/dist/host/guides/terminal.md +22 -20
  71. package/dist/host/guides/test.md +689 -255
  72. package/dist/host/manifest.json +87 -63
  73. package/dist/host/tests/config.test.ts +136 -82
  74. package/dist/host/tests/policy.test.ts +39 -1
  75. package/dist/host/tests/setupPolicy.ts +86 -28
  76. package/dist/src/core/index.cjs +1387 -611
  77. package/dist/src/core/index.cjs.map +1 -1
  78. package/dist/src/core/index.d.cts +35 -20
  79. package/dist/src/core/index.d.ts +35 -20
  80. package/dist/src/core/index.js +1385 -612
  81. package/dist/src/core/index.js.map +1 -1
  82. package/dist/src/server/index.cjs +12 -12
  83. package/dist/src/server/index.cjs.map +1 -1
  84. package/dist/src/server/index.d.cts +12 -12
  85. package/dist/src/server/index.d.ts +12 -12
  86. package/dist/src/server/index.js +12 -12
  87. package/dist/src/server/index.js.map +1 -1
  88. package/package.json +4 -4
@@ -137,6 +137,14 @@ Each interface's call-signature members are listed under [Methods](#methods). `R
137
137
  and `Failure` come from `@orkestrel/contract` (mirrored at [`contract.md`](contract.md)) and are not
138
138
  re-exported; `retryUntil` reads `Result` internally.
139
139
 
140
+ The chooser wire contract is shared by the browser journey and its server command.
141
+
142
+ | Type | Kind | Shape | Summary |
143
+ | ---------------- | --------- | ------------------------------------------ | ------------------------------------------------------------------------------------- |
144
+ | `FixtureFile` | interface | `{ name, mime, bytes }` | Represents one fixture file as a name, media type, and immutable bytes. |
145
+ | `ChoiceOptions` | interface | `{ keyboard?, budget? }` | Configures native file-picker activation and its bounded provider wait. |
146
+ | `ChooserRequest` | interface | `ChoiceOptions` plus `{ selector, files }` | Carries a resolved file-input selector and fixture selection to the browser provider. |
147
+
140
148
  #### Constants
141
149
 
142
150
  A `Shape` cell holds the constant's declared type.
@@ -177,36 +185,47 @@ instead of propagating.
177
185
 
178
186
  #### Helpers
179
187
 
180
- | API | Kind | Signature | Summary |
181
- | --------------------- | -------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
182
- | `waitForCondition` | function | `(description, condition, options?) => Promise<void>` | Waits until a condition holds within an elapsed-time budget. |
183
- | `retryUntil` | function | `(description, produce, satisfied, options?) => Promise<T>` | Repeats a producer until one produced value satisfies a predicate. |
184
- | `waitForEvent` | function | `(subscribe, description, options?) => Promise<TArgs>` | Waits for the first delivery from an event subscription. |
185
- | `waitForText` | function | `(description, read, text, options?) => Promise<string>` | Waits until a reading of text carries an expected sentence. |
186
- | `checkBounds` | function | `(subject: string, budget: number, interval: number) => void` | Checks the resolved bounds one bounded wait runs under. |
187
- | `buildRetryExhausted` | function | `(description, budget, elapsed, last, cause) => Error` | Builds the error `retryUntil` raises when its elapsed-time budget runs out. |
188
- | `buildRefusal` | function | `(name: string, cause: unknown) => Error` | Builds the error a refused fixture build raises, named for the row it was building for. |
189
- | `dropRegistration` | function | `(registrations: SignalRegistration[], installed) => SignalRegistration \| undefined` | Drops the registration an instrumented signal installed for one listener. |
190
- | `decodeJSONLines` | function | `(text: string) => readonly unknown[]` | Decodes newline-delimited JSON values. |
191
- | `waitForDelay` | function | `(ms?: number) => Promise<void>` | Waits for a host timer to elapse. |
192
- | `waitForAbort` | function | `(signal: AbortSignal) => Promise<void>` | Waits until an abort signal is aborted. |
193
- | `captureError` | function | `(thunk: () => unknown) => unknown` | Captures the value thrown by a thunk. |
194
- | `requireValue` | function | `<T>(value: T \| null \| undefined, message?: string) => T` | Narrows a value away from `null` and `undefined`, throwing when it is absent. |
195
- | `collect` | function | `<T>(source: AsyncIterable<T>) => Promise<readonly T[]>` | Collects every value from an async iterable. |
196
- | `collectStream` | function | `<T>(stream: ReadableStream<T>) => Promise<readonly T[]>` | Collects every value from a readable stream. |
197
- | `roundTripJSON` | function | `<T>(value: T & JSONSafe<T>) => T` | Copies a JSON value through serialization and parsing. |
198
- | `invokeUnchecked` | function | `<T>(target: unknown, method: unknown, args: readonly unknown[]) => T` | Invokes an unknown method through an explicit unchecked result contract. |
199
- | `readProperty` | function | `<T>(target: unknown, key: PropertyKey) => T` | Reads a property from an unknown object or function. |
200
- | `flattenHeaders` | function | `(init: HeadersSource) => Readonly<Record<string, string>>` | Normalizes headers into a frozen plain record. |
201
- | `resolveRoot` | function | `(meta: ImportMeta) => URL` | Resolves the parent directory of a calling module, which is the workspace root when called from the conventional `tests/setup.ts` location. |
202
- | `executeScenario` | function | `(scenario, context) => Promise<void>` | Drives one statechart scenario through its arrange, act, and assert phases. |
203
- | `executeScenarios` | function | `(scenarios, build) => Promise<void>` | Drives a statechart table row by row, each row against a context of its own. |
188
+ | API | Kind | Signature | Summary |
189
+ | ------------------------ | -------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
190
+ | `waitForCondition` | function | `(description, condition, options?) => Promise<void>` | Waits until a condition holds within an elapsed-time budget. |
191
+ | `retryUntil` | function | `(description, produce, satisfied, options?) => Promise<T>` | Repeats a producer until one produced value satisfies a predicate. |
192
+ | `waitForEvent` | function | `(subscribe, description, options?) => Promise<TArgs>` | Waits for the first delivery from an event subscription. |
193
+ | `waitForText` | function | `(description, read, text, options?) => Promise<string>` | Waits until a reading of text carries an expected sentence. |
194
+ | `checkBounds` | function | `(subject: string, budget: number, interval: number) => void` | Checks the resolved bounds one bounded wait runs under. |
195
+ | `buildRetryExhausted` | function | `(description, budget, elapsed, last, cause) => Error` | Builds the error `retryUntil` raises when its elapsed-time budget runs out. |
196
+ | `buildRefusal` | function | `(name: string, cause: unknown) => Error` | Builds the error a refused fixture build raises, named for the row it was building for. |
197
+ | `dropRegistration` | function | `(registrations: SignalRegistration[], installed) => SignalRegistration \| undefined` | Drops the registration an instrumented signal installed for one listener. |
198
+ | `decodeJSONLines` | function | `(text: string) => readonly unknown[]` | Decodes newline-delimited JSON values. |
199
+ | `collectSelectorClasses` | function | `(selector: string) => readonly string[]` | Collects the class names one selector text names, decoded and without repeats. |
200
+ | `waitForDelay` | function | `(ms?: number) => Promise<void>` | Waits for a host timer to elapse. |
201
+ | `waitForAbort` | function | `(signal: AbortSignal) => Promise<void>` | Waits until an abort signal is aborted. |
202
+ | `captureError` | function | `(thunk: () => unknown) => unknown` | Captures the value thrown by a thunk. |
203
+ | `requireValue` | function | `<T>(value: T \| null \| undefined, message?: string) => T` | Narrows a value away from `null` and `undefined`, throwing when it is absent. |
204
+ | `collect` | function | `<T>(source: AsyncIterable<T>) => Promise<readonly T[]>` | Collects every value from an async iterable. |
205
+ | `collectStream` | function | `<T>(stream: ReadableStream<T>) => Promise<readonly T[]>` | Collects every value from a readable stream. |
206
+ | `roundTripJSON` | function | `<T>(value: T & JSONSafe<T>) => T` | Copies a JSON value through serialization and parsing. |
207
+ | `invokeUnchecked` | function | `<T>(target: unknown, method: unknown, args: readonly unknown[]) => T` | Invokes an unknown method through an explicit unchecked result contract. |
208
+ | `readProperty` | function | `<T>(target: unknown, key: PropertyKey) => T` | Reads a property from an unknown object or function. |
209
+ | `flattenHeaders` | function | `(init: HeadersSource) => Readonly<Record<string, string>>` | Normalizes headers into a frozen plain record. |
210
+ | `resolveRoot` | function | `(meta: ImportMeta) => URL` | Resolves the parent directory of a calling module, which is the workspace root when called from the conventional `tests/setup.ts` location. |
211
+ | `executeScenario` | function | `(scenario, context) => Promise<void>` | Drives one statechart scenario through its arrange, act, and assert phases. |
212
+ | `executeScenarios` | function | `(scenarios, build) => Promise<void>` | Drives a statechart table row by row, each row against a context of its own. |
204
213
 
205
214
  `dropRegistration` is the mechanic `createSignal`'s one-shot and scope-abort paths share, exported
206
215
  because both of them call it rather than because a consumer was expected to. Reaching it takes a
207
216
  `SignalRegistration` list of your own: `createSignal` hands back `SignalInterface`, whose members are
208
217
  `controller`, `signal`, and `count`, so nothing this package returns carries the list to pass.
209
218
 
219
+ `collectSelectorClasses` reads the class names out of a selector's text, and the browser's
220
+ `readCascade` reads every loaded rule through it. It reads tokens, not the selector grammar: text
221
+ inside a quoted string, a comment, or an attribute block names no class, so `[title=".card"]`
222
+ contributes nothing, and an escaped name decodes to the class it means, so `.sm\:flex` names
223
+ `sm:flex`. A name may open with an underscore, a hyphen, two hyphens, a non-ASCII letter, or an
224
+ escape. Text it cannot read whole — an open or doubled quote, comment, attribute block, or
225
+ parenthesis, a cut-short escape, a class name opening with a digit, or a character outside the
226
+ selector alphabet such as a declaration block's braces — throws an `Error` reading
227
+ `Unreadable selector: <selector>`.
228
+
210
229
  #### Factories
211
230
 
212
231
  | API | Kind | Signature | Summary |
@@ -220,10 +239,37 @@ because both of them call it rather than because a consumer was expected to. Rea
220
239
 
221
240
  ### Browser
222
241
 
223
- Imported from `@orkestrel/test/browser`. Every journey verb here resolves its own target from a
224
- role and an accessible name and drives it through the installed Vitest provider, and none of them
225
- takes an element, a component instance, or a selector for the thing it acts on. That is what keeps
226
- a journey a description of what a person does rather than of what the markup happens to be.
242
+ Imported from `@orkestrel/test/browser`. Every journey verb here resolves its own target from an
243
+ accessible name and, where the platform supplies one, a role, and drives it through the installed
244
+ Vitest provider. None of them takes an element, a component instance, or a selector for the thing it
245
+ acts on. That is what keeps a journey a description of what a person does rather than of what the
246
+ markup happens to be.
247
+
248
+ The low-level `sendTouch` and `tapTouch` instruments take an element for coordinate projection, as
249
+ `readProtocolPoint` does. `sendFileDrag` resolves a rendered role-and-name target without requiring
250
+ focus reachability, so a status or region can receive an external file drag.
251
+
252
+ Native file inputs carry no ARIA role. Their journey uses the label resolver and the same visibility,
253
+ enabled-state, focus-order, uniqueness, and active-modal checks as the other controls.
254
+
255
+ | API | Kind | Signature | Summary |
256
+ | ------------------ | -------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
257
+ | `resolveFileInput` | function | `(name: string) => HTMLInputElement` | Resolves one visible, enabled, focus-reachable native file input by its exact label. |
258
+ | `chooseAccessible` | function | `(name: string, files: readonly FixtureFile[], options?: ChoiceOptions) => Promise<void>` | Activates a labeled native file input and fulfills its observed chooser with fixture bytes. |
259
+
260
+ Register the server entry's `chooseFiles` function as the `chooseFiles` command in the workspace's
261
+ `configs/commands.ts`, the module the root configuration passes to every browser project as
262
+ Vitest's `test.browser.commands`. A package that declares `@orkestrel/test` imports `chooseFiles`
263
+ from `@orkestrel/test/server` there and freezes `{ chooseFiles }` as its exported `commands`
264
+ record. Pass fixture filenames, media types, and byte arrays to `chooseAccessible`; `keyboard: true`
265
+ reaches the input through Tab before pressing Enter, and the default activates it with a native
266
+ pointer click. Run selections sequentially on a page. The server arms its chooser observer before
267
+ activation, checks the event's input identity, and supplies the bytes through the real Playwright
268
+ chooser. A missing or wrong chooser rejects. Observers and input handles are released after
269
+ success, activation failure, timeout, and page closure. Each provider wait defaults to a positive
270
+ 1000 ms budget. An empty fixture selection clears the input; it does not prove a manual OS Cancel
271
+ gesture. This proves browser-native chooser activation and fixture selection in Chromium, not
272
+ manual OS dialog interaction, OS drag, or network upload.
227
273
 
228
274
  The fixture builders, the readers, and the field writers do take an element, and none of them is a
229
275
  journey verb. `build` creates a node, `mount` attaches one, and `render` does both from
@@ -232,16 +278,22 @@ back the root it mounted; `buildContrast`, `buildEscapes`, and `buildCensus` eac
232
278
  a detached control the caller appends where it is reading; `clearStorage` takes nothing at all, and
233
279
  `removeDatabase` takes a database name. The predicates, the element readers, and the describers name
234
280
  a node the caller already has — `isRendered`, `isReachable`, `readHit`, `readText`, `readRole`,
235
- `readName`, `readStates`, `readCensus`, `describeTree`, `describeFocus`, `extractOrphans`,
236
- `readRows`, `readStyle`, `readToken`, `readPixels`, `readContrast`, `readLayers`, `readBackdrop`,
237
- and `readRing` — and each reads that node rather than acting on a target it was handed.
281
+ `readName`, `readStates`, `readProtocolPoint`, `readProtocolNode`, `readAccessibilityNode`,
282
+ `readActiveElement`, `countElements`, `containsNode`, `readCensus`, `describeTree`, `describeFocus`, `extractOrphans`,
283
+ `readRows`, `readStyle`, `readToken`, `readPixels`, `readContrast`, `readColorContrast`,
284
+ `readLayers`, `readBackdrop`, `readRing`, `readAnimations`, `readClippedBlock`, and
285
+ `collectScrollRegions` — and each reads that node rather than acting on a target it was handed.
238
286
  `waitForAnimations` takes one too, and waiting for a browser to stop painting it changes nothing
239
287
  about it. `captureFrame` and `place` take an
240
288
  element as well, and photographing one is a reading too: neither moves focus, dispatches an event,
241
- or changes what the element renders. `typeInput` and `commitInput` are the exception, and it stays
242
- narrow: they write into the field they are given, as the synthetic counterpart of `typeAccessible`
243
- for a component that listens for `input`. The color leaves, the cascade readers, the pane verbs,
244
- and the whole-document readers take a value or nothing at all, so they name no target either.
289
+ or changes what the element renders. `readShotPixel` takes one for the same reason and reads one
290
+ pixel of the provider's shot of it. `settleCapture` takes no element: it parks the pointer the way
291
+ `parkPointer` does and holds the document's endless animations until the resume it returns runs.
292
+ `sendTouch` and `tapTouch` act on an element through protocol coordinates. `typeInput` and
293
+ `commitInput` also take an element: they write into the field they are given, as the synthetic
294
+ counterpart of `typeAccessible` for a component that listens for `input`. The color leaves, the
295
+ cascade readers, the pane verbs, and the whole-document readers take a value or nothing at all, so
296
+ they name no target either, and neither does `stepFocus`, which takes a limit and presses Tab.
245
297
 
246
298
  #### Types
247
299
 
@@ -252,6 +304,7 @@ a union's arms escaped as `\|`.
252
304
  | Type | Kind | Shape | Summary |
253
305
  | --------------------- | --------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
254
306
  | `Color` | type | `readonly [red, green, blue, alpha]` | Represents one rendered color as straight sRGB channels and its alpha. |
307
+ | `ContrastOptions` | interface | `{ floor?, opacity? }` | Configures one color contrast reading: the surface the backdrop walk ends on, and the opacity the painted color is shown at. |
255
308
  | `ElementOptions` | interface | `{ classes?, text?, attributes? }` | Configures one built element: its class list, its text, and its attributes. |
256
309
  | `FrameOptions` | interface | `{ path, width, height, element? }` | Configures one captured frame: where it is written, the viewport it is shot at, and what it shoots. |
257
310
  | `FrameOffset` | interface | `{ top, left }` | Represents how far an element frame moves the tester frame from the runner window's origin for the shot, in CSS pixels. |
@@ -262,7 +315,13 @@ a union's arms escaped as `\|`.
262
315
  | `JournalStep` | interface | `{ action, trigger, result }` | Represents one scripted step a journal recorded, and what the surface did about it. |
263
316
  | `JournalInterface` | interface | `{ steps, output }` plus `start` / `stop` / `record` | Records one scenario: every step it took and everything the page said while it ran. |
264
317
  | `StateOptions` | interface | `WaitOptions` plus `{ absent? }` | Configures a bounded wait over the states a control announces. |
265
- | `MediaOptions` | interface | `{ print?, motion?, forced? }` | Configures the tester's print medium, motion preference, and forced colours. |
318
+ | `SettleOptions` | interface | `WaitOptions` plus `{ pointer? }` | Configures one capture settle: the time bounds of `WaitOptions` and whether the pointer parks first. |
319
+ | `AnimationReading` | interface | `{ finite, endless }` | Represents the animations running on one element and its subtree, split by whether each one ends. |
320
+ | `MediaOptions` | interface | `{ print?, motion?, forced?, pointer? }` | Configures the tester's print medium, motion preference, forced colours, and pointer precision. |
321
+ | `StabilityOptions` | interface | `WaitOptions` plus `{ frames? }` | Configures how long a reading must hold still: the time bounds of `WaitOptions` and the count of reads it must agree across. |
322
+ | `AccessibilityNode` | interface | `{ role, name, properties }` | Represents the accessibility node the browser computed for one element. |
323
+ | `SheetAdoption` | interface | `{ sheet: CSSStyleSheet, release() }` | Describes an adopted stylesheet and its idempotent release operation. |
324
+ | `ProtocolPoint` | interface | `{ x, y }` | Represents one point in the coordinates of the browser's top-level viewport, the space a DevTools input command addresses. |
266
325
  | `StorageOptions` | interface | `{ values?, reads?, writes?, quota? }` | Configures an inert `Storage`: its seed, which operations the host permits, and its quota. |
267
326
  | `WebStorageInterface` | interface | `Storage` plus `{}` plus `permit` | Holds a store the host can withhold and later grant. |
268
327
  | `CensusReading` | interface | `{ elements, tokens, undeclared }` | Reports an authored-class census: the population walked, the tokens found, and the undeclared. |
@@ -292,113 +351,145 @@ A `Shape` cell holds the constant's declared type.
292
351
 
293
352
  #### Helpers
294
353
 
295
- | API | Kind | Signature | Summary |
296
- | -------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
297
- | `resolveAccessible` | function | `(name: string) => HTMLElement` / `(role: string, name: string) => HTMLElement` | Resolves one visible, focus-reachable interactive element by its exact accessible name. A wholly-off-viewport target is scrolled into view before reachability is measured. |
298
- | `resolveAccessibleWithin` | function | `(region: string, role: string, name: string) => HTMLElement` | Resolves one human-reachable control by role and accessible-name text inside a named region. |
299
- | `resolveRendered` | function | `(first: string, second?: string) => HTMLElement` | Resolves one rendered, focus-reachable interactive element without requiring it to intersect the viewport yet. |
300
- | `computeNamePattern` | function | `(name: string) => RegExp` | Computes the pattern that matches one accessible name a decorative glyph may sit beside. |
301
- | `isOutsideViewport` | function | `(rectangle: DOMRectReadOnly) => boolean` | Determines whether a rectangle lies wholly outside the browser viewport. |
302
- | `isRendered` | function | `(element: Element) => boolean` | Determines whether the accessibility tree presents one element at all. |
303
- | `isReachable` | function | `(element: Element) => boolean` | Determines whether a person can click one element where it sits. |
304
- | `readHit` | function | `(element: Element) => Element \| undefined` | Reads the topmost element at one element's bounding-box centre. |
305
- | `clickAccessible` | function | `(name: string) => Promise<void>` / `(role: string, name: string) => Promise<void>` | Clicks one visible, focus-reachable control by its accessible name through the browser provider. |
306
- | `clickAccessibleWithin` | function | `(region: string, role: string, name: string) => Promise<void>` | Clicks one human-reachable control by role and accessible-name text inside a named region. |
307
- | `clickDisclosure` | function | `(name: string) => Promise<void>` | Opens or closes one native details disclosure by its rendered summary. |
308
- | `sendProtocol` | function | `(method: string, params: Readonly<Record<string, unknown>>) => Promise<void>` | Sends one DevTools protocol command through the browser provider. |
309
- | `hoverAccessible` | function | `(name: string) => Promise<void>` / `(role: string, name: string) => Promise<void>` | Hovers one visible, focus-reachable control by its accessible name through the browser provider. |
310
- | `holdAccessible` | function | `(name: string) => Promise<void>` / `(role: string, name: string) => Promise<void>` | Holds the primary pointer button on one visible, focus-reachable control by its accessible name. |
311
- | `holdAccessibleWithin` | function | `(region: string, role: string, name: string) => Promise<void>` | Holds the primary pointer button on one control by role and accessible-name text inside a named region. |
312
- | `driveHold` | function | `(resolve: () => HTMLElement, name: string) => Promise<void>` | Holds the primary pointer button on the control a resolver returns, through the browser provider. |
313
- | `releasePointer` | function | `() => Promise<void>` | Releases a held pointer and parks it outside the page, clearing hover. |
314
- | `typeAccessible` | function | `(name: string, text: string) => Promise<void>` | Replaces a named field's value through focus, select-all, deletion, and real keystrokes. |
315
- | `fillAccessible` | function | `(name: string, text: string) => Promise<void>` | Replaces a named field's value in one operation, for text too long to type key by key. |
316
- | `traverseAccessible` | function | `(name: string) => Promise<HTMLElement>` | Reaches a named control only through natural forward Tab traversal from the current focus. |
317
- | `traverseAccessibleWithin` | function | `(region: string, role: string, name: string) => Promise<HTMLElement>` | Reaches a control by role and accessible-name text inside a named region, only through natural forward Tab traversal from the current focus. |
318
- | `driveTraversal` | function | `(resolve: () => HTMLElement, name: string) => Promise<HTMLElement>` | Reaches the control a resolver returns, only through natural forward Tab traversal from the current focus. |
319
- | `pressKeys` | function | `(keys: string) => Promise<void>` | Sends a key sequence to whatever holds focus, and refuses to send it to nothing. |
320
- | `readPerception` | function | `(name: string) => string` | Reads the normalized visible text of one named region, dialog, table, tab panel, or alert. |
321
- | `readPage` | function | `() => string` | Reads the normalized visible text of the whole page. |
322
- | `readFocus` | function | `() => string \| undefined` | Reads the rendered text of the element that holds focus. |
323
- | `readValue` | function | `(role: string, name: string) => string` | Reads the value a resolved control renders. |
324
- | `readRefusal` | function | `(name: string) => string \| undefined` / `(role: string, name: string) => string \| undefined` | Reads the refusal one named target answers with, or nothing when it resolves. |
325
- | `readText` | function | `(element: Element) => string` | Reads one element's rendered text the way a name computation reads it. |
326
- | `readRole` | function | `(element: Element) => string \| undefined` | Reads the role one element carries in the accessibility tree. |
327
- | `readName` | function | `(element: Element) => string` | Reads the accessible name one element is announced under. |
328
- | `readStates` | function | `(element: Element) => readonly string[]` | Reads the states one element is announced in. |
329
- | `describeTree` | function | `(element: Element) => string` | Describes the accessible tree one rendered element presents. |
330
- | `describeFocus` | function | `(element: Element) => string` | Describes the order sequential keyboard navigation visits one element's controls in. |
331
- | `waitForFrame` | function | `() => Promise<void>` | Waits for one animation frame to settle pending browser paint work. |
332
- | `waitForState` | function | `(name, state, options?) => Promise<readonly string[]>` / `(role, name, state, options?) => Promise<readonly string[]>` | Waits until one named control announces a state, or stops announcing it. |
333
- | `waitForAnimations` | function | `(element: Element, options?: WaitOptions) => Promise<void>` | Waits until every finite animation on one element and its subtree has stopped moving. |
334
- | `build` | function | `<K extends keyof HTMLElementTagNameMap>(tag: K, options?: ElementOptions) => HTMLElementTagNameMap[K]` | Builds one unmounted element of a known tag, wearing the classes, text, and attributes asked for. |
335
- | `mount` | function | `<T extends Element>(element: T) => T` | Puts one element into the document and hands it straight back. |
336
- | `render` | function | `(markup: string) => HTMLDivElement` / `(tag: K, classes: string) => HTMLElementTagNameMap[K]` | Renders one fixture into the document from trusted markup. |
337
- | `typeInput` | function | `(element: HTMLInputElement \| HTMLTextAreaElement, text: string) => void` | Sets one field's value and announces it the way typing into the field does. |
338
- | `commitInput` | function | `(element: HTMLInputElement \| HTMLTextAreaElement, text: string) => void` | Sets one field's value and commits it, the way typing and then leaving the field does. |
339
- | `clearStorage` | function | `() => void` | Clears both browser storage surfaces. |
340
- | `removeDatabase` | function | `(name: string) => Promise<void>` | Deletes one IndexedDB database and reports what the request actually did. |
341
- | `convertSRGB` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts normalized encoded sRGB channels to the clipped paint scale. |
342
- | `convertLinearSRGB` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts linear sRGB channels to encoded, clipped paint channels. |
343
- | `convertXYZD65` | function | `(x: number, y: number, z: number, alpha?: number) => Color` | Converts D65 XYZ coordinates to clipped sRGB paint channels. |
344
- | `convertXYZD50` | function | `(x: number, y: number, z: number, alpha?: number) => Color` | Converts D50 XYZ coordinates to clipped sRGB paint channels. |
345
- | `convertOKLab` | function | `(lightness: number, a: number, b: number, alpha?: number) => Color` | Converts OKLab coordinates to clipped sRGB paint channels. |
346
- | `convertLab` | function | `(lightness: number, a: number, b: number, alpha?: number) => Color` | Converts CIE Lab coordinates relative to D50 to clipped sRGB paint channels. |
347
- | `convertDisplayP3` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts encoded Display P3 channels to clipped sRGB paint channels. |
348
- | `convertA98RGB` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts encoded A98 RGB channels to clipped sRGB paint channels. |
349
- | `convertProPhotoRGB` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts encoded ProPhoto RGB channels to clipped sRGB paint channels. |
350
- | `convertRec2020` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts encoded Rec. 2020 channels to clipped sRGB paint channels. |
351
- | `parseColor` | function | `(value: string) => Color \| undefined` | Parses computed CSS Color 4 values into clipped straight sRGB channels. |
352
- | `parseCSSColor` | function | `(value: string) => Color \| undefined` | Resolves any CSS color expression to straight sRGB channels, by asking the browser. |
353
- | `matchesColor` | function | `(first: string \| Color, second: string \| Color) => boolean` | Determines whether two colors render the same, within the rounding a browser does. |
354
- | `blendColor` | function | `(front: Color, back: Color) => Color` | Composites one color over another. |
355
- | `measureLuminance` | function | `(color: Color) => number` | Measures one opaque color's WCAG relative luminance. |
356
- | `measureContrast` | function | `(front: Color, back: Color) => number` | Measures the WCAG 2.x contrast ratio between two opaque colors. |
357
- | `readLayers` | function | `(element: Element) => readonly Color[]` | Collects readable background color layers and refuses an unreadable painted layer. |
358
- | `readBackdrop` | function | `(element: Element, floor: Color) => Color` | Resolves the opaque color standing behind one element. |
359
- | `readContrast` | function | `(element: Element, floor?: Color) => number` | Measures the WCAG 2.x contrast ratio between an element's computed text and background colors. |
360
- | `readRing` | function | `(control: Element, worn?: Element) => number \| undefined` | Measures the contrast the focus chrome painted on one control reaches against its own backdrop. |
361
- | `readClipEdge` | function | `(element: Element) => number \| undefined` | Measures the row a clipping element cuts its content off at, in document coordinates. |
362
- | `readClipMargin` | function | `(element: Element) => number` | Measures how far past its own box a clipping element lets its content show. |
363
- | `clipsOverflow` | function | `(element: Element) => boolean` | Reports whether an element clips its descendants' overflow. |
364
- | `measureContent` | function | `() => number` | Measures the row the document's own content ends on, in document coordinates. |
365
- | `stagePane` | function | `(width: number, height: number) => Promise<void>` | Sets the tester's viewport and renders the runner's pane at the size that viewport claims. |
366
- | `releasePane` | function | `() => Promise<void>` | Hands the tester pane back to the runner's own layout, at the viewport it had before staging. |
367
- | `computeOffset` | function | `(box: DOMRectReadOnly, width: number, height: number) => FrameOffset` | Computes how far an element frame moves the tester frame so the element is shot inside the runner's window. |
368
- | `stageMedia` | function | `(options: MediaOptions) => Promise<void>` | Stages the tester's print medium, motion preference, and forced colours through the browser provider. |
369
- | `releaseMedia` | function | `() => Promise<void>` | Restores the media readings observed before the first stage as explicit emulation. With nothing staged, clears every override and waits for a stable reading, not a proved engine baseline. Checks the budget between polls, so a frame that never paints is not bounded by it. |
370
- | `captureFrame` | function | `(options: FrameOptions) => Promise<string>` | Shoots one frame at one viewport size and proves the file on disk holds this run's bytes. |
371
- | `readFrame` | function | `(path: string) => Promise<FrameReading>` | Reads one written frame back and reports its size and the color its bottom row paints. |
372
- | `readCascade` | function | `() => ReadonlySet<string>` | Collects every class token the stylesheets loaded into this document actually define. |
373
- | `readClasses` | function | `(root: ParentNode) => ReadonlySet<string>` | Collects every class token the markup under one root carries. |
374
- | `readCensus` | function | `(root: ParentNode) => CensusReading` | Takes the authored-class census of one subtree against the cascade this document loaded. |
375
- | `readRules` | function | `() => readonly CSSRule[]` | Collects every rule the stylesheets loaded into this document hold, nested grouping rules included. |
376
- | `findRule` | function | `(selector: string) => CSSStyleRule \| undefined` | Finds the first style rule in the cascade whose selector carries a fragment. |
377
- | `findKeyframes` | function | `(name: string) => CSSKeyframesRule \| undefined` | Finds the animation the cascade declares under one name. |
378
- | `readRows` | function | `(root: ParentNode, selector: string) => readonly string[]` | Reads the normalized visible text of every element a selector matches, in document order. |
379
- | `extractOrphans` | function | `(root: ParentNode, child: string, parent: string) => readonly string[]` | Collects every element carrying a component class rendered outside the container it belongs to. |
380
- | `extractStyles` | function | `(root: ParentNode) => readonly string[]` | Collects the markup of every element carrying a non-empty `style` attribute and of every `<style>` element, in document order, `root` included in both populations when it is an `Element`. |
381
- | `readStyle` | function | `(element: Element, property: string, pseudo?: string) => string` | Reads one resolved CSS property from a real browser element or a named pseudo-element. |
382
- | `readToken` | function | `(element: Element, name: string) => string` | Reads one custom property from an element's resolved style. |
383
- | `readRootToken` | function | `(name: string) => string` | Reads one custom property from the document element. |
384
- | `readPixels` | function | `(element: Element, property: string, pseudo?: string) => number` | Reads one resolved CSS length as a number of pixels. |
385
- | `expandCaptures` | function | `(states: readonly string[], variants: readonly CaptureVariant[]) => readonly string[]` | Expands a capture registry across every variant into the filenames a complete portfolio holds. |
386
- | `buildDenial` | function | `(operation: string, key?: string) => DOMException` | Builds the refusal a host withholding a storage operation raises. |
387
- | `buildContrast` | function | `(bar: number) => ContrastFixture` | Builds a detached translucent stack whose composited and flat contrast readings straddle one bar. |
388
- | `buildEscapes` | function | `(permitted: string) => EscapeFixture` | Builds detached markup carrying one style escape of each kind, plus the sheet a project allows. |
389
- | `buildCensus` | function | `() => CensusFixture` | Builds detached markup carrying one undeclared class token on HTML and another on SVG. |
354
+ | API | Kind | Signature | Summary |
355
+ | -------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
356
+ | `resolveAccessible` | function | `(name: string) => HTMLElement` / `(role: string, name: string) => HTMLElement` | Resolves one visible, focus-reachable interactive element by its exact accessible name. A wholly-off-viewport target is scrolled into view before reachability is measured. |
357
+ | `resolveAccessibleWithin` | function | `(region: string, role: string, name: string) => HTMLElement` | Resolves one human-reachable control by role and accessible-name text inside a named region. |
358
+ | `resolveRendered` | function | `(first: string, second?: string) => HTMLElement` | Resolves one rendered, focus-reachable interactive element without requiring it to intersect the viewport yet. |
359
+ | `computeNamePattern` | function | `(name: string) => RegExp` | Computes the pattern that matches one accessible name a decorative glyph may sit beside. |
360
+ | `isOutsideViewport` | function | `(rectangle: DOMRectReadOnly) => boolean` | Determines whether a rectangle lies wholly outside the browser viewport. |
361
+ | `isRendered` | function | `(element: Element) => boolean` | Determines whether the accessibility tree presents one element at all. |
362
+ | `isReachable` | function | `(element: Element) => boolean` | Determines whether a person can click one element where it sits. |
363
+ | `readHit` | function | `(element: Element) => Element \| undefined` | Reads the topmost element at one element's bounding-box centre. |
364
+ | `clickAccessible` | function | `(name: string) => Promise<void>` / `(role: string, name: string) => Promise<void>` | Clicks one visible, focus-reachable control by its accessible name through the browser provider. |
365
+ | `clickAccessibleWithin` | function | `(region: string, role: string, name: string) => Promise<void>` | Clicks one human-reachable control by role and accessible-name text inside a named region. |
366
+ | `clickDisclosure` | function | `(name: string) => Promise<void>` | Opens or closes one native details disclosure by its rendered summary. |
367
+ | `sendProtocol` | function | `(method: string, params?: Readonly<Record<string, unknown>>) => Promise<unknown>` | Sends one DevTools protocol command through the browser provider. |
368
+ | `readProtocolPoint` | function | `(element: Element, offset?: ProtocolPoint) => ProtocolPoint` | Projects a point of one element through every frame that holds it into the browser's top-level viewport. |
369
+ | `readProtocolNode` | function | `(element: Element) => Promise<number>` | Resolves one element to the node the browser's DOM tree holds for it. |
370
+ | `readAccessibilityNode` | function | `(element: Element) => Promise<AccessibilityNode>` | Reads the accessibility node the browser computed for one element. |
371
+ | `hoverAccessible` | function | `(name: string) => Promise<void>` / `(role: string, name: string) => Promise<void>` | Hovers one visible, focus-reachable control by its accessible name through the browser provider. |
372
+ | `holdAccessible` | function | `(name: string) => Promise<void>` / `(role: string, name: string) => Promise<void>` | Holds the primary pointer button on one visible, focus-reachable control by its accessible name. |
373
+ | `holdAccessibleWithin` | function | `(region: string, role: string, name: string) => Promise<void>` | Holds the primary pointer button on one control by role and accessible-name text inside a named region. |
374
+ | `driveHold` | function | `(resolve: () => HTMLElement, name: string) => Promise<void>` | Holds the primary pointer button on the control a resolver returns, through the browser provider. |
375
+ | `parkPointer` | function | `() => Promise<void>` | Parks the pointer outside the page and waits for the parked pointer's frame to paint. |
376
+ | `releasePointer` | function | `() => Promise<void>` | Releases a held pointer and parks it outside the page, clearing hover. |
377
+ | `typeAccessible` | function | `(name: string, text: string) => Promise<void>` | Replaces a named field's value through focus, select-all, deletion, and real keystrokes. |
378
+ | `fillAccessible` | function | `(name: string, text: string) => Promise<void>` | Replaces a named field's value in one operation, for text too long to type key by key. |
379
+ | `traverseAccessible` | function | `(name: string) => Promise<HTMLElement>` | Reaches a named control only through natural forward Tab traversal from the current focus. |
380
+ | `traverseAccessibleWithin` | function | `(region: string, role: string, name: string) => Promise<HTMLElement>` | Reaches a control by role and accessible-name text inside a named region, only through natural forward Tab traversal from the current focus. |
381
+ | `readActiveElement` | function | `(root?: Document \| ShadowRoot) => HTMLElement \| undefined` | Reads the HTML element that holds focus, following it into open shadow roots and same-origin frames. |
382
+ | `countElements` | function | `(root?: Document \| ShadowRoot) => number` | Counts the elements a tree holds, descending into open shadow roots and same-origin frames. |
383
+ | `containsNode` | function | `(root: Node, node: Node) => boolean` | Determines whether a node sits inside a root, through open shadow roots and frames. |
384
+ | `stepFocus` | function | `(limit: number) => AsyncGenerator<HTMLElement \| undefined, void, undefined>` | Presses Tab and yields the element focus lands on, once per press, up to a limit. |
385
+ | `driveTraversal` | function | `(resolve: () => HTMLElement, name: string) => Promise<HTMLElement>` | Reaches the control a resolver returns, only through natural forward Tab traversal from the current focus. |
386
+ | `walkFocus` | function | `(stop?: (element: HTMLElement) => boolean) => Promise<readonly HTMLElement[]>` | Walks forward by real Tab presses from the focused element until a predicate accepts a stop or the Tab order ends. |
387
+ | `collectTabStops` | function | `(root: Element, start: HTMLElement) => Promise<readonly HTMLElement[]>` | Collects the elements of a region that a forward Tab walk reaches from a starting element. |
388
+ | `pressKeys` | function | `(keys: string) => Promise<void>` | Sends a key sequence to whatever holds focus, and refuses to send it to nothing. |
389
+ | `readPerception` | function | `(name: string) => string` | Reads the normalized visible text of one named region, dialog, table, tab panel, or alert. |
390
+ | `readPage` | function | `() => string` | Reads the normalized visible text of the whole page. |
391
+ | `readFocus` | function | `() => string \| undefined` | Reads the rendered text of the element that holds focus. |
392
+ | `readValue` | function | `(role: string, name: string) => string` | Reads the value a resolved control renders. |
393
+ | `readRefusal` | function | `(name: string) => string \| undefined` / `(role: string, name: string) => string \| undefined` | Reads the refusal one named target answers with, or nothing when it resolves. |
394
+ | `readText` | function | `(element: Element) => string` | Reads one element's rendered text the way a name computation reads it. |
395
+ | `readRole` | function | `(element: Element) => string \| undefined` | Reads the role one element carries in the accessibility tree. |
396
+ | `readName` | function | `(element: Element) => string` | Reads the accessible name one element is announced under. |
397
+ | `readStates` | function | `(element: Element) => readonly string[]` | Reads the states one element is announced in. |
398
+ | `describeTree` | function | `(element: Element) => string` | Describes the accessible tree one rendered element presents. |
399
+ | `describeFocus` | function | `(element: Element) => string` | Describes the order sequential keyboard navigation visits one element's controls in. |
400
+ | `waitForFrame` | function | `() => Promise<void>` | Waits for one animation frame to settle pending browser paint work. |
401
+ | `waitForState` | function | `(name, state, options?) => Promise<readonly string[]>` / `(role, name, state, options?) => Promise<readonly string[]>` | Waits until one named control announces a state, or stops announcing it. |
402
+ | `waitForAnimations` | function | `(element: Element, options?: WaitOptions) => Promise<void>` | Waits until every finite animation on one element and its subtree has stopped moving. |
403
+ | `readAnimations` | function | `(element: Element) => AnimationReading` | Reads the animations running on one element and its subtree, split by whether each one ends. |
404
+ | `settleCapture` | function | `(options?: SettleOptions) => Promise<() => void>` | Settles the page for one capture: parks the pointer, waits out every finite animation, and holds each endless animation at its first frame. |
405
+ | `collectScrollRegions` | function | `(root: Element, axis?: 'inline' \| 'block') => readonly HTMLElement[]` | Collects the rendered boxes a person can scroll. |
406
+ | `describeScrollRegions` | function | `(regions: readonly HTMLElement[]) => string` | Describes the window's scroll position and each scroll region's offsets and extents. |
407
+ | `build` | function | `<K extends keyof HTMLElementTagNameMap>(tag: K, options?: ElementOptions) => HTMLElementTagNameMap[K]` | Builds one unmounted element of a known tag, wearing the classes, text, and attributes asked for. |
408
+ | `mount` | function | `<T extends Element>(element: T) => T` | Puts one element into the document and hands it straight back. |
409
+ | `typeInput` | function | `(element: HTMLInputElement \| HTMLTextAreaElement, text: string) => void` | Sets one field's value and announces it the way typing into the field does. |
410
+ | `commitInput` | function | `(element: HTMLInputElement \| HTMLTextAreaElement, text: string) => void` | Sets one field's value and commits it, the way typing and then leaving the field does. |
411
+ | `render` | function | `(markup: string) => HTMLDivElement` / `(tag: K, classes: string) => HTMLElementTagNameMap[K]` | Renders one fixture into the document from trusted markup. |
412
+ | `clearStorage` | function | `() => void` | Clears both browser storage surfaces. |
413
+ | `removeDatabase` | function | `(name: string) => Promise<void>` | Deletes one IndexedDB database and reports what the request actually did. |
414
+ | `convertSRGB` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts normalized encoded sRGB channels to the clipped paint scale. |
415
+ | `convertLinearSRGB` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts linear sRGB channels to encoded, clipped paint channels. |
416
+ | `convertXYZD65` | function | `(x: number, y: number, z: number, alpha?: number) => Color` | Converts D65 XYZ coordinates to clipped sRGB paint channels. |
417
+ | `convertXYZD50` | function | `(x: number, y: number, z: number, alpha?: number) => Color` | Converts D50 XYZ coordinates to clipped sRGB paint channels. |
418
+ | `convertOKLab` | function | `(lightness: number, a: number, b: number, alpha?: number) => Color` | Converts OKLab coordinates to clipped sRGB paint channels. |
419
+ | `convertLab` | function | `(lightness: number, a: number, b: number, alpha?: number) => Color` | Converts CIE Lab coordinates relative to D50 to clipped sRGB paint channels. |
420
+ | `convertDisplayP3` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts encoded Display P3 channels to clipped sRGB paint channels. |
421
+ | `convertA98RGB` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts encoded A98 RGB channels to clipped sRGB paint channels. |
422
+ | `convertProPhotoRGB` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts encoded ProPhoto RGB channels to clipped sRGB paint channels. |
423
+ | `convertRec2020` | function | `(red: number, green: number, blue: number, alpha?: number) => Color` | Converts encoded Rec. 2020 channels to clipped sRGB paint channels. |
424
+ | `parseColor` | function | `(value: string) => Color \| undefined` | Parses computed CSS Color 4 values into clipped straight sRGB channels. |
425
+ | `parseCSSColor` | function | `(value: string, scope?: Element) => Color \| undefined` | Resolves any CSS color expression to straight sRGB channels, by asking the browser. |
426
+ | `matchesColor` | function | `(first: string \| Color, second: string \| Color) => boolean` | Determines whether two colors render the same, within the rounding a browser does. |
427
+ | `blendColor` | function | `(front: Color, back: Color) => Color` | Composites one color over another. |
428
+ | `measureLuminance` | function | `(color: Color) => number` | Measures one opaque color's WCAG relative luminance. |
429
+ | `measureContrast` | function | `(front: Color, back: Color) => number` | Measures the WCAG 2.x contrast ratio between two opaque colors. |
430
+ | `readLayers` | function | `(element: Element) => readonly Color[]` | Collects readable background color layers and refuses an unreadable painted layer. |
431
+ | `readBackdrop` | function | `(element: Element, floor: Color) => Color` | Resolves the opaque color standing behind one element. |
432
+ | `readColorContrast` | function | `(element: Element, color: string, options?: ContrastOptions) => number` | Measures the WCAG 2.x contrast ratio one color reaches when painted over an element's backdrop. |
433
+ | `readContrast` | function | `(element: Element, floor?: Color) => number` | Measures the WCAG 2.x contrast ratio between an element's computed text and background colors. |
434
+ | `readRing` | function | `(control: Element, worn?: Element) => number \| undefined` | Measures the contrast the focus chrome painted on one control reaches against its own backdrop. |
435
+ | `readClipEdge` | function | `(element: Element) => number \| undefined` | Measures the row a clipping element cuts its content off at, in document coordinates. |
436
+ | `readClipMargin` | function | `(element: Element) => number` | Measures how far past its own box a clipping element lets its content show. |
437
+ | `readClippedBlock` | function | `(element: Element) => number` | Measures the block size an element shows after every clipping ancestor cuts it. |
438
+ | `collectClippingAncestors` | function | `(element: Element) => readonly Element[]` | Collects the ancestors of an element that clip their descendants' overflow. |
439
+ | `clipsOverflow` | function | `(element: Element) => boolean` | Reports whether an element clips its descendants' overflow. |
440
+ | `measureContent` | function | `() => number` | Measures the row the document's own content ends on, in document coordinates. |
441
+ | `stagePane` | function | `(width: number, height: number) => Promise<void>` | Sets the tester's viewport and renders the runner's pane at the size that viewport claims. |
442
+ | `releasePane` | function | `() => Promise<void>` | Hands the tester pane back to the runner's own layout, at the viewport it had before staging. |
443
+ | `computeOffset` | function | `(box: DOMRectReadOnly, width: number, height: number) => FrameOffset` | Computes how far an element frame moves the tester frame so the element is shot inside the runner's window. |
444
+ | `stageMedia` | function | `(options: MediaOptions) => Promise<void>` | Stages the tester's print medium, motion preference, forced colours, and pointer precision through the browser provider. |
445
+ | `releaseMedia` | function | `() => Promise<void>` | Restores the media readings observed before the first stage as explicit emulation. With nothing staged, clears every override and waits for a stable reading, not a proved engine baseline. Checks the budget between polls, so a frame that never paints is not bounded by it. |
446
+ | `waitForStability` | function | `(description: string, read: () => string, options?: StabilityOptions) => Promise<void>` | Waits for a reading to hold across its last `frames + 1` reads, each later read taken after a painted frame, with the interval slept between polls. Checks the budget between reads, so a frame that never paints is not bounded by it. |
447
+ | `captureFrame` | function | `(options: FrameOptions) => Promise<string>` | Shoots one frame at one viewport size and proves the file on disk holds this run's bytes. |
448
+ | `paintImage` | function | `(encoded: string) => Promise<OffscreenCanvasRenderingContext2D \| undefined>` | Decodes a base64 PNG and paints it, unscaled, onto a fresh offscreen 2D canvas. |
449
+ | `readFrame` | function | `(path: string) => Promise<FrameReading>` | Reads one written frame back and reports its size and the color its bottom row paints. |
450
+ | `readShotPixel` | function | `(element: Element, x: number, y: number) => Promise<Color>` | Reads one painted pixel of an element's screenshot. |
451
+ | `readCascade` | function | `() => ReadonlySet<string>` | Collects every class token the stylesheets loaded into this document actually define. |
452
+ | `readClasses` | function | `(root: ParentNode) => ReadonlySet<string>` | Collects every class token the markup under one root carries. |
453
+ | `readCensus` | function | `(root: ParentNode) => CensusReading` | Takes the authored-class census of one subtree against the cascade this document loaded. |
454
+ | `sendTouch` | function | `(type: 'touchStart' \| 'touchMove' \| 'touchEnd' \| 'touchCancel', element?: Element, point?: ProtocolPoint) => Promise<void>` | Sends one emulated touch event through the browser protocol. |
455
+ | `tapTouch` | function | `(element: Element, options?: StabilityOptions) => Promise<void>` | Taps an element's centre with emulated touch after its box settles. |
456
+ | `releaseTouch` | function | `() => Promise<void>` | Cancels emulated touch input, disables touch emulation, and parks the pointer. |
457
+ | `sendFileDrag` | function | `(type: 'dragEnter' \| 'dragOver' \| 'dragCancel', role: string, name: string, names: readonly string[]) => Promise<void>` | Carries files from outside the page over a rendered target named by role and accessible name. |
458
+ | `adoptSheet` | function | `(text: string) => SheetAdoption` | Adopts CSS text into the document with an idempotent release operation. |
459
+ | `readLayerNames` | function | `(source: CSSStyleSheet \| CSSGroupingRule \| CSSStyleRule, layer?: string) => readonly string[]` | Reads layer statements and blocks in declaration order, including repeated names. |
460
+ | `readRules` | function | `() => readonly CSSRule[]` | Collects every rule the stylesheets loaded into this document hold, nested grouping rules included. |
461
+ | `findRule` | function | `(selector: string) => CSSStyleRule \| undefined` | Finds the first style rule in the cascade whose selector carries a fragment. |
462
+ | `findKeyframes` | function | `(name: string) => CSSKeyframesRule \| undefined` | Finds the animation the cascade declares under one name. |
463
+ | `readRows` | function | `(root: ParentNode, selector: string) => readonly string[]` | Reads the normalized visible text of every element a selector matches, in document order. |
464
+ | `extractOrphans` | function | `(root: ParentNode, child: string, parent: string) => readonly string[]` | Collects every element carrying a component class rendered outside the container it belongs to. |
465
+ | `extractStyles` | function | `(root: ParentNode) => readonly string[]` | Collects the markup of every element carrying a non-empty `style` attribute and of every `<style>` element, in document order, `root` included in both populations when it is an `Element`. |
466
+ | `readStyle` | function | `(element: Element, property: string, pseudo?: string) => string` | Reads one resolved CSS property from a real browser element or a named pseudo-element. |
467
+ | `readToken` | function | `(element: Element, name: string) => string` | Reads one custom property from an element's resolved style. |
468
+ | `readRootToken` | function | `(name: string) => string` | Reads one custom property from the document element. |
469
+ | `readPixels` | function | `(element: Element, property: string, pseudo?: string) => number` | Reads one resolved CSS length as a number of pixels. |
470
+ | `expandCaptures` | function | `(states: readonly string[], variants: readonly CaptureVariant[]) => readonly string[]` | Expands a capture registry across every variant into the filenames a complete portfolio holds. |
471
+ | `buildDenial` | function | `(operation: string, key?: string) => DOMException` | Builds the refusal a host withholding a storage operation raises. |
472
+ | `buildContrast` | function | `(bar: number) => ContrastFixture` | Builds a detached translucent stack whose composited and flat contrast readings straddle one bar. |
473
+ | `buildEscapes` | function | `(permitted: string) => EscapeFixture` | Builds detached markup carrying one style escape of each kind, plus the sheet a project allows. |
474
+ | `buildCensus` | function | `() => CensusFixture` | Builds detached markup carrying one undeclared class token on HTML and another on SVG. |
390
475
 
391
476
  #### Factories
392
477
 
393
- | API | Kind | Signature | Summary |
394
- | -------------------- | -------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
395
- | `createPointerEvent` | function | `(name: string, options?: PointerEventInit) => PointerEvent` | Creates one real pointer event, ready to dispatch. |
396
- | `createDragEvent` | function | `(name: string, options?: DragEventInit) => DragEvent` | Creates one real drag event carrying a live data transfer, ready to dispatch. |
397
- | `createPortfolio` | function | `(options: PortfolioOptions) => PortfolioInterface` | Creates the capture portfolio one run places its screenshots through. |
398
- | `createChannel` | function | `(name: string, output: string[], forward: (...data: unknown[]) => void) => (...data: unknown[]) => void` | Creates one console channel that records every call it receives and hands that call on unchanged. |
399
- | `createJournal` | function | `() => JournalInterface` | Creates the journal one scenario records its steps and the page's own output into. |
400
- | `createStorage` | function | `(options?: StorageOptions) => WebStorageInterface` | Creates an inert `Storage` a host can withhold, grant, and run out of room in. |
401
- | `createHarness` | function | `(options: HarnessOptions<TState, TEvent, TContext>) => HarnessInterface` | Creates a mounted statechart harness that renders one transition table and drives it row by row. |
478
+ | API | Kind | Signature | Summary |
479
+ | --------------------- | -------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
480
+ | `createPortfolio` | function | `(options: PortfolioOptions) => PortfolioInterface` | Creates the capture portfolio one run places its screenshots through; a disabled run still refuses. |
481
+ | `createChannel` | function | `(name: string, output: string[], forward: (...data: unknown[]) => void) => (...data: unknown[]) => void` | Creates one console channel that records every call it receives and hands that call on unchanged. |
482
+ | `createJournal` | function | `() => JournalInterface` | Creates the journal one scenario records its steps and the page's own output into. |
483
+ | `createStorage` | function | `(options?: StorageOptions) => WebStorageInterface` | Creates an inert `Storage` a host can withhold, grant, and run out of room in. |
484
+ | `createHarness` | function | `(options: HarnessOptions<TState, TEvent, TContext>) => HarnessInterface` | Creates a mounted statechart harness that renders one transition table and drives it row by row. |
485
+ | `createPointerEvent` | function | `(name: string, options?: PointerEventInit) => PointerEvent` | Creates one real pointer event, ready to dispatch. |
486
+ | `createDragEvent` | function | `(name: string, options?: DragEventInit) => DragEvent` | Creates one real drag event carrying a live data transfer, ready to dispatch. |
487
+ | `createKeyboardEvent` | function | `(name: string, options?: KeyboardEventInit) => KeyboardEvent` | Creates one real keyboard event, ready to dispatch. |
488
+
489
+ `createPointerEvent` and `createDragEvent` default to `composed: true` from 0.0.27, where 0.0.26
490
+ left it `false`. An event dispatched inside an open shadow root stayed there in 0.0.26, and a
491
+ listener outside that root heard nothing; a listener outside it hears the event from 0.0.27. Pass
492
+ `{ composed: false }` to restore the 0.0.26 boundary behavior.
402
493
 
403
494
  `resolveAccessible` counts a match as reachable only when every condition holds: it is connected; it
404
495
  passes a visibility check honouring opacity and CSS; its box has non-zero width and height; its
@@ -498,12 +589,26 @@ clipped to 0–255 after conversion; this reader does not perform perceptual gam
498
589
  Alpha is clipped to 0–1.
499
590
 
500
591
  `parseCSSColor` is the live half of the pair `parseColor` opens. `parseColor` reads text and speaks
501
- only the computed syntaxes a cascade hands back; `parseCSSColor` stages a probe element, hands the
502
- expression to the real cascade, and reads back what the engine computed — which is the only way a
503
- keyword, a hex triple, a `var()` reference, or a `color-mix()` becomes channels at all. The probe is
504
- mounted, so a `var()` reference resolves against the tokens `:root` declares, and it is removed in a
505
- `finally`. Refusal is the CSSOM's: an expression it will not parse returns `undefined`. A `var()`
506
- naming an undeclared custom property is not refused, and [Limits](#limits) states what that costs.
592
+ only the computed syntaxes a cascade hands back; `parseCSSColor` hands the expression to the real
593
+ cascade and reads back what the engine computed — which is the only way a keyword, a hex triple, a
594
+ `var()` reference, or a `color-mix()` becomes channels at all. With no scope the expression resolves
595
+ on a probe that is a child of the body, mounted so a `var()` reference resolves against the tokens
596
+ `:root` declares, and removed in a `finally`. Refusal is the CSSOM's: an expression it will not parse
597
+ returns `undefined`. A `var()` naming an undeclared custom property is not refused, and
598
+ [Limits](#limits) states what that costs. The optional `scope` argument resolves the expression on
599
+ that same body-mounted probe, which carries the scope's computed context onto its own inline style:
600
+ the computed value of every custom property the expression names, a fallback's included, the
601
+ scope's computed `color`, and its computed `color-scheme`. The expression is written into
602
+ `background-color`, so `currentColor` is the carried `color`, and a system color or `light-dark()`
603
+ answers as the scope's scheme. The element's own and inherited custom properties apply, a registered
604
+ property that does not inherit included, and an `input`, an SVG element, or any other element
605
+ resolves like any other. A custom property is named by letters, digits, `_`, and `-` after its `--`,
606
+ and a name the scope leaves undeclared is not carried, so its `var()` fallback still applies. The
607
+ scope element is never written: the probe is new, so it has no earlier style to start a transition
608
+ from, a transition running on the scope keeps running and `currentColor` reads the color the scope
609
+ shows at that moment, and the scope's geometry and scroll position stay as they were. No attribute,
610
+ style, or child of the scope changes, so a `MutationObserver` on it records nothing. A disconnected
611
+ scope computes no style and returns `undefined`.
507
612
 
508
613
  `matchesColor` compares two colors as a browser renders them. Each string side resolves through
509
614
  `parseCSSColor`, so a keyword, a token reference, and the `rgb()` an engine computes for either
@@ -522,6 +627,21 @@ or the color a fragment is really mounted onto — and the same stack composites
522
627
  being refused. A detached element is refused either way, because its computed foreground color does
523
628
  not exist.
524
629
 
630
+ `readColorContrast` is the same measurement with the color handed in, for paint that is not the
631
+ element's own text: a separator grip, a placeholder, a checkbox mark. `readContrast` and `readRing`
632
+ both measure through it. A computed color value parses directly, and any other expression — a
633
+ keyword, a hex triple, a `var()` reference — resolves through `parseCSSColor` at the element's own
634
+ scope: a token reads as the element and its ancestors declare it, and `currentColor` is the
635
+ element's own text color, not the body's. The read carries the element's computed custom
636
+ properties, `color`, and `color-scheme` onto a probe, so an `input` and a registered token that does
637
+ not inherit resolve as they do on any other element, and the element itself is never written. A
638
+ disconnected element computes no style, so it refuses such an expression as unreadable; hand it a
639
+ computed color such as `rgb(255, 255, 255)`. `floor` takes the
640
+ part `readContrast`'s second argument takes, with the same refusal and the same voice when it is
641
+ omitted. `opacity` multiplies the color's own alpha before it is composited, for a mark painted
642
+ inside an element whose `opacity` is below `1`, and a value outside `0` to `1` is refused rather than
643
+ read.
644
+
525
645
  `readLayers` is the reading that refusal turns on. It hands back the painted layers themselves,
526
646
  element first and the deepest last, so a walk that ended on a real surface is the one whose last
527
647
  layer has an alpha of `1`. A composited color cannot answer that question: 64 half-transparent
@@ -533,7 +653,10 @@ An unreadable painted background layer throws an error naming its element and co
533
653
  Non-finite calculations such as `color(srgb calc(infinity) 0 0)` are deliberately unreadable.
534
654
  A layer with an explicit zero alpha paints nothing and is skipped, including an unreadable one.
535
655
  Background images remain outside this color reader. The refusal propagates through `readBackdrop`,
536
- `readContrast`, and the focused-control reading in `readRing`, even with a supplied floor.
656
+ `readContrast`, `readColorContrast`, and the reading `readRing` takes of painted focus chrome, even
657
+ with a supplied floor. `readRing` reads the backdrop for every control matching `:focus-visible`
658
+ before it looks for chrome, so a focused control whose cascade paints no chrome is refused over an
659
+ unreadable surface too.
537
660
 
538
661
  `readBackdrop` composites that stack and takes its floor as an argument rather than reaching for
539
662
  `CANVAS_COLOR` itself, so a measurement over a surface the canvas never shows through names the
@@ -566,6 +689,16 @@ class declared inside an earlier grouping rule; 0.0.8 popped a stack and inserte
566
689
  first. Iterate the set where the order is the subject and read `has` where membership is. A
567
690
  `@keyframes` rule's own children are outside the walk, so an animation's stops define no token here.
568
691
 
692
+ Each rule's selector text is read by `collectSelectorClasses`, and that reading differs from 0.0.26,
693
+ which matched a pattern over the text. An escaped name decodes, so `.sm\:flex` defines `sm:flex`
694
+ where 0.0.26 read `sm`. Text inside an attribute value, a string, or a comment defines nothing, so
695
+ `[title=".card"]` defines no `card`. A name opening with an underscore, a hyphen, or a non-ASCII
696
+ letter counts, where 0.0.26 skipped it. A non-ASCII letter inside a name stays in it, so `.café`
697
+ defines `café` where 0.0.26 ended the class at the first code point outside ASCII letters, digits,
698
+ `_`, and `-` and defined `caf`. A census that saw any of those misreadings reports the
699
+ corrected set, so `readCensus` can move a token into or out of `undeclared`. One rule's own tokens
700
+ enter the set in code-unit order rather than in the order the selector writes them.
701
+
569
702
  `findRule` and `findKeyframes` differ in how they match, and the subject is what decides it. A
570
703
  selector is compound, so `findRule` matches its argument as a substring of the whole selector text:
571
704
  `findRule('.card')` finds `.card`, `.card:hover`, and `.panel > .card` alike, and more of the
@@ -626,7 +759,9 @@ the body instead, taking the largest bottom edge in document coordinates plus th
626
759
  bottom margin, and adds the body's and the root's bottom padding and margin under them. An
627
760
  ancestor that clips its overflow (the `clipsOverflow` helper: an `overflow-y` value other than the
628
761
  `visible` keyword, or a paint containment) caps a descendant's edge at that ancestor's clip edge,
629
- which the `readClipEdge` helper reads. An `overflow-y` value of the `hidden` keyword, the `auto`
762
+ which the `readClipEdge` helper reads. The `collectClippingAncestors` helper finds those ancestors
763
+ through the flat tree, from an element to the slot it is assigned to, to its parent, and out of a
764
+ shadow root to its host, so a wrapper in a shadow tree around a slot clips what is slotted into it. An `overflow-y` value of the `hidden` keyword, the `auto`
630
765
  keyword, or the `scroll` keyword clips at the padding box, whatever the ancestor's
631
766
  `overflow-clip-margin` value selects. The `clip` keyword and a paint containment over a `visible`
632
767
  overflow clip at the box that value selects, the padding box by default, expanded by the length the
@@ -657,15 +792,27 @@ written at a height that is already wrong. That bound is 4: a document holding h
657
792
  fixed block settles in two restagings, one whose growth is capped part way settles in three, and
658
793
  the fourth is headroom.
659
794
 
660
- An element frame no taller than the declared height is shot in the declared pane, so its viewport
661
- lengths resolve against the declared viewport whether the element sits above the fold, below it, or
662
- fixed: a `50vh` element under a 900-row block reads back 422 rows in an 844-row pane. The re-reading
663
- takes the element's own height, so the pane grows only for an element taller than the declared
664
- height, and then to that element's height, which is what its viewport lengths resolve against.
795
+ An element that fits with its native scroll clearance is shot in the declared pane, so its viewport
796
+ lengths resolve against the declared viewport: a `50vh` element under a 900-row block reads back 422
797
+ rows in an 844-row pane. A short fixed panel that native alignment cannot move keeps that pane too.
798
+ A captured sticky element with a vertical start inset keeps the declared pane when native end
799
+ alignment places its box fully inside it. Its own inset determines that fitted placement; adding
800
+ the document's start-clearance gap can otherwise grow a viewport-bound height on every restaging.
801
+ This exception belongs to the captured sticky element, not to in-flow content it contains or a
802
+ region containing sticky children. A sticky declaration without a vertical start inset retains
803
+ the ordinary clearance behavior.
804
+ For an element whose height plus clearance exceeds the declared height, native start alignment
805
+ resolves the clearance, and the pane reserves the element's height plus the space above its aligned
806
+ top. This includes an element shorter than the declared pane. It honors authored root scroll padding
807
+ and target scroll margins, including computed lengths, without
808
+ parsing CSS or hiding a sticky header. The captured image still contains only the element's box.
809
+ An overlay without authored clearance remains visible in the image; the instrument detects no
810
+ arbitrary occlusion. Viewport-dependent clearance participates in the same bounded settling loop,
811
+ so a frame whose geometry has not settled within that bound is refused.
665
812
  Where the element's box lies outside the pane, the document is scrolled by the nearest distance that
666
813
  would bring it inside, and the box is read again. A fixed element past the pane can take a scroll
667
- that does not move it, and that scroll is handed back with the rest. The document is not scrolled for
668
- an element already inside the pane.
814
+ that does not move it, and that scroll is handed back with the rest. Other short elements that need no
815
+ additional room keep their nearest native alignment.
669
816
 
670
817
  The provider paints an element that fits the runner's window only where that window shows it. Where
671
818
  such an element lies past the window, the `captureFrame` function offsets the calling tester frame up
@@ -694,8 +841,10 @@ pixels; a file with no PNG header is refused without a size.
694
841
 
695
842
  `createPortfolio` refuses an unregistered variant name at creation, so a run cannot write a filename
696
843
  naming a combination it did not render. A portfolio left un-`enabled` is the ordinary run: `place`
697
- resizes nothing, writes nothing, and records nothing, so a journey calls it unconditionally. An
698
- enabled `place` applies the variant and writes `<directory>/<state>--<variant>.png` through
844
+ resizes nothing, writes nothing, and records nothing, so a journey calls it unconditionally. It
845
+ still refuses an unregistered state and a second placement of one state, so a journey that names a
846
+ state wrongly fails the ordinary run rather than the first capture run; `placements` and `paths`
847
+ stay empty, because they answer for what was captured. An enabled `place` applies the variant and writes `<directory>/<state>--<variant>.png` through
699
848
  `captureFrame`, so it stages the pane at the variant's size, covers the whole document, verifies the
700
849
  written bytes, and records only a path that read back as this run's own frame. The tester comes back
701
850
  at the viewport it had before the placement, so a journey that places a state carries on at its own
@@ -719,6 +868,12 @@ collision refusal before writing.
719
868
 
720
869
  Imported from `@orkestrel/test/server`.
721
870
 
871
+ The provider command consumes the browser journey's resolved selector inside Vitest's tester frame.
872
+
873
+ | API | Kind | Signature | Summary |
874
+ | ------------- | -------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
875
+ | `chooseFiles` | function | `(context: ChooserContext, request: ChooserRequest) => Promise<void>` | Observes a native chooser before activating its input and supplies the verified chooser's fixture bytes. |
876
+
722
877
  #### Types
723
878
 
724
879
  A `Shape` cell holds an interface's data members as bare names in braces, `?` marking an optional
@@ -736,6 +891,7 @@ it adds after.
736
891
  | `InventoryOptions` | interface | `{ extensions?, exclude? }` | Configures a source inventory read. |
737
892
  | `UpgradeOptions` | interface | `WaitOptions` plus `{ path?, protocols? }` | Configures a client upgrade request. |
738
893
  | `UpgradeResult` | type | `{ claimed, protocol } \| { claimed, status }` | Represents what one server did with a client upgrade request. |
894
+ | `ChooserContext` | interface | `{ page, iframe }` | Supplies the real Playwright page and tester frame to a native chooser command. |
739
895
 
740
896
  #### Constants
741
897
 
@@ -903,6 +1059,24 @@ this package never reserves a port number.
903
1059
  The call-signature members of each behavioral interface. Their `readonly` data members stay in the
904
1060
  earlier [Surface](#surface) rows.
905
1061
 
1062
+ #### `SheetAdoption`
1063
+
1064
+ The adoption releases only the sheet it owns.
1065
+
1066
+ | Method | Signature | Summary |
1067
+ | --------- | ------------ | ----------------------------------------------------------------------- |
1068
+ | `release` | `() => void` | Removes this sheet from the document without removing another adoption. |
1069
+
1070
+ Release an adoption after the fixture has read its sheet.
1071
+
1072
+ ```ts
1073
+ import type { SheetAdoption } from '@orkestrel/test/browser'
1074
+ import { adoptSheet } from '@orkestrel/test/browser'
1075
+
1076
+ const adopted: SheetAdoption = adoptSheet('@layer example {}')
1077
+ adopted.release()
1078
+ ```
1079
+
906
1080
  #### `RecorderInterface`
907
1081
 
908
1082
  | Method | Returns | Summary |
@@ -1086,51 +1260,66 @@ what the link reaches, not on what it stores.
1086
1260
  Every message `src/browser` throws. Keep them distinct: a journey asserts the one it means, and
1087
1261
  absent, present-but-gated, and ambiguous are different findings about an interface.
1088
1262
 
1089
- | Voice | Thrown by |
1090
- | ---------------------------------------------------------------------------------------- | ------------------------- |
1091
- | `No interactive element has the accessible name "<name>"` | `resolveRendered` |
1092
- | `Interactive target "<name>" is not visible and focus-reachable` | `resolveRendered` |
1093
- | `Interactive target "<name>" is ambiguous across <n> elements` | `resolveRendered` |
1094
- | `Interactive target "<name>" could not be resolved` | `resolveRendered` |
1095
- | `Interactive target "<name>" is unreachable after scrolling` | `resolveAccessible` |
1096
- | `Interactive target "<name>" is not reachable inside "<region>"` | `resolveAccessibleWithin` |
1097
- | `Interactive target "<name>" is ambiguous across <n> elements inside "<region>"` | `resolveAccessibleWithin` |
1098
- | `Interactive target "<name>" could not be resolved inside "<region>"` | `resolveAccessibleWithin` |
1099
- | `Native disclosure "<name>" is not visible and focus-reachable` | `clickDisclosure` |
1100
- | `Native disclosure "<name>" is ambiguous across <n> elements` | `clickDisclosure` |
1101
- | `Native disclosure "<name>" could not be resolved` | `clickDisclosure` |
1102
- | `Interactive target "<name>" is not reachable through forward Tab traversal: <trail>` | `driveTraversal` |
1103
- | `Named region "<name>" is not visible` | `readPerception` |
1104
- | `Named region "<name>" is ambiguous across <n> elements` | `readPerception` |
1105
- | `Named region "<name>" could not be resolved` | `readPerception` |
1106
- | `Interactive target "<name>" does not carry a value` | `readValue` |
1107
- | `Computed foreground color is unavailable` | `readContrast` |
1108
- | `Computed background color is unavailable` | `readContrast` |
1109
- | `Tester pane is unavailable for a capture` | `stagePane` |
1110
- | `Tester pane rendered <w>x<h> for a <w>x<h> viewport` | `stagePane` |
1111
- | `Capture frame at <path> never settled after <n> restagings: <h> over a <h> pane` | `captureFrame` |
1112
- | `Capture frame was written to <path> where <path> was asked for` | `captureFrame` |
1113
- | `Capture frame at <path> is not the one this run shot` | `captureFrame` |
1114
- | `Capture frame at <path> could not be read` | `readFrame` |
1115
- | `Capture frame at <path> is not an image this browser decodes` | `readFrame` |
1116
- | `Capture frame at <path> is not an image this browser decodes: <w>x<h> device pixels` | `readFrame` |
1117
- | `Capture frame at <path> cannot be measured without a 2D canvas` | `readFrame` |
1118
- | `Capture variant "<name>" is not registered` | `createPortfolio` |
1119
- | `Capture state "<state>" is not registered` | `place` |
1120
- | `Capture state "<state>" is already placed` | `place` |
1121
- | `IndexedDB database "<name>" could not be deleted` | `removeDatabase` |
1122
- | `IndexedDB database "<name>" is blocked by an open connection` | `removeDatabase` |
1123
- | `Key sequence "<keys>" was sent with nothing focused` | `pressKeys` |
1124
- | `Condition "<subject>" did not hold within <n>ms (waited <n>ms) (last states: <states>)` | `waitForState` |
1125
- | `Animation subject is not connected` | `waitForAnimations` |
1126
- | `Animation "<subject>" did not settle within <n>ms (waited <n>ms): <animations>` | `waitForAnimations` |
1127
- | `Class census walked no element` | `readCensus` |
1128
- | `Contrast control cannot straddle the bar <bar>` | `buildContrast` |
1129
- | `Access is denied for <operation> "<key>"` | `buildDenial` |
1130
- | `No room is left for <key>` | `createStorage` |
1131
- | `Storage quota must be a non-negative integer` | `createStorage` |
1132
- | `Statechart harness mounted no transition` | `createHarness` |
1133
- | `Statechart harness carries no status` | `status` |
1263
+ | Voice | Thrown by |
1264
+ | ---------------------------------------------------------------------------------------- | ---------------------------------------------- |
1265
+ | `No interactive element has the accessible name "<name>"` | `resolveRendered` |
1266
+ | `Interactive target "<name>" is not visible and focus-reachable` | `resolveRendered` |
1267
+ | `Interactive target "<name>" is ambiguous across <n> elements` | `resolveRendered` |
1268
+ | `Interactive target "<name>" could not be resolved` | `resolveRendered` |
1269
+ | `Interactive target "<name>" is unreachable after scrolling` | `resolveAccessible` |
1270
+ | `Interactive target "<name>" is not reachable inside "<region>"` | `resolveAccessibleWithin` |
1271
+ | `Interactive target "<name>" is ambiguous across <n> elements inside "<region>"` | `resolveAccessibleWithin` |
1272
+ | `Interactive target "<name>" could not be resolved inside "<region>"` | `resolveAccessibleWithin` |
1273
+ | `Native disclosure "<name>" is not visible and focus-reachable` | `clickDisclosure` |
1274
+ | `Native disclosure "<name>" is ambiguous across <n> elements` | `clickDisclosure` |
1275
+ | `Native disclosure "<name>" could not be resolved` | `clickDisclosure` |
1276
+ | `The element is not connected to a document, so it has no point to read` | `readProtocolPoint` |
1277
+ | `Interactive target "<name>" is not reachable through forward Tab traversal: <trail>` | `driveTraversal` |
1278
+ | `Named region "<name>" is not visible` | `readPerception` |
1279
+ | `Named region "<name>" is ambiguous across <n> elements` | `readPerception` |
1280
+ | `Named region "<name>" could not be resolved` | `readPerception` |
1281
+ | `Interactive target "<name>" does not carry a value` | `readValue` |
1282
+ | `Computed foreground color is unavailable` | `readContrast` |
1283
+ | `Computed background color is unavailable` | `readColorContrast` |
1284
+ | `Color "<value>" is unreadable` | `readColorContrast` |
1285
+ | `Contrast opacity must be between 0 and 1` | `readColorContrast` |
1286
+ | `The element is not connected to a document, so it has no clipping ancestors to read` | `collectClippingAncestors`, `readClippedBlock` |
1287
+ | `Tester pane is unavailable for a capture` | `stagePane` |
1288
+ | `Tester pane rendered <w>x<h> for a <w>x<h> viewport` | `stagePane` |
1289
+ | `Capture frame at <path> never settled after <n> restagings: <h> over a <h> pane` | `captureFrame` |
1290
+ | `Capture frame was written to <path> where <path> was asked for` | `captureFrame` |
1291
+ | `Capture frame at <path> is not the one this run shot` | `captureFrame` |
1292
+ | `Capture frame at <path> could not be read` | `readFrame` |
1293
+ | `Capture frame at <path> is not an image this browser decodes` | `readFrame` |
1294
+ | `Capture frame at <path> is not an image this browser decodes: <w>x<h> device pixels` | `readFrame` |
1295
+ | `Capture frame at <path> cannot be measured without a 2D canvas` | `readFrame` |
1296
+ | `Shot is not an image this browser decodes` | `readShotPixel` |
1297
+ | `Shot cannot be measured without a 2D canvas` | `readShotPixel` |
1298
+ | `Shot pixel position must be a fraction from 0 to 1` | `readShotPixel` |
1299
+ | `Capture variant "<name>" is not registered` | `createPortfolio` |
1300
+ | `Capture state "<state>" is not registered` | `place` |
1301
+ | `Capture state "<state>" is already placed` | `place` |
1302
+ | `IndexedDB database "<name>" could not be deleted` | `removeDatabase` |
1303
+ | `IndexedDB database "<name>" is blocked by an open connection` | `removeDatabase` |
1304
+ | `Key sequence "<keys>" was sent with nothing focused` | `pressKeys` |
1305
+ | `Focus sits in a frame from another origin, which cannot be read` | `readActiveElement` |
1306
+ | `Focus traversal did not end` | `walkFocus` |
1307
+ | `The start element cannot take focus` | `collectTabStops` |
1308
+ | `Stability frames must be a positive integer` | `waitForStability` |
1309
+ | `Condition "<description>" did not hold within <n>ms (waited <n>ms)` | `waitForStability` |
1310
+ | `Condition "<subject>" did not hold within <n>ms (waited <n>ms) (last states: <states>)` | `waitForState` |
1311
+ | `Animation subject is not connected` | `waitForAnimations` |
1312
+ | `Animation "<subject>" did not settle within <n>ms (waited <n>ms): <animations>` | `waitForAnimations` |
1313
+ | `Class census walked no element` | `readCensus` |
1314
+ | `Contrast control cannot straddle the bar <bar>` | `buildContrast` |
1315
+ | `Access is denied for <operation> "<key>"` | `buildDenial` |
1316
+ | `No room is left for <key>` | `createStorage` |
1317
+ | `Storage quota must be a non-negative integer` | `createStorage` |
1318
+ | `Statechart harness mounted no transition` | `createHarness` |
1319
+ | `Statechart harness carries no status` | `status` |
1320
+
1321
+ `readContrast` measures through `readColorContrast`, so its background refusal is that row's voice,
1322
+ and `readRing` never raises it, because it names `CANVAS_COLOR` as the floor on every reading.
1134
1323
 
1135
1324
  Some of those rows are not plain `Error` messages. `buildDenial` returns a `DOMException` named
1136
1325
  `SecurityError`, `createStorage` raises that one from every operation the permission withholds and a
@@ -1144,7 +1333,8 @@ states it last read, so an exhausted wait says what the control was announcing i
1144
1333
  `waitForAnimations` names the subject through `readRole` and `readName` and lists the animations
1145
1334
  still running. Both validate their bounds through `checkBounds`, which raises
1146
1335
  `Wait budget must be finite and non-negative` for the first and `Animation budget must be finite and
1147
- non-negative` for the second.
1336
+ non-negative` for the second. `settleCapture` raises the `waitForAnimations` voices unchanged, and
1337
+ because it waits on the document's root element, an exhausted settle names `html` as its subject.
1148
1338
 
1149
1339
  Some of them are narrowing rather than findings, and no input reaches them. Each `could not be
1150
1340
  resolved` is one: a preceding length check does not narrow the later lookup under
@@ -1162,27 +1352,39 @@ disk disagrees with the bytes the provider handed back — which a provider that
1162
1352
  never produces, so the suite proves that comparison discriminates with a planted file rather than by
1163
1353
  reaching the refusal. `Capture frame at <path> cannot be measured without a 2D canvas` is narrowing
1164
1354
  of the same kind: a canvas allocated for this reading and asked for no other context type hands one
1165
- back. The remaining refusals of the `readFrame` function are driven, by a path holding no file, by a
1355
+ back. `readShotPixel` carries the same pair for a shot the provider hands back rather than a file:
1356
+ `Shot cannot be measured without a 2D canvas` is that narrowing, and `Shot is not an image this
1357
+ browser decodes` fires only where the provider returns bytes that are not the image it shot. Both
1358
+ readers decode and draw through `paintImage`, which hands the decode failure and a missing context
1359
+ on to the caller to word, so the decode is driven there with bytes that are no image. The
1360
+ `Shot pixel position must be a fraction from 0 to 1` refusal is driven, by a fraction below `0`,
1361
+ above `1`, and `NaN`. The remaining refusals of the `readFrame` function are driven, by a path holding no file, by a
1166
1362
  file holding no image, and by a PNG header over no image data, and so is
1167
1363
  `Capture frame at <path> never settled after <n> restagings`, by a fixture whose full-height panel
1168
1364
  grows with every pane the capture stages.
1169
1365
 
1170
1366
  The pointer, pseudo-element, and media helpers add the following voices.
1171
1367
 
1172
- | Voice | Thrown by |
1173
- | ------------------------------------------------------------- | -------------- |
1174
- | `Browser provider exposes no DevTools session` | `sendProtocol` |
1175
- | `Pointer is already held at <x>x<y>` | `driveHold` |
1176
- | `Interactive target "<name>" did not enter the pressed state` | `driveHold` |
1177
- | `Pseudo-element "<pseudo>" must start with "::"` | `readStyle` |
1178
- | `Pseudo-element "<pseudo>" is not one this engine exposes` | `readStyle` |
1179
- | `Media emulation was staged with nothing to emulate` | `stageMedia` |
1180
- | `Media emulation did not reach the tester: <query>` | `stageMedia` |
1181
- | `Media emulation did not clear from the tester` | `releaseMedia` |
1368
+ | Voice | Thrown by |
1369
+ | ------------------------------------------------------------- | ------------------ |
1370
+ | `Browser provider exposes no DevTools session` | `sendProtocol` |
1371
+ | `Pointer is already held at <x>x<y>` | `driveHold` |
1372
+ | `Interactive target "<name>" did not enter the pressed state` | `driveHold` |
1373
+ | `Protocol search returned no search id` | `readProtocolNode` |
1374
+ | `Protocol search found <n> elements where one is required` | `readProtocolNode` |
1375
+ | `Protocol search returned no node id` | `readProtocolNode` |
1376
+ | `Pseudo-element "<pseudo>" must start with "::"` | `readStyle` |
1377
+ | `Pseudo-element "<pseudo>" is not one this engine exposes` | `readStyle` |
1378
+ | `Media emulation was staged with nothing to emulate` | `stageMedia` |
1379
+ | `Media emulation did not reach the tester: <query>` | `stageMedia` |
1380
+ | `Media emulation did not clear from the tester` | `releaseMedia` |
1182
1381
 
1183
1382
  The resolver's absent, gated, ambiguous, and unreachable voices stay unchanged. The DevTools-session
1184
1383
  and media-delivery refusals guard provider failures the managed Chromium suite doesn't induce.
1185
1384
  The suite exercises real delivery rather than substituting a provider that manufactures a failure.
1385
+ `readAccessibilityNode` raises the `readProtocolNode` voices unchanged. Of those, `found <n>
1386
+ elements` is driven, by a detached element. The other two narrow a reply that omits `searchId` or
1387
+ `nodeIds`, which the browser never sends, so they guard a provider that does.
1186
1388
 
1187
1389
  ### Refusals outside the journey layer
1188
1390
 
@@ -1404,8 +1606,9 @@ These hold across `src/core`, `src/browser`, `src/server`, and this guide.
1404
1606
  predicates, the element readers, and the describers do take a node —
1405
1607
  `isRendered`, `isReachable`, `readHit`, `readText`, `readRole`, `readName`, `readStates`,
1406
1608
  `readCensus`, `describeTree`, `describeFocus`, `extractOrphans`, `readRows`, `readStyle`,
1407
- `readToken`, `readPixels`, `readContrast`, `readLayers`, `readBackdrop`, and `readRing` — and
1408
- each is a reader of a node the caller already has rather than a verb that acts on a target.
1609
+ `readToken`, `readPixels`, `readContrast`, `readColorContrast`, `readLayers`, `readBackdrop`,
1610
+ and `readRing` — and each is a reader of a node the caller already has rather than a verb that
1611
+ acts on a target.
1409
1612
  `waitForAnimations` takes one as the subject of a wait, and waiting for a browser to stop
1410
1613
  painting it changes nothing about it. `buildContrast`, `buildEscapes`, and `buildCensus` return
1411
1614
  detached nodes for the caller to append and remove, the way `build` does. `captureFrame`
@@ -1526,7 +1729,8 @@ These hold across `src/core`, `src/browser`, `src/server`, and this guide.
1526
1729
  the one before it produced so a converging document lands on its fixed point rather than
1527
1730
  creeping toward it. The re-reading is bounded by `CAPTURE_STAGINGS`, and a document still
1528
1731
  growing at that bound is refused rather than photographed at a stale height. An element frame
1529
- keeps the declared pane unless its element is taller, and offsets the calling tester frame only
1732
+ keeps the declared pane unless its element needs more room with its native scroll clearance,
1733
+ and offsets the calling tester frame only
1530
1734
  as far as brings the element inside the runner's window. What
1531
1735
  the capture borrows it gives back: the `releasePane` function returns the tester to the viewport
1532
1736
  it held before the staging, the capture then restores the tester's scroll position, even where
@@ -1553,6 +1757,13 @@ These hold across `src/core`, `src/browser`, `src/server`, and this guide.
1553
1757
  still read `running` would strand the gate on a reading the harness never leaves. The gate stays
1554
1758
  outside this package: the harness carries its own tally, so nothing here reads a harness back,
1555
1759
  and no page is generated or published to host one.
1760
+ 20. **Sheet adoption releases only its own stylesheet.** Repeated release leaves other adoptions
1761
+ intact. [`tests/src/browser/sheets/helpers.test.ts`](../tests/src/browser/sheets/helpers.test.ts)
1762
+ proves independent adoptions and repeated release.
1763
+ 21. **File drags carry trusted `Files` events to rendered targets.** A destination need not be
1764
+ focusable. The file-drag cases in
1765
+ [`tests/src/browser/journeys/helpers.test.ts`](../tests/src/browser/journeys/helpers.test.ts)
1766
+ prove trusted entry, over, and leave events over a status outside the Tab order.
1556
1767
 
1557
1768
  ### Threat model
1558
1769
 
@@ -1706,16 +1917,56 @@ the helper rather than to the host, and each names what to reach for instead.
1706
1917
  used; it is covered by review. The measured layout is a single accessible, uniformly scaled tester
1707
1918
  iframe. A covered centre or unsupported geometry can refuse even when the resolver accepts the
1708
1919
  target. The verb inherits the resolver's focus reachability conditions.
1920
+ - **`parkPointer` ends hover and never ends a press.** It moves the pointer to (-1, -1) and waits for
1921
+ a frame, and it sends no button-up, so a press that the `holdAccessible` verb holds stays recorded under
1922
+ `POINTER_HOLD` and its button-up is still owed to `releasePointer`. The control the press was on
1923
+ loses both its `:hover` paint and its `:active` paint when the pointer leaves it. Use it where a case clears hover with no
1924
+ press held, and never as a teardown for a hold.
1709
1925
  - **`releasePointer` parks the pointer outside the page.** It releases at the recorded point first,
1710
- which can produce a click. It then clears hover by moving to (-1, -1) in the runner page's
1711
- coordinates, one pixel above and to the left of that page's viewport. The browser hit-tests
1712
- nothing outside the viewport, so no element takes a `mouseover` event or hover paint from the
1713
- parked pointer until the next pointer verb. This holds even where a staging, scroll, or offset
1714
- lays content over the park point. Register it with `afterEach` before a hover
1926
+ which can produce a click. It then clears hover through `parkPointer`, which moves to (-1, -1) in
1927
+ the runner page's coordinates, one pixel above and to the left of that page's viewport. The
1928
+ browser hit-tests nothing outside the viewport, so no element takes a `mouseover` event or hover
1929
+ paint from the parked pointer until the next pointer verb. This holds even where a staging,
1930
+ scroll, or offset lays content over the park point. Register it with `afterEach` before a hover
1715
1931
  or hold. A second release sends no button-up event. Holds must not overlap. A rejected press
1716
1932
  leaves no marker. A rejected release keeps the marker for a retry and still attempts the park. If
1717
1933
  that park also rejects, the park's error surfaces with the release rejection attached in the
1718
1934
  aggregate's errors; the park rejection is its cause.
1935
+ - **`walkFocus` reports every stop it can read and refuses none of them.** It records the focused
1936
+ element and then presses Tab through `stepFocus`, the one Tab loop that `driveTraversal` shares,
1937
+ and returns what focus reached, so a focusable scroll container that Chromium focuses without a
1938
+ `tabindex` is a stop, and `describeFocus` omits it. Each stop is the innermost element
1939
+ `readActiveElement` reads, so a control inside an open shadow root or a same-origin frame is a stop
1940
+ of its own and its host or frame element is not. A non-HTML control inside one, such as an SVG
1941
+ element, reads as `undefined` rather than as its host, and `readFocus` follows it. A closed shadow root cannot be entered, so its
1942
+ host stands for what it holds, and focus in a frame from another origin throws, because the page
1943
+ cannot name a control there. A hidden or disabled stop is returned like any other. A consumer that
1944
+ refuses one throws from the predicate it passes. The walk ends where focus returns to the body or
1945
+ revisits a stop, and throws only when it outlasts the count `countElements` reads, which includes
1946
+ open shadow roots and same-origin frames. A control inside a same-origin frame belongs to the
1947
+ frame's window, so it fails this window's `instanceof HTMLElement`. `collectTabStops` is the same
1948
+ walk bounded to a region by `containsNode`, which places a control inside an open shadow root or a
1949
+ same-origin frame in the region that holds its host or frame element. The start element is the
1950
+ walk's origin and never its end, so a start immediately before the region, outside it, reaches the
1951
+ region's first stop with the first press.
1952
+ - **`waitForStability` compares the last `frames + 1` reads.** The first read is immediate and each
1953
+ later read follows a painted frame, with the interval slept between polls, so the reads are not on
1954
+ consecutive frames when the interval is longer than one. A reading that settles on read `n`
1955
+ resolves on read `n + frames`. The default is four frames; the budget and interval are those of
1956
+ `waitForCondition`. A reading that changes on every read exhausts the budget, and the refusal names
1957
+ the description. The budget and the abort signal are checked between reads, so a frame that never
1958
+ paints is not bounded by either. The `releaseMedia` function with nothing staged waits through it
1959
+ for one frame.
1960
+ - **`settleCapture` holds endless animations until its resume runs.** It parks the pointer through
1961
+ `parkPointer`, so a held press stays held and only loses its hover and pressed paint; `pointer: false`
1962
+ leaves the pointer, its hover, and that paint where they are. It waits out every finite animation
1963
+ in the document, root element included, then pauses each endless one at its first frame and
1964
+ resolves to the resume. Call the resume from a `finally` block once the frame is shot. A settle
1965
+ whose wait exhausts throws before it holds anything, so it owes no resume.
1966
+ - **`readShotPixel` reads a shot at the runner's scale.** The position is a fraction of the shot,
1967
+ and a fraction of `1` reads its last row or column. Where the runner scales the tester, the shot's
1968
+ edge pixels can blend with what lies beside the element, so read a pixel inside a painted band rather
1969
+ than on its edge.
1719
1970
  - **`stageMedia` overrides the provider page, and `releaseMedia` restores the first stage's
1720
1971
  readings.** Pin a base with `{ motion: true }` rather than assuming the host prefers motion.
1721
1972
  `motion: false` stages reduced motion; `print: true` stages print, and `print: false` stages
@@ -1724,28 +1975,70 @@ the helper rather than to the host, and each names what to reach for instead.
1724
1975
  other emulated feature the provider configured is cleared. An empty `{}`
1725
1976
  refuses. Each staged query has a 1000 ms read-back budget with a 10 ms poll interval; refusal
1726
1977
  restores the carried pre-call readings before throwing and can take two budgets. A restoration
1727
- failure is attached as the refusal's cause. The read-back exhaustion path can't be driven from
1728
- inert input against a conforming engine; its restoration is covered by review and by the shared
1729
- payload code, not by a case. The first stage records print, reduced motion, dark colour scheme,
1730
- and forced colours on the tester root under `MEDIA_STAGE`, as a bit string in that order (`1` for
1731
- a match, `0` otherwise). Later stages keep that marker. Release restores those readings as
1732
- explicit emulation rather than removing every override, waits per axis for its recorded value, and
1733
- removes the marker. A release whose wait exhausts keeps `MEDIA_STAGE` on the tester root, so the
1734
- next `releaseMedia` retries from the same recorded readings. A release with nothing staged clears
1735
- every override and compares readings taken strictly after the reset until they are stable.
1978
+ failure is attached as the refusal's cause. The pointer refusal proof passes an unsupported runtime
1979
+ pointer value through the JavaScript boundary and checks restoration after read-back exhaustion.
1980
+ The first stage records print, reduced motion, dark colour scheme, and forced colours on the
1981
+ tester root under `MEDIA_STAGE`, as a bit string in that order (`1` for a match, `0` otherwise).
1982
+ The first pointer stage appends a fifth bit for the pre-call coarse-pointer reading; later pointer
1983
+ stages retain it. A stage without a pointer keeps the four-bit marker until a pointer stage occurs.
1984
+ Release restores those readings as explicit emulation rather than removing every override, waits
1985
+ per axis for its recorded value, and removes the marker. A release whose wait exhausts keeps
1986
+ `MEDIA_STAGE` on the tester root, so the next `releaseMedia` retries from the same recorded
1987
+ readings. A release with nothing staged clears media overrides and compares readings taken
1988
+ strictly after the reset until they are stable.
1736
1989
  Stability doesn't prove the engine's own baseline. Each release read-back has a 1000 ms budget and
1737
1990
  waits for a frame per poll, with 10 ms between polls. The budget is checked between polls, so a
1738
1991
  frame that never paints is not bounded by it. Media scopes must not overlap. The browser project
1739
1992
  runs files serially.
1993
+ - **Pointer emulation also changes hover.** `pointer: 'coarse'` enables touch emulation with one
1994
+ touch point and reads `(pointer: coarse)`, `(any-pointer: coarse)`, `(hover: none)`, and
1995
+ `(any-hover: none)`. `pointer: 'fine'` disables touch emulation and reads fine precision with
1996
+ hover support. A pointer stage and its release preserve precision, not an earlier maximum
1997
+ touch-point count. Staging and releasing media without a pointer axis sends no touch-emulation
1998
+ command, including an unstaged release.
1999
+ - **Touch input requires a Chromium protocol session.** `tapTouch` enables touch emulation, waits
2000
+ for the touch layout's box to settle, then sends a single-point start and end. It does not scroll
2001
+ or check focus reachability. A tap that
2002
+ stops a scroller's momentum can produce no click. `releaseTouch` tolerates Chromium's idle
2003
+ cancellation refusal, disables emulation, and parks without sending a mouse release. Parking
2004
+ does not prove that sticky touch hover cleared. A native drag holding the mouse needs its own
2005
+ `mouseReleased` protocol command before parking. Touch cleanup also disables a coarse pointer
2006
+ staged by `stageMedia`, leaving `(pointer: fine)` while `MEDIA_STAGE` stays unchanged. Stage
2007
+ `pointer: 'coarse'` again after cleanup when later steps need it. `releaseMedia` still restores
2008
+ the reading recorded before the first pointer stage. A first pointer stage while tap emulation
2009
+ is live records coarse. In teardown, call `releaseMedia` before `releaseTouch`. The reverse
2010
+ order restores coarse emulation with one touch point after touch cleanup.
2011
+ - **File cancellation must leave the target first.** Chromium raises no `dragleave` for protocol
2012
+ cancellation alone. `sendFileDrag` first moves outside the page. It writes named fixture files
2013
+ under `tmp/drag`, refuses names containing a path, and sends no drop or file-content delivery.
2014
+ - **Layer names follow declaration order at every depth.** `readLayerNames` descends through
2015
+ grouping and style rules, qualifying each statement and block by its enclosing layer. It includes
2016
+ anonymous layers and repeated names, and reads conditions whether or not they match the document.
2017
+ An anonymous nested layer contributes a trailing dot to its enclosing qualified name.
2018
+ An `@import ... layer()` rule contributes nothing, and neither do its imported sheet's rules.
2019
+ A dotted declaration such as `@layer a.b` contributes `a.b` as declared, without an implicit `a`.
2020
+ Distinct anonymous layers produce the same names; the result does not distinguish their identities.
1740
2021
  - **`readStyle` refuses a pseudo argument rather than ignoring it.** The argument must start with
1741
2022
  `::` before the engine's selector-support check runs. A supported pseudo-element with no value for
1742
2023
  the requested property still reads as an empty string. `readPixels` uses the same guard.
1743
2024
  - **`sendProtocol` reaches a Chromium-family provider alone.** It needs the provider's DevTools
1744
- session, passes the named command and parameters through, and discards the response. Browser
1745
- termination cannot promise cleanup. Edge observations belong to a separate run.
2025
+ session, passes the named command and parameters through, and resolves the reply as `unknown`.
2026
+ Narrow the reply with guards before reading a field, and omit the parameters for a command that
2027
+ takes none. Browser termination cannot promise cleanup. Edge observations belong to a separate run.
2028
+ It resolves the reply since 0.0.27, where 0.0.26 resolved nothing. A callback typed to return
2029
+ `void | Promise<void>`, such as a teardown or an `onTestFinished` hook, that returns
2030
+ `sendProtocol(…)` directly no longer type-checks. Write `async () => { await sendProtocol(…) }`.
2031
+ - **`readProtocolPoint` reads the layout now, and `readProtocolNode` hands back an id to use at
2032
+ once.** The point is the layout at the call, so a box that moves afterwards moves the point it
2033
+ named. Each frame on the way up adds its border and its padding, because its content starts inside
2034
+ both. An element no document holds is refused, because its window still names the tester frame and
2035
+ the call would otherwise answer that frame's corner. A node id comes from a document the browser is asked for afresh on every call, so use it
2036
+ before the next call. Both leave `readHit` its own question, which is what the element's own
2037
+ document hit-tests at the centre and never leaves that document.
1746
2038
  - **`parseCSSColor` resolves an undeclared token to the inherited color.** A `var()` naming a custom
1747
2039
  property nothing declares is not a parse failure: the cascade accepts it and computes the
1748
- inherited color, so `parseCSSColor('var(--absent)')` hands back channels rather than `undefined`.
2040
+ inherited color, which is the body's, or the `scope` element's with one, so
2041
+ `parseCSSColor('var(--absent)')` hands back channels rather than `undefined`.
1749
2042
  Read `readToken` or `readRootToken` where a missing token is the subject.
1750
2043
  - **`createLoopback` cannot take back an upgraded socket.** A server that claims an upgrade keeps
1751
2044
  that connection, detached from the server itself, so `destroy()`'s `closeAllConnections` never
@@ -2273,7 +2566,7 @@ browser suite runs. It is a native disclosure with two doors: the summary toggle
2273
2566
  button closes it and does nothing when it is already closed. That second door is what gives the
2274
2567
  table a row whose event leaves the state where it found it, which a lone `<details>` cannot have —
2275
2568
  its one event always flips. Every value these fences claim is pinned in
2276
- `tests/src/browser/factories.test.ts`, because the fences drive a browser and the `guides` project
2569
+ `tests/src/browser/journeys/factories.test.ts`, because the fences drive a browser and the `guides` project
2277
2570
  runs with the browser disabled.
2278
2571
 
2279
2572
  The phases are module functions the whole table shares, which is the shape a table of any size
@@ -2547,11 +2840,11 @@ Object.keys(
2547
2840
  // 'src/core/types.ts', 'src/core/validators.ts']
2548
2841
 
2549
2842
  // A directory key takes every key below it.
2550
- Object.keys(readInventory(root, ['src'], { extensions: ['.ts'], exclude: ['src/server'] }))
2551
- // ['src/browser/constants.ts', 'src/browser/factories.ts', 'src/browser/helpers.ts',
2552
- // 'src/browser/index.ts', 'src/browser/types.ts', 'src/core/constants.ts',
2553
- // 'src/core/factories.ts', 'src/core/helpers.ts', 'src/core/index.ts', 'src/core/types.ts',
2554
- // 'src/core/validators.ts']
2843
+ Object.keys(
2844
+ readInventory(root, ['src'], { extensions: ['.ts'], exclude: ['src/server', 'src/browser'] }),
2845
+ )
2846
+ // ['src/core/constants.ts', 'src/core/factories.ts', 'src/core/helpers.ts', 'src/core/index.ts',
2847
+ // 'src/core/types.ts', 'src/core/validators.ts']
2555
2848
 
2556
2849
  // An exclusion also applies to a target you name, so naming one file below an excluded directory
2557
2850
  // does not reinstate it.
@@ -2907,6 +3200,41 @@ await traverseAccessible('Evaluate')
2907
3200
  readPerception('Run') // one visible named region, whitespace collapsed, hidden-but-read text kept
2908
3201
  ```
2909
3202
 
3203
+ #### Tap with one touch
3204
+
3205
+ Resolve the element the fixture supplies, then release touch emulation in cleanup.
3206
+
3207
+ ```ts
3208
+ import { releaseTouch, resolveRendered, tapTouch } from '@orkestrel/test/browser'
3209
+
3210
+ try {
3211
+ await tapTouch(resolveRendered('Tap target'))
3212
+ } finally {
3213
+ await releaseTouch()
3214
+ }
3215
+ ```
3216
+
3217
+ The browser proof records one trusted touch click and no click on the sibling control.
3218
+ Pass a trailing stability-options object when the fixture needs a longer settling budget.
3219
+
3220
+ #### Drag files from outside the page
3221
+
3222
+ A rendered status receives the drag even when it is absent from the Tab order.
3223
+
3224
+ ```ts
3225
+ import { sendFileDrag } from '@orkestrel/test/browser'
3226
+
3227
+ await sendFileDrag('dragEnter', 'status', 'Drop status', ['note.txt'])
3228
+ try {
3229
+ await sendFileDrag('dragOver', 'status', 'Drop status', ['note.txt'])
3230
+ } finally {
3231
+ await sendFileDrag('dragCancel', 'status', 'Drop status', ['note.txt'])
3232
+ }
3233
+ ```
3234
+
3235
+ The browser proof observes trusted entry, over, and leave events carrying the `Files` type.
3236
+ No drop occurs; fixture files remain under `tmp/drag` for the caller to remove.
3237
+
2910
3238
  ### Hold a control and read the pressed paint
2911
3239
 
2912
3240
  Register pointer cleanup before a test can fail. This fixture declares `padding-top: 16px` on its
@@ -2982,6 +3310,21 @@ matchMedia('(prefers-reduced-motion: reduce)').matches === reduced // true
2982
3310
  readPixels(button, 'padding-top') === (reduced ? 2 : 1) // true
2983
3311
  ```
2984
3312
 
3313
+ Pointer precision uses touch emulation. These readings are proved in the browser media cases.
3314
+
3315
+ ```ts
3316
+ import { releaseMedia, stageMedia } from '@orkestrel/test/browser'
3317
+
3318
+ await stageMedia({ pointer: 'coarse' })
3319
+ matchMedia('(pointer: coarse)').matches // true
3320
+ matchMedia('(any-pointer: coarse)').matches // true
3321
+ matchMedia('(hover: none)').matches // true
3322
+ await stageMedia({ pointer: 'fine' })
3323
+ matchMedia('(pointer: fine)').matches // true
3324
+ matchMedia('(hover: hover)').matches // true
3325
+ await releaseMedia()
3326
+ ```
3327
+
2985
3328
  ### Send a key to what holds focus
2986
3329
 
2987
3330
  Bring focus about through a verb, then send the sequence. A key sent while the document body holds
@@ -3452,6 +3795,16 @@ await portfolio.place('start-empty')
3452
3795
  portfolio.place('answer-partial') // rejects: Capture state "answer-partial" is not registered
3453
3796
  ```
3454
3797
 
3798
+ Settle the page before each frame of a capture run. `settleCapture` parks the pointer, waits out
3799
+ every finite animation in the document, and holds each endless one at its first frame, so a hover,
3800
+ a transition caught part way, and a spinner's phase stay out of the picture. It resolves to the
3801
+ resume; call `place` after it, and call the resume from a `finally` block, so the held animations
3802
+ run again whether or not the frame was written. Pass `pointer: false` where the pointer is part of
3803
+ the state, such as a drag in flight. Gate the settle on the flag that `enabled` takes: an ordinary run's
3804
+ `place` shoots nothing, and parking the pointer there changes the journey that run is proving.
3805
+ Where a frame holds a scroll region, record `describeScrollRegions` of what `collectScrollRegions`
3806
+ finds beside it, because a picture does not show how far a pane scrolls.
3807
+
3455
3808
  ### Measure a document's content edge
3456
3809
 
3457
3810
  A capture stages the pane at the height the document needs, and the body's box cannot answer for
@@ -3557,7 +3910,11 @@ Each entry names the contracts its file proves. The test names carry the cases.
3557
3910
  carrying both the arrival and the departure that `absent` waits past, an empty expectation and an
3558
3911
  empty departure each refused, a reader throw propagated unchanged, the timeout naming the wait and
3559
3912
  the budget, an abort rejecting with the signal's own reason, and a refused bound raised through the
3560
- family. `decodeJSONLines` takes empty input, a trailing newline, CRLF,
3913
+ family. `collectSelectorClasses` takes a selector list whose functional pseudo-classes nest class
3914
+ names beside an attribute block, quoted strings, and a comment that name none, every escape form
3915
+ decoded with the out-of-range points read as the replacement character, names opening with an
3916
+ underscore, a hyphen, two hyphens, and a non-ASCII letter, the empty selector, and each unreadable
3917
+ text refused by name. `decodeJSONLines` takes empty input, a trailing newline, CRLF,
3561
3918
  line order, primitive lines, and a malformed physical line named with the native `SyntaxError` as
3562
3919
  its cause. `collect` and `collectStream` drain an empty and an ordered source, and the stream's
3563
3920
  reader lock is released afterwards. `roundTripJSON` takes a copy of a flat and a nested
@@ -3589,9 +3946,22 @@ Each entry names the contracts its file proves. The test names carry the cases.
3589
3946
  that is called empty and called twice. `createHostileValues` proves a naive-reader failure for
3590
3947
  every member, frozen and fresh membership, and one total guard's benign and hostile answers with
3591
3948
  loop-index attribution.
3592
- - [`tests/src/browser/helpers.test.ts`](../tests/src/browser/helpers.test.ts) — the journey-layer,
3593
- role-map, and announced-half-and-clickable-half contracts across the layer, in real Chromium
3594
- against constructed markup. The resolver takes a bare name, a
3949
+ - [`tests/src/browser/helpers.test.ts`](../tests/src/browser/helpers.test.ts),
3950
+ [`tests/src/browser/journeys/helpers.test.ts`](../tests/src/browser/journeys/helpers.test.ts),
3951
+ [`tests/src/browser/captures/helpers.test.ts`](../tests/src/browser/captures/helpers.test.ts),
3952
+ [`tests/src/browser/colors/helpers.test.ts`](../tests/src/browser/colors/helpers.test.ts), and
3953
+ [`tests/src/browser/sheets/helpers.test.ts`](../tests/src/browser/sheets/helpers.test.ts) — the
3954
+ journey-layer, role-map, and announced-half-and-clickable-half contracts across the layer, in real Chromium
3955
+ against constructed markup. The touch proofs cover a pointer-dependent target beside a sibling,
3956
+ a target moving across painted frames before settling, a supplied stability budget that refuses
3957
+ the tap, and both orders of media and touch teardown. The pointer proof restores the first coarse
3958
+ reading after fine then coarse staging. The file-drag proofs reach a target under a smooth root
3959
+ and refuse a file name without scrolling or writing any file in the batch. The `releaseTouch`
3960
+ rethrow of a non-idle protocol error is covered by review: its cancellation parameters are fixed,
3961
+ and no controllable non-idle refusal was found through the browser provider without replacing
3962
+ its transport or disrupting the tester session. A provider with a recoverable protocol-failure
3963
+ seam would let a proof drive that branch.
3964
+ The resolver takes a bare name, a
3595
3965
  role that disambiguates a tab from its own panel, a name no element carries, a name carried only by
3596
3966
  a role outside `ACCESSIBLE_ROLES`, and the disabled, hidden, and inert matches that are present but
3597
3967
  gated. It takes the glyph cases against a fixture stylesheet that really paints one: an exact name
@@ -3623,7 +3993,17 @@ Each entry names the contracts its file proves. The test names carry the cases.
3623
3993
  reading, and a refused bound. `waitForAnimations` takes a descendant transition awaited to its end,
3624
3994
  the exhaustion naming the subject and the animation still running, an infinite-iteration animation
3625
3995
  excluded while it is still turning, a detached subject refused, and a refused bound; its settled
3626
- reading is compared against the interpolated one taken mid-transition. `readRefusal` takes the
3996
+ reading is compared against the interpolated one taken mid-transition. `readAnimations` takes a
3997
+ running transition and a running infinite animation split apart, a paused animation and one
3998
+ outside the subject left out, and a detached element read as two empty lists. `settleCapture`
3999
+ takes a hovered control's transition waited out and a spinner held at its first frame until the
4000
+ resume sets it running, a held press whose hover and pressed paint `pointer: false` leaves in
4001
+ place beside the default park that takes both away, and an exhausted wait refused with nothing
4002
+ held. `collectScrollRegions` takes a vertical and a horizontal scroller beside a box whose content
4003
+ fits, a clipping box, and a hidden one, each axis alone, and a scrolling root counted itself;
4004
+ `describeScrollRegions` takes a region named by its tag, its id, and its cut accessible name with
4005
+ its offsets after a scroll, the window's position after a scroll, and an empty population.
4006
+ `readRefusal` takes the
3627
4007
  absent, gated, and ambiguous voices beside a target that resolves under a bare name and under a
3628
4008
  role, and — as the control for the rethrow — a fixture element whose own `tabIndex` getter refuses
3629
4009
  with a string, handed straight back. `readCensus` takes a population carrying both undeclared
@@ -3650,8 +4030,23 @@ Each entry names the contracts its file proves. The test names carry the cases.
3650
4030
  region an `aria-hidden` attribute withholds, and its not-visible and ambiguous refusals, `readPage`
3651
4031
  the whole page as one
3652
4032
  normalized sentence, and
3653
- `readFocus` a focused control's rendered text, a focused element that renders none, and nothing
3654
- holding focus at all; `readValue` takes a rendered value and a control carrying none. The element
4033
+ `readFocus` a focused control's rendered text, a focused element that renders none, an SVG element
4034
+ focused inside an open shadow root that reads nothing rather than its host's text, a host holding
4035
+ focus with nothing inside, and nothing holding focus at all; `readValue` takes a rendered value
4036
+ and a control carrying none. The focus
4037
+ walk takes its inputs across each boundary: `readActiveElement` takes the page's focused control,
4038
+ focus inside an open shadow root and through nested ones, an SVG element focused inside one that
4039
+ answers nothing rather than its host, a host holding focus with nothing inside
4040
+ and one with a closed root, a control inside a same-origin frame typed for the frame's window, a
4041
+ frame holding focus with nothing inside, and a frame from another origin refused;
4042
+ `countElements` takes the shadow and frame content a plain query misses; `containsNode` takes a
4043
+ region's own content, a shadow root's and a frame's, and a detached node left outside;
4044
+ `stepFocus` takes one press per landing, none past the consumer's last read, a limit of zero, and
4045
+ a landing on nothing. `walkFocus` and `collectTabStops` take the buttons of an open shadow root
4046
+ and of a same-origin frame between two light controls, a start element inside a shadow root, a
4047
+ start element immediately before the region that reaches the region's first stop, and
4048
+ focus in a foreign frame refused; `readProtocolPoint` takes a padded frame beside a bordered one,
4049
+ both hit-tested through the protocol, and a disconnected element refused. The element
3655
4050
  readers follow: `readText` takes an `aria-hidden` glyph dropped with the runs around it collapsed
3656
4051
  and an element with no text at all; `readRole` takes exactly the tags `IMPLICIT_ROLES` carries and
3657
4052
  one it leaves out, a declared role taken over the implicit one, a section made a region only by
@@ -3676,7 +4071,12 @@ Each entry names the contracts its file proves. The test names carry the cases.
3676
4071
  nothing paints and one whose every painted layer is translucent, a stack 64 translucent layers
3677
4072
  deep whose composite has rounded to the canvas's own channels, a detached element whose computed
3678
4073
  foreground does not exist, and the same unpainted and translucent stacks measured against a
3679
- supplied floor instead of refused. The color leaves beneath it take their own inputs: `parseColor`
4074
+ supplied floor instead of refused. `readColorContrast` takes the ceiling, the floor, a clear
4075
+ color, and a half-opaque one over an opaque surface, a keyword and a `var()` reference resolved
4076
+ through the cascade, a translucent and an unpainted stack refused until a floor is named and then
4077
+ composited onto it, an `opacity` reading equal to the declared alpha it multiplies with the
4078
+ refused factors beside it, and an unreadable color and an unreadable painted layer each refused.
4079
+ The color leaves beneath it take their own inputs: `parseColor`
3680
4080
  across the legacy and modern syntaxes, a refused keyword, hex triple, empty value, and unsupported
3681
4081
  color space, and — as the control the literals cannot supply — what this browser actually computes
3682
4082
  for a keyword and for a `color-mix()`. `blendColor`, `measureLuminance`, and `measureContrast`
@@ -3687,7 +4087,8 @@ Each entry names the contracts its file proves. The test names carry the cases.
3687
4087
  composited onto it, and an opaque layer that ends the walk. `readRing` takes a painted outline and
3688
4088
  a painted box-shadow reached through `traverseAccessible`, a control that is not focused, a
3689
4089
  focused control left the browser's own ring, a focus style that only repaints the control's fill,
3690
- and a `worn` element whose reading separates from the control's own. `measureContent` takes a
4090
+ a `worn` element whose reading separates from the control's own, and an unpainted focused control
4091
+ over an unreadable surface that is refused. `measureContent` takes a
3691
4092
  1600-row document read under an 844 pane and under a 2356 one, against the scroll height that
3692
4093
  agrees with it under the shorter pane and stretches to the pane under the taller, and a document
3693
4094
  whose last element carries a bottom margin under a body carrying bottom padding, where a reading
@@ -3717,7 +4118,10 @@ Each entry names the contracts its file proves. The test names carry the cases.
3717
4118
  height on the panel's own color with the scroll handed back; an element below both the pane and
3718
4119
  the window, whole on the document's floor with the scroll handed back; a `50vh` element and a
3719
4120
  `30vh` element below the fold, at the declared pane rather than a grown one; an element taller
3720
- than the pane, in a pane of its own height; an element that outgrows every pane, refused with the
4121
+ than the pane, in a pane of its own height; tall and near-viewport elements below authored native
4122
+ clearance, with decoded heading pixels distinguishing them from an undeclared sticky obstruction;
4123
+ a fixed element beyond a padded viewport that keeps its declared viewport height;
4124
+ an element that outgrows every pane, refused with the
3721
4125
  pane and the scroll handed back; a second tester frame the offset leaves in place; and a frame
3722
4126
  carrying no `style` attribute, handed back without one. After the `releasePointer` function, an
3723
4127
  element frame takes no `mouseover` event for an element inside the window, which also takes no `scroll` event;
@@ -3736,8 +4140,31 @@ Each entry names the contracts its file proves. The test names carry the cases.
3736
4140
  frame's size and floor, read a second way through the cascade's own answer for the same canvas, a
3737
4141
  bottom row split between two colors reported as no floor at all, a path holding no file,
3738
4142
  a file holding no image and refused without a size, and a PNG header over no image data, refused
3739
- with the size the header declares. The `readCascade` function takes class tokens collected from
3740
- plain and grouped rules and only real ones;
4143
+ with the size the header declares. The `paintImage` function takes a PNG painted at its natural
4144
+ size and bytes that are no image, whose decode failure it hands on unchanged. The `readShotPixel`
4145
+ function takes each half of a split shot, frozen, and both corners clamped onto the shot, and
4146
+ refuses a fraction below `0`, above `1`, infinite, or `NaN` on either axis. The `readClippedBlock` function takes a frame
4147
+ clipping a taller child, a paint containment and a clip margin, elements wholly under a frame's
4148
+ edge, an unclipped control, a block in an open shadow tree under a clipping wrapper around its
4149
+ host, a slotted child under a clipping wrapper around its slot, a disconnected element refused,
4150
+ and the same reading on a page scrolled past its top, which a reading
4151
+ that skipped the coordinate conversion fails. The `collectClippingAncestors` function takes the
4152
+ same climb through a host and through a slot, nearest first, and a disconnected element refused.
4153
+ The `measureContent` function takes a slotted child ended at the shadow wrapper that clips around
4154
+ its slot. The `waitForStability` function takes reads spaced by the interval past the second.
4155
+ The `parseCSSColor` function takes an expression resolved on the scope it is handed, a registered
4156
+ token that does not inherit, a keyword, a hex triple, and `currentColor` at an `input` and an SVG
4157
+ element, a running color transition read at its sampled midpoint and left running, a scroller
4158
+ with a running height transition keeping its height and scroll position, a scope that records no
4159
+ mutation during the read, a transition on every property that starts none, a color scheme
4160
+ answered to a system color and to `light-dark()`, a token named only in a fallback carried, a
4161
+ disconnected scope answered with nothing, and an element that is no HTML, SVG, or MathML element
4162
+ resolved like any other. The `readColorContrast`
4163
+ function takes a token on an ancestor the body never declares, `currentColor`, and a token the
4164
+ element declares itself, each read at the element, a keyword, a hex triple, and a token at an
4165
+ `input`, a registered token that does not inherit, and a disconnected element refused. The `readCascade` function takes class tokens collected from
4166
+ plain and grouped rules and only real ones, and an escaped class decoded beside an attribute
4167
+ value whose class-like text defines nothing;
3741
4168
  `readRows` takes a row joined from its own text nodes rather than from run-together content, and
3742
4169
  an empty list; `extractOrphans` takes a child class rendered outside its container with a nested
3743
4170
  one left alone, nothing reported when every child sits inside one, and, as the control, an element
@@ -3752,11 +4179,14 @@ Each entry names the contracts its file proves. The test names carry the cases.
3752
4179
  element carrying an inline attribute reported once, a whitespace-only attribute read past, a
3753
4180
  fragment root, and — as the control — markup whose classes and `data-*` attributes leave it with
3754
4181
  nothing to report.
3755
- - [`tests/src/browser/factories.test.ts`](../tests/src/browser/factories.test.ts) — the journal
4182
+ - [`tests/src/browser/journeys/factories.test.ts`](../tests/src/browser/journeys/factories.test.ts) and
4183
+ [`tests/src/browser/captures/factories.test.ts`](../tests/src/browser/captures/factories.test.ts) — the journal
3756
4184
  contract, and the portfolio's refusals, its disabled gate, and its writes. Creation refuses an
3757
4185
  unregistered variant name; the registry expands across every variant whether or not the run
3758
4186
  writes; a run
3759
- that is not enabled applies nothing, writes nothing, and records nothing; an enabled run applies
4187
+ that is not enabled applies nothing, writes nothing, and records nothing, yet refuses an
4188
+ unregistered state and a second placement of one state, and accepts two distinct registered states
4189
+ with its lists still empty; an enabled run applies
3760
4190
  the variant, resizes the viewport, writes a real file through the provider, records it, and hands
3761
4191
  out snapshots rather than its own lists; it refuses an unregistered state and a second placement of
3762
4192
  one state; and a placement handed an element writes a frame that is not the whole page's and leaves
@@ -3769,7 +4199,11 @@ Each entry names the contracts its file proves. The test names carry the cases.
3769
4199
  operation, each withheld operation refused in its own voice, both permissions granted at once with
3770
4200
  the store answering from what it kept, a quota spent on accepted writes alone with `removeItem`
3771
4201
  consuming none of it, a granted permission replenishing no room, every refused quota value, and —
3772
- as the control for the inertness claim — a write that leaves `localStorage` exactly as it was.
4202
+ as the control for the inertness claim — a write that leaves `localStorage` exactly as it was. The
4203
+ event factories take the real event type, the bubbling, cancelable, and composed defaults and an
4204
+ override of each, and an event dispatched from a button inside an open shadow root that a
4205
+ `document` listener hears, beside a bare event of the same type that it does not, and a pointer
4206
+ or drag event whose `{ composed: false }` override keeps it inside the root.
3773
4207
  - [`tests/src/server/helpers.test.ts`](../tests/src/server/helpers.test.ts) — the `readInventory` and
3774
4208
  wait-family contracts, and each pure leaf against its own inputs. `resolveContained` takes
3775
4209
  contained relative and absolute targets and both spellings of an escape, and `requireContained`