@variance-authority/core 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +458 -0
  2. package/README.md +13 -10
  3. package/dist/attribute/component-hash.d.ts +3 -2
  4. package/dist/attribute/component-hash.js +3 -2
  5. package/dist/attribute/component-hash.js.map +1 -1
  6. package/dist/attribute/composition.d.ts +16 -0
  7. package/dist/attribute/composition.js +4 -6
  8. package/dist/attribute/composition.js.map +1 -1
  9. package/dist/attribute/index.d.ts +3 -1
  10. package/dist/attribute/index.js +2 -1
  11. package/dist/attribute/index.js.map +1 -1
  12. package/dist/attribute/region.js +1 -5
  13. package/dist/attribute/region.js.map +1 -1
  14. package/dist/attribute/source.d.ts +10 -24
  15. package/dist/attribute/source.js +4 -38
  16. package/dist/attribute/source.js.map +1 -1
  17. package/dist/attribute/subject-pieces.d.ts +37 -0
  18. package/dist/attribute/subject-pieces.js +122 -0
  19. package/dist/attribute/subject-pieces.js.map +1 -0
  20. package/dist/format/capture.d.ts +25 -0
  21. package/dist/format/capture.js +6 -0
  22. package/dist/format/capture.js.map +1 -1
  23. package/dist/format/document.d.ts +2 -2
  24. package/dist/format/document.js +8 -2
  25. package/dist/format/document.js.map +1 -1
  26. package/dist/format/drive.d.ts +191 -0
  27. package/dist/format/drive.js +343 -0
  28. package/dist/format/drive.js.map +1 -0
  29. package/dist/format/environment.d.ts +20 -2
  30. package/dist/format/environment.js +7 -0
  31. package/dist/format/environment.js.map +1 -1
  32. package/dist/format/index.d.ts +6 -3
  33. package/dist/format/index.js +4 -2
  34. package/dist/format/index.js.map +1 -1
  35. package/dist/format/intervention.d.ts +79 -0
  36. package/dist/format/intervention.js.map +1 -1
  37. package/dist/format/origin.d.ts +32 -0
  38. package/dist/format/origin.js +54 -0
  39. package/dist/format/origin.js.map +1 -0
  40. package/dist/format/snapshot.d.ts +2 -0
  41. package/dist/format/snapshot.js.map +1 -1
  42. package/dist/format/stabilize.d.ts +28 -5
  43. package/dist/format/stabilize.js +48 -100
  44. package/dist/format/stabilize.js.map +1 -1
  45. package/dist/format/waits.d.ts +12 -0
  46. package/dist/format/waits.js +156 -0
  47. package/dist/format/waits.js.map +1 -0
  48. package/dist/index.d.ts +2 -1
  49. package/dist/index.js +2 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/nearest.d.ts +46 -0
  52. package/dist/nearest.js +102 -0
  53. package/dist/nearest.js.map +1 -0
  54. package/dist/relate/before.d.ts +19 -24
  55. package/dist/relate/before.js +35 -37
  56. package/dist/relate/before.js.map +1 -1
  57. package/dist/relate/beyond.d.ts +59 -0
  58. package/dist/relate/beyond.js +98 -0
  59. package/dist/relate/beyond.js.map +1 -0
  60. package/dist/relate/carried.d.ts +122 -0
  61. package/dist/relate/carried.js +279 -0
  62. package/dist/relate/carried.js.map +1 -0
  63. package/dist/relate/dominate.d.ts +19 -0
  64. package/dist/relate/dominate.js +77 -0
  65. package/dist/relate/dominate.js.map +1 -0
  66. package/dist/relate/index.d.ts +3 -0
  67. package/dist/relate/index.js +3 -0
  68. package/dist/relate/index.js.map +1 -1
  69. package/dist/relate/merkle.js +2 -11
  70. package/dist/relate/merkle.js.map +1 -1
  71. package/dist/relate/narrow.d.ts +2 -2
  72. package/dist/relate/narrow.js +26 -73
  73. package/dist/relate/narrow.js.map +1 -1
  74. package/dist/relate/reach.d.ts +1 -1
  75. package/dist/relate/reach.js +10 -53
  76. package/dist/relate/reach.js.map +1 -1
  77. package/dist/relate/records.d.ts +1 -1
  78. package/dist/relate/records.js.map +1 -1
  79. package/dist/rules/index.d.ts +3 -3
  80. package/dist/rules/index.js +3 -3
  81. package/dist/rules/index.js.map +1 -1
  82. package/dist/rules/normalize/cascade.d.ts +12 -0
  83. package/dist/rules/normalize/cascade.js +34 -16
  84. package/dist/rules/normalize/cascade.js.map +1 -1
  85. package/dist/rules/normalize/fixture.d.ts +1 -0
  86. package/dist/rules/normalize/fixture.js +1 -0
  87. package/dist/rules/normalize/fixture.js.map +1 -1
  88. package/dist/rules/normalize/index.d.ts +2 -2
  89. package/dist/rules/normalize/index.js +9 -2
  90. package/dist/rules/normalize/index.js.map +1 -1
  91. package/dist/rules/normalize/value.d.ts +13 -2
  92. package/dist/rules/normalize/value.js +42 -8
  93. package/dist/rules/normalize/value.js.map +1 -1
  94. package/dist/rules/normalize/wrapper.js +5 -0
  95. package/dist/rules/normalize/wrapper.js.map +1 -1
  96. package/dist/rules/ruleset.d.ts +24 -4
  97. package/dist/rules/ruleset.js +24 -4
  98. package/dist/rules/ruleset.js.map +1 -1
  99. package/dist/segment/index.d.ts +20 -4
  100. package/dist/segment/index.js +28 -7
  101. package/dist/segment/index.js.map +1 -1
  102. package/dist/shard/index.d.ts +11 -0
  103. package/dist/shard/index.js +11 -0
  104. package/dist/shard/index.js.map +1 -0
  105. package/dist/shard/placement.d.ts +117 -0
  106. package/dist/shard/placement.js +170 -0
  107. package/dist/shard/placement.js.map +1 -0
  108. package/package.json +8 -1
@@ -1 +1 @@
1
- {"version":3,"file":"environment.js","sourceRoot":"","sources":["../../src/format/environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAe,MAAM,WAAW,CAAC;AAqHrD,MAAM,UAAU,cAAc,CAAC,MAAyB;IACtD,MAAM,MAAM,GAAG;QACb,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE;QAC/B,UAAU,EAAE,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE;QACpC,MAAM,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE;QAC5B,4EAA4E;QAC5E,0EAA0E;QAC1E,wDAAwD;QACxD,aAAa,EAAE,MAAM,CAAC,aAAa;KACpC,CAAC;IAEF,8EAA8E;IAC9E,4EAA4E;IAC5E,2DAA2D;IAC3D,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;IAEzE,OAAO;QACL,MAAM,EAAE,WAAW,CAAC,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;QACpE,cAAc,EAAE,WAAW,CAAC,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;QACpE,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAyB,EACzB,KAAwB;IAExB,MAAM,MAAM,GAAuB,EAAE,CAAC;IAEtC,MAAM,MAAM,GAAG,CAAC,KAAuB,EAAE,CAAU,EAAE,CAAU,EAAQ,EAAE;QACvE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,IAAI,KAAK,EAAE;YAAE,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC,CAAC;IAEF,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACvD,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACpD,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACnE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC1D,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,aAAa,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IAEnE,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { digestValue, type Digest } from './hash.js';\nimport type { ProfileId } from './profile.js';\n\n/**\n * The environment key: a hash of every render input that is not code.\n *\n * The system's central guarantee is *same hash ⇒ same render*. That guarantee is\n * exactly as strong as this key's coverage, and no stronger — an uncovered input\n * (a font that silently changed, an asset served from a URL whose bytes moved)\n * produces two different renders under one key, which is a false `unchanged`.\n *\n * Adding a field here is a mass-invalidation event (spec §7.3) and must be\n * treated as one. Leaving a field out is a correctness bug. When in doubt,\n * include it: over-invalidation costs CI minutes, under-invalidation costs trust.\n */\nexport interface EnvironmentInputs {\n /** Which collector, and therefore which dimensions exist at all (ADR-0002). */\n readonly profile: ProfileId;\n\n /** Engine identity. `jsdom@26.1.0`, `chromium@131.0.6778.33`. */\n readonly engine: string;\n\n /**\n * Normalization ruleset version. Changing the rules changes every hash by\n * design, so the ruleset is a render input like any other.\n */\n readonly ruleset: string;\n\n /** Computed-style allowlist version. Widening it is also mass invalidation. */\n readonly allowlist: string;\n\n readonly viewport: Viewport;\n\n /**\n * Fonts available at render time, as `family/weight/style/contentHash`.\n * A font substitution changes metrics, and therefore geometry, without\n * changing a single byte of code.\n */\n readonly fonts: readonly string[];\n\n /**\n * Resolved media/feature conditions. ADR-0003 flattens `@media`, `@supports`,\n * and `@container` away from the CSS text, so the conditions they were\n * flattened against must live here or they vanish from the key entirely.\n */\n readonly conditions: Readonly<Record<string, string | boolean | number>>;\n\n /**\n * External assets keyed by request URL, valued by content hash. URLs are not\n * identities: the same `url(...)` can resolve to different bytes tomorrow.\n */\n readonly assets: Readonly<Record<string, string>>;\n\n /**\n * Identity of the stabilization recipe the page was held still with, from\n * `recipeDigest` — absent when the subject was observed untouched.\n *\n * A render input, and one of the more consequential ones: pinning animations\n * changes `transform` and `opacity` on every animated node, so a baseline\n * collected without it and a run collected with it disagree everywhere and\n * agree about nothing. Without this field that disagreement arrives as a\n * change with a component and a file attached — a confident, specific, wrong\n * answer. With it, the two are different baselines and never meet.\n *\n * Optional rather than required because *not stabilizing* is a real state a\n * caller may be in — a jsdom capture has nothing to hold still — and\n * `undefined` is omitted from the canonical form, so the untouched case hashes\n * as the absence it is rather than as an empty recipe.\n */\n readonly stabilization?: Digest;\n}\n\nexport interface Viewport {\n readonly width: number;\n readonly height: number;\n readonly deviceScaleFactor: number;\n /** `prefers-color-scheme`, and the reason a theme switch is not a code change. */\n readonly colorScheme: 'light' | 'dark';\n}\n\nexport interface EnvironmentKey {\n /**\n * Every render input, including the ones only pixels can see.\n *\n * The key a raster baseline must be stored under. Two machines agree on it only\n * if they agree on device pixel ratio as well as everything else — which is why\n * pixel-based tools end up inside a container.\n */\n readonly digest: Digest;\n\n /**\n * The inputs that can reach the *semantic* representation.\n *\n * This is the key that matters economically. A raster baseline is machine-bound\n * because rasterization is machine-bound, and the usual answer — put the whole\n * pipeline in Docker — pays that cost on every subject of every build in order\n * to stabilise the tier that decides almost nothing.\n *\n * The semantic representation is built from the box tree, and device pixel\n * ratio cannot reach a box tree: layout is in CSS pixels, and a 2× render lays\n * out identically. Measured, not assumed — a DPR change moves 3015 pixels and\n * leaves structure and style byte-identical (journal 0012).\n *\n * So the semantic key omits it, and one semantic baseline is valid on a retina\n * laptop, a non-retina CI runner, and a container alike. The container is then\n * needed only for the raster residue, which the tiering already makes rare.\n *\n * What is *not* omitted: fonts, engine, viewport size, ruleset, allowlist, and\n * the resolved conditions. Each of those genuinely changes the box tree, and\n * dropping one to make baselines more portable would buy portability with\n * false `unchanged` verdicts.\n */\n readonly semanticDigest: Digest;\n\n readonly inputs: EnvironmentInputs;\n}\n\nexport function environmentKey(inputs: EnvironmentInputs): EnvironmentKey {\n const shared = {\n profile: inputs.profile,\n engine: inputs.engine,\n ruleset: inputs.ruleset,\n allowlist: inputs.allowlist,\n fonts: [...inputs.fonts].sort(),\n conditions: { ...inputs.conditions },\n assets: { ...inputs.assets },\n // In `shared`, so it reaches the semantic key too. That is the whole point:\n // the recipe's first job is to stop an animation from moving `transform`,\n // which is a value the semantic representation carries.\n stabilization: inputs.stabilization,\n };\n\n // Layout depends on the viewport's size and colour scheme; rasterization also\n // depends on its scale factor. Splitting the viewport is what makes the two\n // keys differ, and it is the only difference between them.\n const { deviceScaleFactor: _scale, ...layoutViewport } = inputs.viewport;\n\n return {\n digest: digestValue({ ...shared, viewport: { ...inputs.viewport } }),\n semanticDigest: digestValue({ ...shared, viewport: layoutViewport }),\n inputs,\n };\n}\n\n/**\n * Explain why two environment keys differ, field by field.\n *\n * Mass invalidation is only survivable if the docket can collapse it to a single\n * named root — \"chromium 131→132\", \"ruleset v3\" — and offer one-action\n * re-baselining (spec §7.3). That collapse needs the specific field, so a diff of\n * environments is a product feature, not a debugging aid.\n */\nexport function diffEnvironments(\n before: EnvironmentInputs,\n after: EnvironmentInputs,\n): readonly EnvironmentDelta[] {\n const deltas: EnvironmentDelta[] = [];\n\n const scalar = (field: EnvironmentField, a: unknown, b: unknown): void => {\n const from = JSON.stringify(a);\n const to = JSON.stringify(b);\n if (from !== to) deltas.push({ field, from, to });\n };\n\n scalar('profile', before.profile, after.profile);\n scalar('engine', before.engine, after.engine);\n scalar('ruleset', before.ruleset, after.ruleset);\n scalar('allowlist', before.allowlist, after.allowlist);\n scalar('viewport', before.viewport, after.viewport);\n scalar('fonts', [...before.fonts].sort(), [...after.fonts].sort());\n scalar('conditions', before.conditions, after.conditions);\n scalar('assets', before.assets, after.assets);\n scalar('stabilization', before.stabilization, after.stabilization);\n\n return deltas;\n}\n\nexport type EnvironmentField =\n | 'profile'\n | 'engine'\n | 'ruleset'\n | 'allowlist'\n | 'viewport'\n | 'fonts'\n | 'conditions'\n | 'assets'\n | 'stabilization';\n\nexport interface EnvironmentDelta {\n readonly field: EnvironmentField;\n readonly from: string;\n readonly to: string;\n}\n"]}
1
+ {"version":3,"file":"environment.js","sourceRoot":"","sources":["../../src/format/environment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAe,MAAM,WAAW,CAAC;AAyIrD,MAAM,UAAU,cAAc,CAAC,MAAyB;IACtD,MAAM,MAAM,GAAG;QACb,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE;QAC/B,UAAU,EAAE,EAAE,GAAG,MAAM,CAAC,UAAU,EAAE;QACpC,MAAM,EAAE,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE;QAC5B,4EAA4E;QAC5E,0EAA0E;QAC1E,wDAAwD;QACxD,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,+EAA+E;QAC/E,wEAAwE;QACxE,yEAAyE;QACzE,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAC;IAEF,8EAA8E;IAC9E,4EAA4E;IAC5E,2DAA2D;IAC3D,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,GAAG,cAAc,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC;IAEzE,OAAO;QACL,MAAM,EAAE,WAAW,CAAC,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,EAAE,GAAG,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;QACpE,cAAc,EAAE,WAAW,CAAC,EAAE,GAAG,MAAM,EAAE,QAAQ,EAAE,cAAc,EAAE,CAAC;QACpE,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAyB,EACzB,KAAwB;IAExB,MAAM,MAAM,GAAuB,EAAE,CAAC;IAEtC,MAAM,MAAM,GAAG,CAAC,KAAuB,EAAE,CAAU,EAAE,CAAU,EAAQ,EAAE;QACvE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QAC7B,IAAI,IAAI,KAAK,EAAE;YAAE,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC,CAAC;IAEF,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;IACvD,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACpD,MAAM,CAAC,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACnE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC1D,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,aAAa,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IACnE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC1D,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAE9C,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { digestValue, type Digest } from './hash.js';\nimport type { ProfileId } from './profile.js';\n\n/**\n * The environment key: a hash of every render input that is not code.\n *\n * The system's central guarantee is *same hash ⇒ same render*. That guarantee is\n * exactly as strong as this key's coverage, and no stronger — an uncovered input\n * (a font that silently changed, an asset served from a URL whose bytes moved)\n * produces two different renders under one key, which is a false `unchanged`.\n *\n * Adding a field here is a mass-invalidation event (spec §7.3) and must be\n * treated as one. Leaving a field out is a correctness bug. When in doubt,\n * include it: over-invalidation costs CI minutes, under-invalidation costs trust.\n */\nexport interface EnvironmentInputs extends PageRegion {\n /** Which collector, and therefore which dimensions exist at all (ADR-0002). */\n readonly profile: ProfileId;\n\n /** Engine identity. `jsdom@26.1.0`, `chromium@131.0.6778.33`. */\n readonly engine: string;\n\n /**\n * Normalization ruleset version. Changing the rules changes every hash by\n * design, so the ruleset is a render input like any other.\n */\n readonly ruleset: string;\n\n /** Computed-style allowlist version. Widening it is also mass invalidation. */\n readonly allowlist: string;\n\n readonly viewport: Viewport;\n\n /**\n * Fonts available at render time, as `family/weight/style/contentHash`.\n * A font substitution changes metrics, and therefore geometry, without\n * changing a single byte of code.\n */\n readonly fonts: readonly string[];\n\n /**\n * Resolved media/feature conditions. ADR-0003 flattens `@media`, `@supports`,\n * and `@container` away from the CSS text, so the conditions they were\n * flattened against must live here or they vanish from the key entirely.\n */\n readonly conditions: Readonly<Record<string, string | boolean | number>>;\n\n /**\n * External assets keyed by request URL, valued by content hash. URLs are not\n * identities: the same `url(...)` can resolve to different bytes tomorrow.\n */\n readonly assets: Readonly<Record<string, string>>;\n\n /**\n * Identity of the stabilization recipe the page was held still with, from\n * `recipeDigest` — absent when the subject was observed untouched.\n *\n * A render input, and one of the more consequential ones: pinning animations\n * changes `transform` and `opacity` on every animated node, so a baseline\n * collected without it and a run collected with it disagree everywhere and\n * agree about nothing. Without this field that disagreement arrives as a\n * change with a component and a file attached — a confident, specific, wrong\n * answer. With it, the two are different baselines and never meet.\n *\n * Optional rather than required because *not stabilizing* is a real state a\n * caller may be in — a jsdom capture has nothing to hold still — and\n * `undefined` is omitted from the canonical form, so the untouched case hashes\n * as the absence it is rather than as an empty recipe.\n */\n readonly stabilization?: Digest;\n}\n\n/**\n * The timezone a page's clock reads in and the language it formats for, as\n * Playwright names them.\n *\n * A page that formats a date or a number without naming a timezone or a language\n * of its own gets its browser's, so one build reads `12:30 AM` in one timezone\n * and `4:30 PM` in another — different text, different widths, no edit anywhere.\n * A collector that opened its page in a region it chose writes it to the\n * environment key, the way the viewport carries its colour scheme. A field is\n * absent where nobody chose it: a jsdom capture runs in its Node process, and an\n * absent field says so rather than naming a timezone nobody set.\n */\nexport interface PageRegion {\n /** An IANA timezone, such as `Europe/Berlin`. */\n readonly timezoneId?: string;\n\n /** A BCP 47 language tag, such as `de-DE`: a run's base locale (spec 0008). */\n readonly locale?: string;\n}\n\nexport interface Viewport {\n readonly width: number;\n readonly height: number;\n readonly deviceScaleFactor: number;\n /** `prefers-color-scheme`, and the reason a theme switch is not a code change. */\n readonly colorScheme: 'light' | 'dark';\n}\n\nexport interface EnvironmentKey {\n /**\n * Every render input, including the ones only pixels can see.\n *\n * The key a raster baseline must be stored under. Two machines agree on it only\n * if they agree on device pixel ratio as well as everything else — which is why\n * pixel-based tools end up inside a container.\n */\n readonly digest: Digest;\n\n /**\n * The inputs that can reach the *semantic* representation.\n *\n * This is the key that matters economically. A raster baseline is machine-bound\n * because rasterization is machine-bound, and the usual answer — put the whole\n * pipeline in Docker — pays that cost on every subject of every build in order\n * to stabilise the tier that decides almost nothing.\n *\n * The semantic representation is built from the box tree, and device pixel\n * ratio cannot reach a box tree: layout is in CSS pixels, and a 2× render lays\n * out identically. Measured, not assumed — a DPR change moves 3015 pixels and\n * leaves structure and style byte-identical (journal 0012).\n *\n * So the semantic key omits it, and one semantic baseline is valid on a retina\n * laptop, a non-retina CI runner, and a container alike. The container is then\n * needed only for the raster residue, which the tiering already makes rare.\n *\n * What is *not* omitted: fonts, engine, viewport size, ruleset, allowlist, and\n * the resolved conditions. Each of those genuinely changes the box tree, and\n * dropping one to make baselines more portable would buy portability with\n * false `unchanged` verdicts.\n */\n readonly semanticDigest: Digest;\n\n readonly inputs: EnvironmentInputs;\n}\n\nexport function environmentKey(inputs: EnvironmentInputs): EnvironmentKey {\n const shared = {\n profile: inputs.profile,\n engine: inputs.engine,\n ruleset: inputs.ruleset,\n allowlist: inputs.allowlist,\n fonts: [...inputs.fonts].sort(),\n conditions: { ...inputs.conditions },\n assets: { ...inputs.assets },\n // In `shared`, so it reaches the semantic key too. That is the whole point:\n // the recipe's first job is to stop an animation from moving `transform`,\n // which is a value the semantic representation carries.\n stabilization: inputs.stabilization,\n // Text is laid out, so a timezone or a language reaches the box tree, and both\n // keys carry them. `undefined` is omitted from the canonical form, so a\n // capture that chose neither keeps the key it had before either existed.\n timezoneId: inputs.timezoneId,\n locale: inputs.locale,\n };\n\n // Layout depends on the viewport's size and colour scheme; rasterization also\n // depends on its scale factor. Splitting the viewport is what makes the two\n // keys differ, and it is the only difference between them.\n const { deviceScaleFactor: _scale, ...layoutViewport } = inputs.viewport;\n\n return {\n digest: digestValue({ ...shared, viewport: { ...inputs.viewport } }),\n semanticDigest: digestValue({ ...shared, viewport: layoutViewport }),\n inputs,\n };\n}\n\n/**\n * Explain why two environment keys differ, field by field.\n *\n * Mass invalidation is only survivable if the docket can collapse it to a single\n * named root — \"chromium 131→132\", \"ruleset v3\" — and offer one-action\n * re-baselining (spec §7.3). That collapse needs the specific field, so a diff of\n * environments is a product feature, not a debugging aid.\n */\nexport function diffEnvironments(\n before: EnvironmentInputs,\n after: EnvironmentInputs,\n): readonly EnvironmentDelta[] {\n const deltas: EnvironmentDelta[] = [];\n\n const scalar = (field: EnvironmentField, a: unknown, b: unknown): void => {\n const from = JSON.stringify(a);\n const to = JSON.stringify(b);\n if (from !== to) deltas.push({ field, from, to });\n };\n\n scalar('profile', before.profile, after.profile);\n scalar('engine', before.engine, after.engine);\n scalar('ruleset', before.ruleset, after.ruleset);\n scalar('allowlist', before.allowlist, after.allowlist);\n scalar('viewport', before.viewport, after.viewport);\n scalar('fonts', [...before.fonts].sort(), [...after.fonts].sort());\n scalar('conditions', before.conditions, after.conditions);\n scalar('assets', before.assets, after.assets);\n scalar('stabilization', before.stabilization, after.stabilization);\n scalar('timezoneId', before.timezoneId, after.timezoneId);\n scalar('locale', before.locale, after.locale);\n\n return deltas;\n}\n\nexport type EnvironmentField =\n | 'profile'\n | 'engine'\n | 'ruleset'\n | 'allowlist'\n | 'viewport'\n | 'fonts'\n | 'conditions'\n | 'assets'\n | 'stabilization'\n | 'timezoneId'\n | 'locale';\n\nexport interface EnvironmentDelta {\n readonly field: EnvironmentField;\n readonly from: string;\n readonly to: string;\n}\n"]}
@@ -21,19 +21,22 @@ export type { AccessibilitySnapshot } from './accessibility.js';
21
21
  export { accessibilitySnapshot } from './accessibility.js';
22
22
  export type { ObservationProfile, ProfileId } from './profile.js';
23
23
  export { JSDOM_PROFILE, CHROMIUM_PROFILE, profileById, tierOfProfile } from './profile.js';
24
+ export { assetUrl, pageOrigin, withoutOrigin } from './origin.js';
24
25
  export type { Tier } from './tier.js';
25
26
  export { tierReaches } from './tier.js';
26
- export type { EnvironmentInputs, EnvironmentKey, EnvironmentDelta, EnvironmentField, Viewport, } from './environment.js';
27
+ export type { EnvironmentInputs, EnvironmentKey, EnvironmentDelta, EnvironmentField, PageRegion, Viewport, } from './environment.js';
27
28
  export { environmentKey, diffEnvironments } from './environment.js';
28
- export { INTERVENTIONS, SEMANTIC_RECIPE, COLLECT_RECIPE, LAYOUT_RECIPE, RASTER_RECIPE, holdAnimations, pinAnimations, hideCaret, hideScrollbars, hidePresentationalImages, waitForFonts, waitForImages, forTier, conflicts, interventionById, recipeOf, recipeCss, recipeScreenshot, recipeDigest, settleRecipe, describeRecipe, } from './stabilize.js';
29
+ export { INTERVENTIONS, SEMANTIC_RECIPE, COLLECT_RECIPE, DEFAULT_STABILIZE, LAYOUT_RECIPE, RASTER_RECIPE, holdAnimations, pinAnimations, hideCaret, hideScrollbars, hidePresentationalImages, waitForFonts, waitForImages, forTier, conflicts, interventionById, recipeOf, recipeToDrive, recipeCss, recipeScreenshot, recipeDigest, settleRecipe, describeRecipe, } from './stabilize.js';
29
30
  export type { Intervention, Recipe, Trick, ScreenshotOptions, SettleTarget, } from './stabilize.js';
31
+ export { DEFAULT_NOW, driveBeforeLoad, driveBeforeRead, featuresOf, forDriver } from './drive.js';
32
+ export type { DriverState, DriverTarget, MotionPreference } from './intervention.js';
30
33
  export type { Provenance, OwnerFrame, SourceLocation, StackFrame } from './provenance.js';
31
34
  export { propsDigest, heldDigest, jsxSourceOf, relativizeSource, JSX_SOURCE } from './provenance.js';
32
35
  export type { Holding, HeldCell, HeldValue } from './holding.js';
33
36
  export type { Wiring } from './wiring.js';
34
37
  export { keyedByPosition } from './wiring.js';
35
38
  export type { RawCapture, RawNode, RawAria, MatchedRule, Declaration, Rect, SubjectRef, Diagnostic, } from './capture.js';
36
- export { mergeDiagnostics } from './capture.js';
39
+ export { intersectionArea, mergeDiagnostics } from './capture.js';
37
40
  export type { SemanticSnapshot, SemanticNode, NodePath, StyleProvenanceEntry, IgnoreSite, ComponentHash, } from './snapshot.js';
38
41
  export { documentDigest, identityDigest, occupiesPixels, pictured, picture } from './document.js';
39
42
  export type { RenderDocument, RenderFrame, FrameElement, RenderResource, RenderIdentity, Raster, Pictured, } from './document.js';
@@ -16,11 +16,13 @@ export { shapeValue, pointerShape, pointerToken, DROPPED, VALUE_RECIPE } from '.
16
16
  export { digestString, digestBytes, digestValue, digestCombine, digestOfSha256, fileNameFor, digestFileName, digestOfFileName } from './hash.js';
17
17
  export { accessibilitySnapshot } from './accessibility.js';
18
18
  export { JSDOM_PROFILE, CHROMIUM_PROFILE, profileById, tierOfProfile } from './profile.js';
19
+ export { assetUrl, pageOrigin, withoutOrigin } from './origin.js';
19
20
  export { tierReaches } from './tier.js';
20
21
  export { environmentKey, diffEnvironments } from './environment.js';
21
- export { INTERVENTIONS, SEMANTIC_RECIPE, COLLECT_RECIPE, LAYOUT_RECIPE, RASTER_RECIPE, holdAnimations, pinAnimations, hideCaret, hideScrollbars, hidePresentationalImages, waitForFonts, waitForImages, forTier, conflicts, interventionById, recipeOf, recipeCss, recipeScreenshot, recipeDigest, settleRecipe, describeRecipe, } from './stabilize.js';
22
+ export { INTERVENTIONS, SEMANTIC_RECIPE, COLLECT_RECIPE, DEFAULT_STABILIZE, LAYOUT_RECIPE, RASTER_RECIPE, holdAnimations, pinAnimations, hideCaret, hideScrollbars, hidePresentationalImages, waitForFonts, waitForImages, forTier, conflicts, interventionById, recipeOf, recipeToDrive, recipeCss, recipeScreenshot, recipeDigest, settleRecipe, describeRecipe, } from './stabilize.js';
23
+ export { DEFAULT_NOW, driveBeforeLoad, driveBeforeRead, featuresOf, forDriver } from './drive.js';
22
24
  export { propsDigest, heldDigest, jsxSourceOf, relativizeSource, JSX_SOURCE } from './provenance.js';
23
25
  export { keyedByPosition } from './wiring.js';
24
- export { mergeDiagnostics } from './capture.js';
26
+ export { intersectionArea, mergeDiagnostics } from './capture.js';
25
27
  export { documentDigest, identityDigest, occupiesPixels, pictured, picture } from './document.js';
26
28
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/format/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAG/D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG3F,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGjJ,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAG3F,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AASxC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpE,OAAO,EACL,aAAa,EACb,eAAe,EACf,cAAc,EACd,aAAa,EACb,aAAa,EACb,cAAc,EACd,aAAa,EACb,SAAS,EACT,cAAc,EACd,wBAAwB,EACxB,YAAY,EACZ,aAAa,EACb,OAAO,EACP,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,cAAc,GACf,MAAM,gBAAgB,CAAC;AAUxB,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGrG,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAY9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAWhD,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC","sourcesContent":["/**\n * `@variance-authority/core/format` — what a subject *is*, as a value.\n *\n * The shapes every other group reads and none of them may redefine: a raw\n * capture as a collector hands it over, the normalized snapshot it becomes, the\n * document a renderer paints, the identity that addresses the result, and the\n * hashing that makes any of it comparable.\n *\n * A consumer writing their own collector needs exactly this and nothing else.\n * That is the entrypoint's reason to exist: the format is a contract other people\n * implement, and implementing it should not require reading the rules that\n * interpret it or the verdicts drawn from it.\n */\n\n// compass: variance-authority.normalization\n\nexport type { CanonicalValue } from './canonical.js';\nexport { canonicalize, canonicalNumber } from './canonical.js';\n\nexport type { CapturedValue, ShapeValueOptions, ValueShaping } from './value.js';\nexport { shapeValue, pointerShape, pointerToken, DROPPED, VALUE_RECIPE } from './value.js';\n\nexport type { Digest } from './hash.js';\nexport { digestString, digestBytes, digestValue, digestCombine, digestOfSha256, fileNameFor, digestFileName, digestOfFileName } from './hash.js';\n\nexport type { AccessibilitySnapshot } from './accessibility.js';\nexport { accessibilitySnapshot } from './accessibility.js';\n\nexport type { ObservationProfile, ProfileId } from './profile.js';\nexport { JSDOM_PROFILE, CHROMIUM_PROFILE, profileById, tierOfProfile } from './profile.js';\n\nexport type { Tier } from './tier.js';\nexport { tierReaches } from './tier.js';\n\nexport type {\n EnvironmentInputs,\n EnvironmentKey,\n EnvironmentDelta,\n EnvironmentField,\n Viewport,\n} from './environment.js';\nexport { environmentKey, diffEnvironments } from './environment.js';\n\nexport {\n INTERVENTIONS,\n SEMANTIC_RECIPE,\n COLLECT_RECIPE,\n LAYOUT_RECIPE,\n RASTER_RECIPE,\n holdAnimations,\n pinAnimations,\n hideCaret,\n hideScrollbars,\n hidePresentationalImages,\n waitForFonts,\n waitForImages,\n forTier,\n conflicts,\n interventionById,\n recipeOf,\n recipeCss,\n recipeScreenshot,\n recipeDigest,\n settleRecipe,\n describeRecipe,\n} from './stabilize.js';\nexport type {\n Intervention,\n Recipe,\n Trick,\n ScreenshotOptions,\n SettleTarget,\n} from './stabilize.js';\n\nexport type { Provenance, OwnerFrame, SourceLocation, StackFrame } from './provenance.js';\nexport { propsDigest, heldDigest, jsxSourceOf, relativizeSource, JSX_SOURCE } from './provenance.js';\nexport type { Holding, HeldCell, HeldValue } from './holding.js';\nexport type { Wiring } from './wiring.js';\nexport { keyedByPosition } from './wiring.js';\n\nexport type {\n RawCapture,\n RawNode,\n RawAria,\n MatchedRule,\n Declaration,\n Rect,\n SubjectRef,\n Diagnostic,\n} from './capture.js';\nexport { mergeDiagnostics } from './capture.js';\n\nexport type {\n SemanticSnapshot,\n SemanticNode,\n NodePath,\n StyleProvenanceEntry,\n IgnoreSite,\n ComponentHash,\n} from './snapshot.js';\n\nexport { documentDigest, identityDigest, occupiesPixels, pictured, picture } from './document.js';\nexport type {\n RenderDocument,\n RenderFrame,\n FrameElement,\n RenderResource,\n RenderIdentity,\n Raster,\n Pictured,\n} from './document.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/format/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAG/D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG3F,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGjJ,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE3F,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAUxC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpE,OAAO,EACL,aAAa,EACb,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,cAAc,EACd,aAAa,EACb,SAAS,EACT,cAAc,EACd,wBAAwB,EACxB,YAAY,EACZ,aAAa,EACb,OAAO,EACP,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,cAAc,GACf,MAAM,gBAAgB,CAAC;AAQxB,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIlG,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGrG,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAY9C,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAWlE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC","sourcesContent":["/**\n * `@variance-authority/core/format` — what a subject *is*, as a value.\n *\n * The shapes every other group reads and none of them may redefine: a raw\n * capture as a collector hands it over, the normalized snapshot it becomes, the\n * document a renderer paints, the identity that addresses the result, and the\n * hashing that makes any of it comparable.\n *\n * A consumer writing their own collector needs exactly this and nothing else.\n * That is the entrypoint's reason to exist: the format is a contract other people\n * implement, and implementing it should not require reading the rules that\n * interpret it or the verdicts drawn from it.\n */\n\n// compass: variance-authority.normalization\n\nexport type { CanonicalValue } from './canonical.js';\nexport { canonicalize, canonicalNumber } from './canonical.js';\n\nexport type { CapturedValue, ShapeValueOptions, ValueShaping } from './value.js';\nexport { shapeValue, pointerShape, pointerToken, DROPPED, VALUE_RECIPE } from './value.js';\n\nexport type { Digest } from './hash.js';\nexport { digestString, digestBytes, digestValue, digestCombine, digestOfSha256, fileNameFor, digestFileName, digestOfFileName } from './hash.js';\n\nexport type { AccessibilitySnapshot } from './accessibility.js';\nexport { accessibilitySnapshot } from './accessibility.js';\n\nexport type { ObservationProfile, ProfileId } from './profile.js';\nexport { JSDOM_PROFILE, CHROMIUM_PROFILE, profileById, tierOfProfile } from './profile.js';\n\nexport { assetUrl, pageOrigin, withoutOrigin } from './origin.js';\n\nexport type { Tier } from './tier.js';\nexport { tierReaches } from './tier.js';\n\nexport type {\n EnvironmentInputs,\n EnvironmentKey,\n EnvironmentDelta,\n EnvironmentField,\n PageRegion,\n Viewport,\n} from './environment.js';\nexport { environmentKey, diffEnvironments } from './environment.js';\n\nexport {\n INTERVENTIONS,\n SEMANTIC_RECIPE,\n COLLECT_RECIPE,\n DEFAULT_STABILIZE,\n LAYOUT_RECIPE,\n RASTER_RECIPE,\n holdAnimations,\n pinAnimations,\n hideCaret,\n hideScrollbars,\n hidePresentationalImages,\n waitForFonts,\n waitForImages,\n forTier,\n conflicts,\n interventionById,\n recipeOf,\n recipeToDrive,\n recipeCss,\n recipeScreenshot,\n recipeDigest,\n settleRecipe,\n describeRecipe,\n} from './stabilize.js';\nexport type {\n Intervention,\n Recipe,\n Trick,\n ScreenshotOptions,\n SettleTarget,\n} from './stabilize.js';\nexport { DEFAULT_NOW, driveBeforeLoad, driveBeforeRead, featuresOf, forDriver } from './drive.js';\nexport type { DriverState, DriverTarget, MotionPreference } from './intervention.js';\n\nexport type { Provenance, OwnerFrame, SourceLocation, StackFrame } from './provenance.js';\nexport { propsDigest, heldDigest, jsxSourceOf, relativizeSource, JSX_SOURCE } from './provenance.js';\nexport type { Holding, HeldCell, HeldValue } from './holding.js';\nexport type { Wiring } from './wiring.js';\nexport { keyedByPosition } from './wiring.js';\n\nexport type {\n RawCapture,\n RawNode,\n RawAria,\n MatchedRule,\n Declaration,\n Rect,\n SubjectRef,\n Diagnostic,\n} from './capture.js';\nexport { intersectionArea, mergeDiagnostics } from './capture.js';\n\nexport type {\n SemanticSnapshot,\n SemanticNode,\n NodePath,\n StyleProvenanceEntry,\n IgnoreSite,\n ComponentHash,\n} from './snapshot.js';\n\nexport { documentDigest, identityDigest, occupiesPixels, pictured, picture } from './document.js';\nexport type {\n RenderDocument,\n RenderFrame,\n FrameElement,\n RenderResource,\n RenderIdentity,\n Raster,\n Pictured,\n} from './document.js';\n"]}
@@ -43,9 +43,21 @@ export interface SettleTarget {
43
43
  */
44
44
  export interface PageGlobals {
45
45
  readonly document: {
46
+ /** Read to find what a test left focused; a shadow host stands in for its tree. */
47
+ readonly activeElement?: PageFocusable | null;
48
+ /** Asked whether an `autofocus` is still to be honoured. */
49
+ querySelector(selectors: string): unknown;
46
50
  readonly fonts?: {
47
51
  readonly ready: Promise<unknown>;
52
+ [Symbol.iterator](): Iterator<{
53
+ readonly family: string;
54
+ readonly status: string;
55
+ }>;
48
56
  };
57
+ /** Read to name a web font by where it was asked for; a cross-origin sheet throws. */
58
+ readonly styleSheets: ArrayLike<{
59
+ readonly cssRules: ArrayLike<PageCssRule>;
60
+ }>;
49
61
  readonly images: ArrayLike<{
50
62
  readonly complete: boolean;
51
63
  readonly currentSrc: string;
@@ -58,6 +70,63 @@ export interface PageGlobals {
58
70
  };
59
71
  setTimeout(handler: () => void, ms: number): unknown;
60
72
  clearTimeout(handle: unknown): void;
73
+ requestAnimationFrame(callback: () => void): unknown;
74
+ }
75
+ /** An element as a focus reset reads it: its own shadow tree's focus, and a way to let go. */
76
+ export interface PageFocusable {
77
+ readonly shadowRoot?: {
78
+ readonly activeElement: PageFocusable | null;
79
+ } | null;
80
+ blur?(): void;
81
+ }
82
+ /** The two values of `prefers-reduced-motion` a browser can be told to report. */
83
+ export type MotionPreference = 'no-preference' | 'reduce';
84
+ /**
85
+ * What a trick asks of the browser driver, where neither a stylesheet nor a
86
+ * script in the page can reach.
87
+ *
88
+ * A media preference, the clock and the pointer belong to the browser rather
89
+ * than the document. A media query reads what the browser emulates, `Date` has
90
+ * to be replaced before the page's first script reads it, and a hover ends only
91
+ * when the pointer leaves. Declared as data rather than as a function, so a host
92
+ * with no driver — a page agent, a test runner's own browser — can tell which
93
+ * tricks it cannot honour, and the hosts that can drive agree on what each one
94
+ * means. [`drive.ts`](./drive.ts) applies it.
95
+ */
96
+ export interface DriverState {
97
+ /** `prefers-reduced-motion: reduce`, emulated before the page loads. */
98
+ readonly reducedMotion?: 'reduce';
99
+ /** `Date` reads one fixed instant, in milliseconds, from before the page's first script. */
100
+ readonly clock?: {
101
+ readonly at: number;
102
+ };
103
+ /** The pointer off the page, before the subject is read. */
104
+ readonly pointer?: 'outside';
105
+ }
106
+ /**
107
+ * The driver surface a {@link DriverState} is applied through.
108
+ *
109
+ * Structural, and the shape Playwright's `Page` already has, so no driver
110
+ * package leaks into `core`.
111
+ */
112
+ export interface DriverTarget {
113
+ emulateMedia(options: {
114
+ reducedMotion: MotionPreference;
115
+ }): Promise<void>;
116
+ addInitScript(script: (frozen: number) => void, arg: number): Promise<unknown>;
117
+ readonly mouse: {
118
+ move(x: number, y: number): Promise<void>;
119
+ };
120
+ }
121
+ /** A rule as a settle step reads it: its declarations, the rules it groups, and the sheet it imports. */
122
+ interface PageCssRule {
123
+ readonly style?: {
124
+ getPropertyValue(property: string): string;
125
+ };
126
+ readonly cssRules?: ArrayLike<PageCssRule>;
127
+ readonly styleSheet?: {
128
+ readonly cssRules: ArrayLike<PageCssRule>;
129
+ } | null;
61
130
  }
62
131
  export interface Intervention {
63
132
  /**
@@ -83,5 +152,15 @@ export interface Intervention {
83
152
  readonly css?: string;
84
153
  readonly screenshot?: ScreenshotOptions;
85
154
  readonly settle?: (target: SettleTarget) => Promise<void>;
155
+ /**
156
+ * What the browser driver must do for this trick to hold.
157
+ *
158
+ * A trick with a `driver` is in force only where a host drove it: the hosts
159
+ * that own the browser report the ids they applied, and the page drops the
160
+ * rest from its recipe, so a reading never records a hold it did not have.
161
+ * See `forDriver`.
162
+ */
163
+ readonly driver?: DriverState;
86
164
  }
165
+ export {};
87
166
  //# sourceMappingURL=intervention.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"intervention.js","sourceRoot":"","sources":["../../src/format/intervention.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG","sourcesContent":["/**\n * What an intervention is, as a shape.\n *\n * Beside [`stabilize.ts`](./stabilize.ts), which argues why the set is open and\n * then fills it. This file is the declaration that argument depends on: an id, a\n * tier, a property it claims, and the three ways it can reach a page. A trick\n * from another project is one of these and nothing more, so the shape is worth\n * reading without reading the tricks.\n */\n\nimport type { Tier } from './tier.js';\n\n/** What kind of move it is, for reading a recipe at a glance. */\nexport type Trick =\n /** Puts the page into a known state. */\n | 'reset'\n /** Stops something that would otherwise keep moving. */\n | 'hold'\n /** Blocks until something outside the page's control has landed. */\n | 'wait'\n /** Removes something that is present but must not be measured. */\n | 'support';\n\n/** Screenshot options a trick contributes. Narrow on purpose. */\nexport interface ScreenshotOptions {\n readonly animations?: 'disabled';\n readonly caret?: 'hide';\n}\n\n/** The page surface a `settle` step may use. Structural, so no driver leaks in. */\nexport interface SettleTarget {\n evaluate<T>(fn: () => T | Promise<T>): Promise<T>;\n}\n\n/**\n * The page's globals, as the little of them a settle step needs.\n *\n * A settle closure is the one place in `core` that reads a live document, and it\n * gets away with it because it never runs here: it is handed to\n * {@link SettleTarget.evaluate}, which ships it into a page. ADR-0001's\n * enforcement is `core`'s `tsconfig`, which has no `lib.dom` — so writing\n * `window.document.fonts` is a compile error and this shape is what remains.\n *\n * That is a stricter arrangement than the convention it replaced, not a\n * loophole. Everything a settle step may touch is enumerated here, in one\n * declaration a reader can check, and a trick that wants more of the DOM has to\n * widen it in public rather than reach for an ambient global.\n */\nexport interface PageGlobals {\n readonly document: {\n readonly fonts?: { readonly ready: Promise<unknown> };\n readonly images: ArrayLike<{\n readonly complete: boolean;\n readonly currentSrc: string;\n readonly src: string;\n loading: string;\n addEventListener(type: string, listener: () => void, options?: { once?: boolean }): void;\n }>;\n };\n setTimeout(handler: () => void, ms: number): unknown;\n clearTimeout(handle: unknown): void;\n}\n\nexport interface Intervention {\n /**\n * Stable identifier, and part of the recipe's digest.\n *\n * Renaming one is therefore a new identity, which is correct: a reader\n * comparing two runs has no way to know that `hold-animations` and\n * `pause-animations` were the same trick.\n */\n readonly id: string;\n readonly trick: Trick;\n readonly needs: Tier;\n /** What it does and what it costs, in one sentence a report can print. */\n readonly because: string;\n\n /**\n * The property this trick governs.\n *\n * Two tricks claiming one subject are in conflict — pinning animations in CSS\n * while asking the compositor to fast-forward them applies both, and the CSS\n * wins silently. See {@link conflicts}.\n */\n readonly governs: string;\n\n readonly css?: string;\n readonly screenshot?: ScreenshotOptions;\n readonly settle?: (target: SettleTarget) => Promise<void>;\n}\n"]}
1
+ {"version":3,"file":"intervention.js","sourceRoot":"","sources":["../../src/format/intervention.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG","sourcesContent":["/**\n * What an intervention is, as a shape.\n *\n * Beside [`stabilize.ts`](./stabilize.ts), which argues why the set is open and\n * then fills it. This file is the declaration that argument depends on: an id, a\n * tier, a property it claims, and the three ways it can reach a page. A trick\n * from another project is one of these and nothing more, so the shape is worth\n * reading without reading the tricks.\n */\n\nimport type { Tier } from './tier.js';\n\n/** What kind of move it is, for reading a recipe at a glance. */\nexport type Trick =\n /** Puts the page into a known state. */\n | 'reset'\n /** Stops something that would otherwise keep moving. */\n | 'hold'\n /** Blocks until something outside the page's control has landed. */\n | 'wait'\n /** Removes something that is present but must not be measured. */\n | 'support';\n\n/** Screenshot options a trick contributes. Narrow on purpose. */\nexport interface ScreenshotOptions {\n readonly animations?: 'disabled';\n readonly caret?: 'hide';\n}\n\n/** The page surface a `settle` step may use. Structural, so no driver leaks in. */\nexport interface SettleTarget {\n evaluate<T>(fn: () => T | Promise<T>): Promise<T>;\n}\n\n/**\n * The page's globals, as the little of them a settle step needs.\n *\n * A settle closure is the one place in `core` that reads a live document, and it\n * gets away with it because it never runs here: it is handed to\n * {@link SettleTarget.evaluate}, which ships it into a page. ADR-0001's\n * enforcement is `core`'s `tsconfig`, which has no `lib.dom` — so writing\n * `window.document.fonts` is a compile error and this shape is what remains.\n *\n * That is a stricter arrangement than the convention it replaced, not a\n * loophole. Everything a settle step may touch is enumerated here, in one\n * declaration a reader can check, and a trick that wants more of the DOM has to\n * widen it in public rather than reach for an ambient global.\n */\nexport interface PageGlobals {\n readonly document: {\n /** Read to find what a test left focused; a shadow host stands in for its tree. */\n readonly activeElement?: PageFocusable | null;\n /** Asked whether an `autofocus` is still to be honoured. */\n querySelector(selectors: string): unknown;\n readonly fonts?: {\n readonly ready: Promise<unknown>;\n [Symbol.iterator](): Iterator<{ readonly family: string; readonly status: string }>;\n };\n /** Read to name a web font by where it was asked for; a cross-origin sheet throws. */\n readonly styleSheets: ArrayLike<{ readonly cssRules: ArrayLike<PageCssRule> }>;\n readonly images: ArrayLike<{\n readonly complete: boolean;\n readonly currentSrc: string;\n readonly src: string;\n loading: string;\n addEventListener(type: string, listener: () => void, options?: { once?: boolean }): void;\n }>;\n };\n setTimeout(handler: () => void, ms: number): unknown;\n clearTimeout(handle: unknown): void;\n requestAnimationFrame(callback: () => void): unknown;\n}\n\n/** An element as a focus reset reads it: its own shadow tree's focus, and a way to let go. */\nexport interface PageFocusable {\n readonly shadowRoot?: { readonly activeElement: PageFocusable | null } | null;\n blur?(): void;\n}\n\n/** The two values of `prefers-reduced-motion` a browser can be told to report. */\nexport type MotionPreference = 'no-preference' | 'reduce';\n\n/**\n * What a trick asks of the browser driver, where neither a stylesheet nor a\n * script in the page can reach.\n *\n * A media preference, the clock and the pointer belong to the browser rather\n * than the document. A media query reads what the browser emulates, `Date` has\n * to be replaced before the page's first script reads it, and a hover ends only\n * when the pointer leaves. Declared as data rather than as a function, so a host\n * with no driver — a page agent, a test runner's own browser — can tell which\n * tricks it cannot honour, and the hosts that can drive agree on what each one\n * means. [`drive.ts`](./drive.ts) applies it.\n */\nexport interface DriverState {\n /** `prefers-reduced-motion: reduce`, emulated before the page loads. */\n readonly reducedMotion?: 'reduce';\n /** `Date` reads one fixed instant, in milliseconds, from before the page's first script. */\n readonly clock?: { readonly at: number };\n /** The pointer off the page, before the subject is read. */\n readonly pointer?: 'outside';\n}\n\n/**\n * The driver surface a {@link DriverState} is applied through.\n *\n * Structural, and the shape Playwright's `Page` already has, so no driver\n * package leaks into `core`.\n */\nexport interface DriverTarget {\n emulateMedia(options: { reducedMotion: MotionPreference }): Promise<void>;\n addInitScript(script: (frozen: number) => void, arg: number): Promise<unknown>;\n readonly mouse: { move(x: number, y: number): Promise<void> };\n}\n\n/** A rule as a settle step reads it: its declarations, the rules it groups, and the sheet it imports. */\ninterface PageCssRule {\n readonly style?: { getPropertyValue(property: string): string };\n readonly cssRules?: ArrayLike<PageCssRule>;\n readonly styleSheet?: { readonly cssRules: ArrayLike<PageCssRule> } | null;\n}\n\nexport interface Intervention {\n /**\n * Stable identifier, and part of the recipe's digest.\n *\n * Renaming one is therefore a new identity, which is correct: a reader\n * comparing two runs has no way to know that `hold-animations` and\n * `pause-animations` were the same trick.\n */\n readonly id: string;\n readonly trick: Trick;\n readonly needs: Tier;\n /** What it does and what it costs, in one sentence a report can print. */\n readonly because: string;\n\n /**\n * The property this trick governs.\n *\n * Two tricks claiming one subject are in conflict — pinning animations in CSS\n * while asking the compositor to fast-forward them applies both, and the CSS\n * wins silently. See {@link conflicts}.\n */\n readonly governs: string;\n\n readonly css?: string;\n readonly screenshot?: ScreenshotOptions;\n readonly settle?: (target: SettleTarget) => Promise<void>;\n\n /**\n * What the browser driver must do for this trick to hold.\n *\n * A trick with a `driver` is in force only where a host drove it: the hosts\n * that own the browser report the ids they applied, and the page drops the\n * rest from its recipe, so a reading never records a hold it did not have.\n * See `forDriver`.\n */\n readonly driver?: DriverState;\n}\n"]}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The origin a page was served from, and a URL with that origin taken off.
3
+ *
4
+ * A collector serves a build on a free port, so the page's own origin is an
5
+ * accident of the run and never part of what was built. Every identity that
6
+ * holds a URL the page resolved — a `url()` in a computed value, a document's
7
+ * base and its resources — drops it through these two, so they agree on what
8
+ * counts as the page's own.
9
+ *
10
+ * `URL` answers, as it does for the engine that serialized `baseURI`. Only an
11
+ * `http` or `https` page has an origin to take off: `about:blank` and `file:`
12
+ * serialize theirs as `"null"`, and a string that does not parse has none.
13
+ */
14
+ export declare function pageOrigin(baseUrl: string | undefined): string | undefined;
15
+ /**
16
+ * `url` as a path when `origin` serves it, unchanged otherwise.
17
+ *
18
+ * Matched up to the slash that follows the authority, so `:400` never claims
19
+ * `:4001`, and a URL another origin serves keeps its origin.
20
+ */
21
+ export declare function withoutOrigin(url: string, origin: string | undefined): string;
22
+ /**
23
+ * The URL an asset key names, for looking up the bytes a document carries.
24
+ *
25
+ * The inverse of keying through {@link withoutOrigin}: a path the page's origin
26
+ * serves is resolved against the document's `baseUrl`, the URL the browser
27
+ * asked for and the resources are keyed by. An absolute key is returned as it
28
+ * is. A key that does not resolve, or a document with no base, answers the key
29
+ * unchanged, so the lookup that follows misses and says which key it missed.
30
+ */
31
+ export declare function assetUrl(key: string, baseUrl: string | undefined): string;
32
+ //# sourceMappingURL=origin.d.ts.map
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The origin a page was served from, and a URL with that origin taken off.
3
+ *
4
+ * A collector serves a build on a free port, so the page's own origin is an
5
+ * accident of the run and never part of what was built. Every identity that
6
+ * holds a URL the page resolved — a `url()` in a computed value, a document's
7
+ * base and its resources — drops it through these two, so they agree on what
8
+ * counts as the page's own.
9
+ *
10
+ * `URL` answers, as it does for the engine that serialized `baseURI`. Only an
11
+ * `http` or `https` page has an origin to take off: `about:blank` and `file:`
12
+ * serialize theirs as `"null"`, and a string that does not parse has none.
13
+ */
14
+ export function pageOrigin(baseUrl) {
15
+ if (baseUrl === undefined)
16
+ return undefined;
17
+ let parsed;
18
+ try {
19
+ parsed = new URL(baseUrl);
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ return parsed.protocol === 'http:' || parsed.protocol === 'https:' ? parsed.origin : undefined;
25
+ }
26
+ /**
27
+ * `url` as a path when `origin` serves it, unchanged otherwise.
28
+ *
29
+ * Matched up to the slash that follows the authority, so `:400` never claims
30
+ * `:4001`, and a URL another origin serves keeps its origin.
31
+ */
32
+ export function withoutOrigin(url, origin) {
33
+ return origin !== undefined && url.startsWith(`${origin}/`) ? url.slice(origin.length) : url;
34
+ }
35
+ /**
36
+ * The URL an asset key names, for looking up the bytes a document carries.
37
+ *
38
+ * The inverse of keying through {@link withoutOrigin}: a path the page's origin
39
+ * serves is resolved against the document's `baseUrl`, the URL the browser
40
+ * asked for and the resources are keyed by. An absolute key is returned as it
41
+ * is. A key that does not resolve, or a document with no base, answers the key
42
+ * unchanged, so the lookup that follows misses and says which key it missed.
43
+ */
44
+ export function assetUrl(key, baseUrl) {
45
+ if (baseUrl === undefined)
46
+ return key;
47
+ try {
48
+ return new URL(key, baseUrl).toString();
49
+ }
50
+ catch {
51
+ return key;
52
+ }
53
+ }
54
+ //# sourceMappingURL=origin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origin.js","sourceRoot":"","sources":["../../src/format/origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,UAAU,CAAC,OAA2B;IACpD,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC5C,IAAI,MAA8D,CAAC;IACnE,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;AACjG,CAAC;AAOD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,MAA0B;IACnE,OAAO,MAAM,KAAK,SAAS,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAC/F,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,OAA2B;IAC/D,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,GAAG,CAAC;IACtC,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC;IACb,CAAC;AACH,CAAC","sourcesContent":["/**\n * The origin a page was served from, and a URL with that origin taken off.\n *\n * A collector serves a build on a free port, so the page's own origin is an\n * accident of the run and never part of what was built. Every identity that\n * holds a URL the page resolved — a `url()` in a computed value, a document's\n * base and its resources — drops it through these two, so they agree on what\n * counts as the page's own.\n *\n * `URL` answers, as it does for the engine that serialized `baseURI`. Only an\n * `http` or `https` page has an origin to take off: `about:blank` and `file:`\n * serialize theirs as `\"null\"`, and a string that does not parse has none.\n */\nexport function pageOrigin(baseUrl: string | undefined): string | undefined {\n if (baseUrl === undefined) return undefined;\n let parsed: { readonly origin: string; readonly protocol: string };\n try {\n parsed = new URL(baseUrl);\n } catch {\n return undefined;\n }\n return parsed.protocol === 'http:' || parsed.protocol === 'https:' ? parsed.origin : undefined;\n}\n\n// `core` builds against ES2022 alone; every host it runs in has the WHATWG `URL`.\ndeclare const URL: {\n new (url: string, base?: string): { readonly origin: string; readonly protocol: string; toString(): string };\n};\n\n/**\n * `url` as a path when `origin` serves it, unchanged otherwise.\n *\n * Matched up to the slash that follows the authority, so `:400` never claims\n * `:4001`, and a URL another origin serves keeps its origin.\n */\nexport function withoutOrigin(url: string, origin: string | undefined): string {\n return origin !== undefined && url.startsWith(`${origin}/`) ? url.slice(origin.length) : url;\n}\n\n/**\n * The URL an asset key names, for looking up the bytes a document carries.\n *\n * The inverse of keying through {@link withoutOrigin}: a path the page's origin\n * serves is resolved against the document's `baseUrl`, the URL the browser\n * asked for and the resources are keyed by. An absolute key is returned as it\n * is. A key that does not resolve, or a document with no base, answers the key\n * unchanged, so the lookup that follows misses and says which key it missed.\n */\nexport function assetUrl(key: string, baseUrl: string | undefined): string {\n if (baseUrl === undefined) return key;\n try {\n return new URL(key, baseUrl).toString();\n } catch {\n return key;\n }\n}\n"]}
@@ -162,6 +162,8 @@ export interface SemanticNode {
162
162
  * on `RawNode`. See {@link SemanticSnapshot.ignoreSites}.
163
163
  */
164
164
  readonly ignoredBy?: readonly string[];
165
+ /** What was not read about this node, carried from `RawNode.unread`. Outside every hash. */
166
+ readonly unread?: string;
165
167
  readonly children: readonly SemanticNode[];
166
168
  }
167
169
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/format/snapshot.ts"],"names":[],"mappings":"","sourcesContent":["import type { SubjectRef, Rect, Diagnostic } from './capture.js';\nimport type { EnvironmentKey } from './environment.js';\nimport type { Digest } from './hash.js';\nimport type { ObservationProfile } from './profile.js';\nimport type { Provenance } from './provenance.js';\nimport type { Wiring } from './wiring.js';\nimport type { Holding } from './holding.js';\n\n/**\n * The normalized semantic snapshot: the verdict's input, and the thing a render\n * hash addresses.\n *\n * Everything volatile is gone by this point. Ids have become structural aliases,\n * class attributes have been dropped entirely, inapplicable CSS has been pruned,\n * and the cascade has been resolved to winning values (ADR-0003). What remains is\n * meant to be *read*: a snapshot a reviewer refuses to open is a snapshot that\n * gets rubber-stamped.\n */\nexport interface SemanticSnapshot {\n readonly formatVersion: 1;\n readonly subject: SubjectRef;\n readonly profile: ObservationProfile;\n readonly environment: EnvironmentKey;\n\n /** Content hash of `root` under `environment`. The identity of this state. */\n readonly renderHash: Digest;\n\n /**\n * Structure and applicable style, hashed separately.\n *\n * These back the tier below full semantic comparison (ADR-0003): matching both\n * settles a subject without resolving layout or taking a screenshot. Split\n * rather than combined because the docket wants to say *which* held — \"the DOM\n * is identical, only styling moved\" is the sentence that turns a diff into a\n * token-band root.\n */\n readonly structureHash: Digest;\n readonly styleHash: Digest;\n\n readonly root: SemanticNode;\n\n /**\n * Where each winning declaration came from. Deliberately outside the hash.\n *\n * Pruning discards exactly what attribution needs — *which rule set this?* — so\n * it is preserved here instead. Keeping it out of the hash is the point: moving\n * a rule between files renames a source without changing a render, and must not\n * invalidate a baseline.\n */\n readonly styleProvenance: readonly StyleProvenanceEntry[];\n\n /**\n * Subtrees the operator declared out of scope, resolved against *this* tree.\n *\n * Deliberately outside the hash, for the reason `styleProvenance` is: an ignore\n * changes what a run says about a render, never the render. Folding it into the\n * identity would invalidate every baseline in the repository the first time\n * somebody masked a clock, which is the surest way to make a safety feature the\n * thing people turn off.\n *\n * Present only when something was excluded, so a snapshot from a run with no\n * ignores is byte-identical to one produced before this field existed.\n */\n readonly ignoreSites?: readonly IgnoreSite[];\n\n /**\n * What the collector or the normalizer could not do, carried forward.\n *\n * Outside the hash: a diagnostic describes the *observation*, not the render,\n * and a snapshot that hashed its own warnings would invalidate whenever the\n * warning text was reworded. But it must survive to the verdict — a subject\n * whose capture half-failed is not a subject that legitimately `unchanged`,\n * and silently discarding that fact is how a broken collector reads as a clean\n * build.\n */\n readonly diagnostics: readonly Diagnostic[];\n}\n\n/**\n * A node's stable address: child indices from the root, e.g. `0/2/1`.\n *\n * Positional because nothing else survives. Ids are volatile (that is why they\n * were aliased away), and class names have been dropped. Position is honest about\n * what it costs: inserting a sibling renumbers everything after it, so the differ\n * matches nodes by shape and provenance before falling back to path (see\n * `diff.ts`), and treats a path change alone as evidence of nothing.\n */\nexport type NodePath = string;\n\nexport interface SemanticNode {\n readonly path: NodePath;\n readonly tag: string;\n\n /** Structural alias, e.g. `#a0`. Absent when the node carried no id. */\n readonly alias?: string;\n\n readonly role?: string;\n readonly name?: string;\n\n /**\n * Accessible description, resolved. See `RawAria.description` for why this is\n * a field of its own rather than an attribute.\n */\n readonly description?: string;\n\n readonly state?: Readonly<Record<string, string | boolean | number>>;\n\n /** Semantic attributes surviving normalization. `class` is never among them. */\n readonly attributes: Readonly<Record<string, string>>;\n\n /** Winning values for allowlisted properties, canonicalized. */\n readonly style: Readonly<Record<string, string>>;\n\n /**\n * Custom properties this node's winning declarations resolved through, as\n * name → resolved value.\n *\n * Recording the name alongside the value is what lets a design-token change\n * surface as one root with counted collateral instead of hundreds of unrelated\n * colour diffs (spec §5, `token` band).\n */\n readonly tokens?: Readonly<Record<string, string>>;\n\n /**\n * Which token each styled property resolved through, including by inheritance.\n *\n * Outside the hash, like `styleProvenance` and for the same reason: swapping a\n * literal for a token of the same value renames a source without changing a\n * render. It exists so the differ can tell *which* property a token edit\n * explains — `tokens` alone says a node uses tokens, not which of its values\n * one drove.\n */\n readonly styleTokens?: Readonly<Record<string, string>>;\n\n /** Present only under a profile with layout. Absent, never zeroed (ADR-0002). */\n readonly rect?: Rect;\n\n /** Text content, or its digest when policy declares the region volatile. */\n readonly text?: string;\n\n readonly provenance?: Provenance;\n\n /**\n * How the framework holds this node's component, when a framework adapter\n * supplied it. See {@link Wiring}.\n *\n * On the node rather than on `Provenance`, though both come from the same\n * adapter and the same fiber, because they answer to different readers.\n * Provenance exists so a diff arrives carrying a name; wiring is a *dimension\n * of the subject*, hashed into a band of its own, and a band's inputs belong\n * beside the other bands' inputs — next to `style` and `attributes`, which is\n * where `shapeOf` goes looking.\n */\n readonly wiring?: Wiring;\n\n /**\n * What that component was holding. **Outside every hash**, always.\n *\n * The field beside it is the contrast worth reading: `wiring` is a band and\n * this is evidence. `Wiring` refuses state values because a hook's value is\n * exactly what may legitimately differ between two readings of one page, and\n * that refusal is right and is not softened here — a `holding` reaches the\n * snapshot and reaches no digest, so a subject whose clock ticked has the same\n * `renderHash` it had before this field existed.\n *\n * It rides outside on the same terms as `styleProvenance`, `styleTokens` and\n * `ignoredBy`, by the same mechanism: `structureOf`, `styleOf` and `shapeOf`\n * project the fields they hash **by name**, so a field none of them names\n * cannot reach an identity. `holding.hash.test.ts` asserts that rather than\n * trusting it, because the cost of being wrong is every baseline in a\n * repository invalidating the first time a component held a timestamp.\n */\n readonly holding?: Holding;\n\n /**\n * `true` on the root of a subtree rendered through a portal.\n *\n * Recorded in the structure hash so that \"the dialog moved from inline to\n * portalled\" is a change rather than a coincidence of identical content, and\n * so the docket can say *where* something rendered, not only that it exists.\n */\n readonly portalled?: boolean;\n\n /**\n * Ignore rules this node was found under. Outside every hash, like its origin\n * on `RawNode`. See {@link SemanticSnapshot.ignoreSites}.\n */\n readonly ignoredBy?: readonly string[];\n\n readonly children: readonly SemanticNode[];\n}\n\n/**\n * A subtree the operator excluded, resolved to a path and a box (spec 0024).\n *\n * Lives in `format` rather than beside the rules that consume it, because it is\n * part of what a snapshot *is*: the collector produced it, it travels with the\n * document over every wire this project has, and `core/judge` is only its first\n * reader. A type the wire format needs cannot live in the policy layer.\n */\nexport interface IgnoreSite {\n /** Root of the excluded subtree, in this snapshot's own path space. */\n readonly path: NodePath;\n\n /** Which rule put it here, matched exactly against `IgnoreRule.id`. */\n readonly rule: string;\n\n /**\n * The box it occupied, for the raster tier.\n *\n * Absent under a profile with no layout — which is ADR-0002 rather than a\n * failure: a profile that cannot measure a box cannot decide pixels either, so\n * there is nothing for the box to subtract from.\n */\n readonly rect?: Rect;\n}\n\n/**\n * A component's own content, hashed per band (ADR-0018).\n *\n * In `format` rather than beside `hashComponents`, which computes it, because a\n * baseline carries these: a stored image plus the component hashes of the\n * document that painted it is what lets a later run tell the component that\n * *caused* a change from the components the change merely moved. That makes this\n * part of the wire format every store and every transport has to preserve, and a\n * type the wire needs cannot live in the layer that derives it.\n */\nexport interface ComponentHash {\n readonly component: string;\n\n /** Boundaries of this component in the subject, counted in document order. */\n readonly instances: number;\n\n /**\n * Tree shape: tags, structural aliases, portalling, allowlisted attributes,\n * and which child boundaries sit where. The `geometry` band's structural half.\n *\n * **Split out of a single `structure` digest on 2026-08-06.** It used to carry\n * the accessible semantics and the text too, and the fusion was invisible\n * until something needed to ask *which band moved* — at which point a baseline\n * could say \"this component changed\" and never say whether a heading was\n * renamed, a paragraph reworded, or a node inserted. Those are three different\n * bands, three different readers, and one of them is the band a route-level\n * test exists to ignore.\n */\n readonly structure: Digest;\n\n /**\n * Role, accessible name, and ARIA state. The `a11y` band.\n *\n * Its own digest because it is the band that must never be absorbed. Every\n * sensitivity level asserts on it, including the ones that ignore everything\n * else — and a level cannot assert on a band that is fused into another one.\n */\n readonly semantics: Digest;\n\n /** Text runs, in document order. The `content` band. */\n readonly text: Digest;\n\n readonly style: Digest;\n\n /**\n * Absent under a profile without layout — absent, never empty (ADR-0002).\n *\n * An empty geometry digest would compare equal between a run that observed no\n * movement and a run that could not observe movement at all, which is the\n * false `unchanged` this system must never produce.\n */\n readonly geometry?: Digest;\n\n /**\n * The box each instance occupies, in document order. Never hashed.\n *\n * A digest can say a component is not what it was and can never say by how\n * much. That is the gap a reviewer falls into: `Button — geometry, token` is\n * true of a control that lost a shadow and of one that grew eight pixels and\n * pushed the page down, and only the second explains why the subject is a\n * different height. Four numbers per instance close it.\n *\n * Absent for a baseline written before this existed and for a profile with no\n * layout — the same absence, and both mean *nobody measured*, never *it did\n * not move*. A `null` entry is a boundary the browser laid out nothing for,\n * kept in place so the list stays parallel to the instances it counts.\n */\n readonly boxes?: readonly (Rect | null)[];\n\n /**\n * Every distinct value the component's own nodes declared, per property, in\n * code-unit order; a custom property appears under its `--name` with the\n * values it resolved to. Never hashed.\n *\n * `boxes` answers *by how much*; this answers *which declaration*. `style`\n * moved says a `Button` is not styled as it was, and a reviewer holding only\n * that opens the file and diffs by eye. With both revisions' values the run\n * says `padding-left 8px → 14px`, which is the line somebody edited.\n *\n * Absent for a baseline written before this existed. That means *nobody\n * recorded it*, and the comparison says nothing about properties rather than\n * reporting every one of them as new.\n */\n readonly values?: Readonly<Record<string, readonly string[]>>;\n}\n\nexport interface StyleProvenanceEntry {\n readonly path: NodePath;\n readonly property: string;\n readonly sheet: string;\n /** Generated segments replaced by placeholders, so it can name a root safely. */\n readonly selector: string;\n readonly tokenName?: string;\n readonly source?: { readonly file: string; readonly line: number };\n}\n"]}
1
+ {"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/format/snapshot.ts"],"names":[],"mappings":"","sourcesContent":["import type { SubjectRef, Rect, Diagnostic } from './capture.js';\nimport type { EnvironmentKey } from './environment.js';\nimport type { Digest } from './hash.js';\nimport type { ObservationProfile } from './profile.js';\nimport type { Provenance } from './provenance.js';\nimport type { Wiring } from './wiring.js';\nimport type { Holding } from './holding.js';\n\n/**\n * The normalized semantic snapshot: the verdict's input, and the thing a render\n * hash addresses.\n *\n * Everything volatile is gone by this point. Ids have become structural aliases,\n * class attributes have been dropped entirely, inapplicable CSS has been pruned,\n * and the cascade has been resolved to winning values (ADR-0003). What remains is\n * meant to be *read*: a snapshot a reviewer refuses to open is a snapshot that\n * gets rubber-stamped.\n */\nexport interface SemanticSnapshot {\n readonly formatVersion: 1;\n readonly subject: SubjectRef;\n readonly profile: ObservationProfile;\n readonly environment: EnvironmentKey;\n\n /** Content hash of `root` under `environment`. The identity of this state. */\n readonly renderHash: Digest;\n\n /**\n * Structure and applicable style, hashed separately.\n *\n * These back the tier below full semantic comparison (ADR-0003): matching both\n * settles a subject without resolving layout or taking a screenshot. Split\n * rather than combined because the docket wants to say *which* held — \"the DOM\n * is identical, only styling moved\" is the sentence that turns a diff into a\n * token-band root.\n */\n readonly structureHash: Digest;\n readonly styleHash: Digest;\n\n readonly root: SemanticNode;\n\n /**\n * Where each winning declaration came from. Deliberately outside the hash.\n *\n * Pruning discards exactly what attribution needs — *which rule set this?* — so\n * it is preserved here instead. Keeping it out of the hash is the point: moving\n * a rule between files renames a source without changing a render, and must not\n * invalidate a baseline.\n */\n readonly styleProvenance: readonly StyleProvenanceEntry[];\n\n /**\n * Subtrees the operator declared out of scope, resolved against *this* tree.\n *\n * Deliberately outside the hash, for the reason `styleProvenance` is: an ignore\n * changes what a run says about a render, never the render. Folding it into the\n * identity would invalidate every baseline in the repository the first time\n * somebody masked a clock, which is the surest way to make a safety feature the\n * thing people turn off.\n *\n * Present only when something was excluded, so a snapshot from a run with no\n * ignores is byte-identical to one produced before this field existed.\n */\n readonly ignoreSites?: readonly IgnoreSite[];\n\n /**\n * What the collector or the normalizer could not do, carried forward.\n *\n * Outside the hash: a diagnostic describes the *observation*, not the render,\n * and a snapshot that hashed its own warnings would invalidate whenever the\n * warning text was reworded. But it must survive to the verdict — a subject\n * whose capture half-failed is not a subject that legitimately `unchanged`,\n * and silently discarding that fact is how a broken collector reads as a clean\n * build.\n */\n readonly diagnostics: readonly Diagnostic[];\n}\n\n/**\n * A node's stable address: child indices from the root, e.g. `0/2/1`.\n *\n * Positional because nothing else survives. Ids are volatile (that is why they\n * were aliased away), and class names have been dropped. Position is honest about\n * what it costs: inserting a sibling renumbers everything after it, so the differ\n * matches nodes by shape and provenance before falling back to path (see\n * `diff.ts`), and treats a path change alone as evidence of nothing.\n */\nexport type NodePath = string;\n\nexport interface SemanticNode {\n readonly path: NodePath;\n readonly tag: string;\n\n /** Structural alias, e.g. `#a0`. Absent when the node carried no id. */\n readonly alias?: string;\n\n readonly role?: string;\n readonly name?: string;\n\n /**\n * Accessible description, resolved. See `RawAria.description` for why this is\n * a field of its own rather than an attribute.\n */\n readonly description?: string;\n\n readonly state?: Readonly<Record<string, string | boolean | number>>;\n\n /** Semantic attributes surviving normalization. `class` is never among them. */\n readonly attributes: Readonly<Record<string, string>>;\n\n /** Winning values for allowlisted properties, canonicalized. */\n readonly style: Readonly<Record<string, string>>;\n\n /**\n * Custom properties this node's winning declarations resolved through, as\n * name → resolved value.\n *\n * Recording the name alongside the value is what lets a design-token change\n * surface as one root with counted collateral instead of hundreds of unrelated\n * colour diffs (spec §5, `token` band).\n */\n readonly tokens?: Readonly<Record<string, string>>;\n\n /**\n * Which token each styled property resolved through, including by inheritance.\n *\n * Outside the hash, like `styleProvenance` and for the same reason: swapping a\n * literal for a token of the same value renames a source without changing a\n * render. It exists so the differ can tell *which* property a token edit\n * explains — `tokens` alone says a node uses tokens, not which of its values\n * one drove.\n */\n readonly styleTokens?: Readonly<Record<string, string>>;\n\n /** Present only under a profile with layout. Absent, never zeroed (ADR-0002). */\n readonly rect?: Rect;\n\n /** Text content, or its digest when policy declares the region volatile. */\n readonly text?: string;\n\n readonly provenance?: Provenance;\n\n /**\n * How the framework holds this node's component, when a framework adapter\n * supplied it. See {@link Wiring}.\n *\n * On the node rather than on `Provenance`, though both come from the same\n * adapter and the same fiber, because they answer to different readers.\n * Provenance exists so a diff arrives carrying a name; wiring is a *dimension\n * of the subject*, hashed into a band of its own, and a band's inputs belong\n * beside the other bands' inputs — next to `style` and `attributes`, which is\n * where `shapeOf` goes looking.\n */\n readonly wiring?: Wiring;\n\n /**\n * What that component was holding. **Outside every hash**, always.\n *\n * The field beside it is the contrast worth reading: `wiring` is a band and\n * this is evidence. `Wiring` refuses state values because a hook's value is\n * exactly what may legitimately differ between two readings of one page, and\n * that refusal is right and is not softened here — a `holding` reaches the\n * snapshot and reaches no digest, so a subject whose clock ticked has the same\n * `renderHash` it had before this field existed.\n *\n * It rides outside on the same terms as `styleProvenance`, `styleTokens` and\n * `ignoredBy`, by the same mechanism: `structureOf`, `styleOf` and `shapeOf`\n * project the fields they hash **by name**, so a field none of them names\n * cannot reach an identity. `holding.hash.test.ts` asserts that rather than\n * trusting it, because the cost of being wrong is every baseline in a\n * repository invalidating the first time a component held a timestamp.\n */\n readonly holding?: Holding;\n\n /**\n * `true` on the root of a subtree rendered through a portal.\n *\n * Recorded in the structure hash so that \"the dialog moved from inline to\n * portalled\" is a change rather than a coincidence of identical content, and\n * so the docket can say *where* something rendered, not only that it exists.\n */\n readonly portalled?: boolean;\n\n /**\n * Ignore rules this node was found under. Outside every hash, like its origin\n * on `RawNode`. See {@link SemanticSnapshot.ignoreSites}.\n */\n readonly ignoredBy?: readonly string[];\n\n /** What was not read about this node, carried from `RawNode.unread`. Outside every hash. */\n readonly unread?: string;\n\n readonly children: readonly SemanticNode[];\n}\n\n/**\n * A subtree the operator excluded, resolved to a path and a box (spec 0024).\n *\n * Lives in `format` rather than beside the rules that consume it, because it is\n * part of what a snapshot *is*: the collector produced it, it travels with the\n * document over every wire this project has, and `core/judge` is only its first\n * reader. A type the wire format needs cannot live in the policy layer.\n */\nexport interface IgnoreSite {\n /** Root of the excluded subtree, in this snapshot's own path space. */\n readonly path: NodePath;\n\n /** Which rule put it here, matched exactly against `IgnoreRule.id`. */\n readonly rule: string;\n\n /**\n * The box it occupied, for the raster tier.\n *\n * Absent under a profile with no layout — which is ADR-0002 rather than a\n * failure: a profile that cannot measure a box cannot decide pixels either, so\n * there is nothing for the box to subtract from.\n */\n readonly rect?: Rect;\n}\n\n/**\n * A component's own content, hashed per band (ADR-0018).\n *\n * In `format` rather than beside `hashComponents`, which computes it, because a\n * baseline carries these: a stored image plus the component hashes of the\n * document that painted it is what lets a later run tell the component that\n * *caused* a change from the components the change merely moved. That makes this\n * part of the wire format every store and every transport has to preserve, and a\n * type the wire needs cannot live in the layer that derives it.\n */\nexport interface ComponentHash {\n readonly component: string;\n\n /** Boundaries of this component in the subject, counted in document order. */\n readonly instances: number;\n\n /**\n * Tree shape: tags, structural aliases, portalling, allowlisted attributes,\n * and which child boundaries sit where. The `geometry` band's structural half.\n *\n * **Split out of a single `structure` digest on 2026-08-06.** It used to carry\n * the accessible semantics and the text too, and the fusion was invisible\n * until something needed to ask *which band moved* — at which point a baseline\n * could say \"this component changed\" and never say whether a heading was\n * renamed, a paragraph reworded, or a node inserted. Those are three different\n * bands, three different readers, and one of them is the band a route-level\n * test exists to ignore.\n */\n readonly structure: Digest;\n\n /**\n * Role, accessible name, and ARIA state. The `a11y` band.\n *\n * Its own digest because it is the band that must never be absorbed. Every\n * sensitivity level asserts on it, including the ones that ignore everything\n * else — and a level cannot assert on a band that is fused into another one.\n */\n readonly semantics: Digest;\n\n /** Text runs, in document order. The `content` band. */\n readonly text: Digest;\n\n readonly style: Digest;\n\n /**\n * Absent under a profile without layout — absent, never empty (ADR-0002).\n *\n * An empty geometry digest would compare equal between a run that observed no\n * movement and a run that could not observe movement at all, which is the\n * false `unchanged` this system must never produce.\n */\n readonly geometry?: Digest;\n\n /**\n * The box each instance occupies, in document order. Never hashed.\n *\n * A digest can say a component is not what it was and can never say by how\n * much. That is the gap a reviewer falls into: `Button — geometry, token` is\n * true of a control that lost a shadow and of one that grew eight pixels and\n * pushed the page down, and only the second explains why the subject is a\n * different height. Four numbers per instance close it.\n *\n * Absent for a baseline written before this existed and for a profile with no\n * layout — the same absence, and both mean *nobody measured*, never *it did\n * not move*. A `null` entry is a boundary the browser laid out nothing for,\n * kept in place so the list stays parallel to the instances it counts.\n */\n readonly boxes?: readonly (Rect | null)[];\n\n /**\n * Every distinct value the component's own nodes declared, per property, in\n * code-unit order; a custom property appears under its `--name` with the\n * values it resolved to. Never hashed.\n *\n * `boxes` answers *by how much*; this answers *which declaration*. `style`\n * moved says a `Button` is not styled as it was, and a reviewer holding only\n * that opens the file and diffs by eye. With both revisions' values the run\n * says `padding-left 8px → 14px`, which is the line somebody edited.\n *\n * Absent for a baseline written before this existed. That means *nobody\n * recorded it*, and the comparison says nothing about properties rather than\n * reporting every one of them as new.\n */\n readonly values?: Readonly<Record<string, readonly string[]>>;\n}\n\nexport interface StyleProvenanceEntry {\n readonly path: NodePath;\n readonly property: string;\n readonly sheet: string;\n /** Generated segments replaced by placeholders, so it can name a root safely. */\n readonly selector: string;\n readonly tokenName?: string;\n readonly source?: { readonly file: string; readonly line: number };\n}\n"]}
@@ -20,8 +20,8 @@ import type { Intervention, ScreenshotOptions, SettleTarget } from './interventi
20
20
  *
21
21
  * A recipe can be filtered by tier, so the structure-and-style rung waits for
22
22
  * nothing; composed from another project's tricks alongside these; digested, so
23
- * a baseline made under one recipe is `incomparable` with a run made under
24
- * another rather than `changed`; and checked for tricks that fight each other.
23
+ * a run under another recipe than its baseline names the recipe as the cause;
24
+ * and checked for tricks that fight each other.
25
25
  *
26
26
  * ## Where the damage lands
27
27
  *
@@ -52,6 +52,7 @@ import type { Intervention, ScreenshotOptions, SettleTarget } from './interventi
52
52
  * digest, and the vocabulary belongs beside the key it is part of.
53
53
  */
54
54
  export type { Intervention, ScreenshotOptions, SettleTarget, Trick } from './intervention.js';
55
+ export { waitForFonts, waitForImages } from './waits.js';
55
56
  /**
56
57
  * Hand animations to the browser rather than emulating the hold in CSS.
57
58
  *
@@ -111,8 +112,6 @@ export declare const hideScrollbars: Intervention;
111
112
  * `alt=""` image would hide real regressions under a green run.
112
113
  */
113
114
  export declare const hidePresentationalImages: Intervention;
114
- export declare const waitForFonts: Intervention;
115
- export declare const waitForImages: Intervention;
116
115
  export declare const INTERVENTIONS: readonly Intervention[];
117
116
  export type Recipe = readonly Intervention[];
118
117
  /**
@@ -124,6 +123,13 @@ export type Recipe = readonly Intervention[];
124
123
  * rung the tier ladder exists to make cheap.
125
124
  */
126
125
  export declare const SEMANTIC_RECIPE: Recipe;
126
+ /**
127
+ * What a document is painted under, and of {@link UNTOUCHED} only `no-focus`: a
128
+ * collected `autofocus` takes focus again. The other three have nothing but a
129
+ * carried script to act on: the printed date and the animation's place are in
130
+ * the document already, its `@media` rules were resolved when it was read, and
131
+ * nothing moves the pointer over it.
132
+ */
127
133
  export declare const LAYOUT_RECIPE: Recipe;
128
134
  export declare const RASTER_RECIPE: Recipe;
129
135
  /**
@@ -153,6 +159,12 @@ export declare const RASTER_RECIPE: Recipe;
153
159
  * the trade {@link SEMANTIC_RECIPE} refuses.
154
160
  */
155
161
  export declare const COLLECT_RECIPE: Recipe;
162
+ /**
163
+ * The ids of {@link COLLECT_RECIPE}, as an absent `stabilize` means them. A list
164
+ * replaces the recipe, so one trick is turned off with this list without it:
165
+ * `stabilize: DEFAULT_STABILIZE.filter((id) => id !== 'freeze-clock')`.
166
+ */
167
+ export declare const DEFAULT_STABILIZE: readonly string[];
156
168
  /**
157
169
  * Resolve a trick by the name a caller wrote down.
158
170
  *
@@ -174,6 +186,17 @@ export declare function interventionById(id: string): Intervention | undefined;
174
186
  * of the message.
175
187
  */
176
188
  export declare function recipeOf(ids: readonly string[]): Recipe;
189
+ /**
190
+ * The recipe a host that drives the browser applies, checked before it opens one:
191
+ * `stabilize` by id, or {@link COLLECT_RECIPE} when it is absent, with its clock
192
+ * fixed at `now`, which travels in the clock's id and lands in the digest. Throws
193
+ * on an unknown id, an instant with no zone, or a `now` with no clock to fix, so
194
+ * the operator hears about it before a page is loaded, not at the first subject.
195
+ */
196
+ export declare function recipeToDrive(options: {
197
+ readonly stabilize?: readonly string[];
198
+ readonly now?: string;
199
+ }): Recipe;
177
200
  /** Only the tricks a tier can observe the effect of. */
178
201
  export declare function forTier(recipe: Recipe, tier: Tier): Recipe;
179
202
  /**
@@ -196,7 +219,7 @@ export declare function settleRecipe(recipe: Recipe, target: SettleTarget): Prom
196
219
  *
197
220
  * Sorted, so the order tricks were composed in does not change the identity —
198
221
  * only which ones are present. Folded into `RenderIdentity`, which is what makes
199
- * a cross-recipe comparison report `incomparable` instead of inventing a
222
+ * a cross-recipe comparison name the recipe as its cause instead of inventing a
200
223
  * component to blame.
201
224
  */
202
225
  export declare function recipeDigest(recipe: Recipe): Digest;