@variance-authority/core 0.14.0 → 0.17.1

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 (147) hide show
  1. package/CHANGELOG.md +878 -0
  2. package/README.md +38 -17
  3. package/dist/attribute/component-hash.d.ts +14 -5
  4. package/dist/attribute/component-hash.js +14 -5
  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/movement.d.ts +1 -1
  13. package/dist/attribute/movement.js.map +1 -1
  14. package/dist/attribute/region.d.ts +47 -4
  15. package/dist/attribute/region.js +91 -22
  16. package/dist/attribute/region.js.map +1 -1
  17. package/dist/attribute/source.d.ts +10 -24
  18. package/dist/attribute/source.js +4 -38
  19. package/dist/attribute/source.js.map +1 -1
  20. package/dist/attribute/subject-pieces.d.ts +37 -0
  21. package/dist/attribute/subject-pieces.js +122 -0
  22. package/dist/attribute/subject-pieces.js.map +1 -0
  23. package/dist/compare/band.d.ts +8 -2
  24. package/dist/compare/band.js +7 -2
  25. package/dist/compare/band.js.map +1 -1
  26. package/dist/compare/diff/attribution.js +6 -4
  27. package/dist/compare/diff/attribution.js.map +1 -1
  28. package/dist/compare/diff/compare-nodes.js +9 -1
  29. package/dist/compare/diff/compare-nodes.js.map +1 -1
  30. package/dist/compare/diff/match.d.ts +2 -1
  31. package/dist/compare/diff/match.js +52 -0
  32. package/dist/compare/diff/match.js.map +1 -1
  33. package/dist/compare/parting.js +6 -4
  34. package/dist/compare/parting.js.map +1 -1
  35. package/dist/format/capture.d.ts +25 -0
  36. package/dist/format/capture.js +6 -0
  37. package/dist/format/capture.js.map +1 -1
  38. package/dist/format/document.d.ts +28 -5
  39. package/dist/format/document.js +8 -2
  40. package/dist/format/document.js.map +1 -1
  41. package/dist/format/drive.d.ts +191 -0
  42. package/dist/format/drive.js +343 -0
  43. package/dist/format/drive.js.map +1 -0
  44. package/dist/format/environment.d.ts +20 -2
  45. package/dist/format/environment.js +7 -0
  46. package/dist/format/environment.js.map +1 -1
  47. package/dist/format/index.d.ts +13 -3
  48. package/dist/format/index.js +8 -2
  49. package/dist/format/index.js.map +1 -1
  50. package/dist/format/intervention.d.ts +79 -0
  51. package/dist/format/intervention.js.map +1 -1
  52. package/dist/format/origin.d.ts +32 -0
  53. package/dist/format/origin.js +54 -0
  54. package/dist/format/origin.js.map +1 -0
  55. package/dist/format/packed.d.ts +32 -0
  56. package/dist/format/packed.js +23 -0
  57. package/dist/format/packed.js.map +1 -0
  58. package/dist/format/shape.d.ts +31 -0
  59. package/dist/format/shape.js +269 -0
  60. package/dist/format/shape.js.map +1 -0
  61. package/dist/format/snapshot.d.ts +2 -0
  62. package/dist/format/snapshot.js.map +1 -1
  63. package/dist/format/stabilize.d.ts +35 -6
  64. package/dist/format/stabilize.js +58 -103
  65. package/dist/format/stabilize.js.map +1 -1
  66. package/dist/format/steps.d.ts +44 -0
  67. package/dist/format/steps.js +67 -0
  68. package/dist/format/steps.js.map +1 -0
  69. package/dist/format/waits.d.ts +12 -0
  70. package/dist/format/waits.js +156 -0
  71. package/dist/format/waits.js.map +1 -0
  72. package/dist/format/width.d.ts +35 -0
  73. package/dist/format/width.js +38 -0
  74. package/dist/format/width.js.map +1 -0
  75. package/dist/index.d.ts +2 -1
  76. package/dist/index.js +2 -1
  77. package/dist/index.js.map +1 -1
  78. package/dist/judge/docket.js +1 -0
  79. package/dist/judge/docket.js.map +1 -1
  80. package/dist/judge/index.d.ts +1 -0
  81. package/dist/judge/index.js +1 -0
  82. package/dist/judge/index.js.map +1 -1
  83. package/dist/judge/restamp.d.ts +20 -0
  84. package/dist/judge/restamp.js +20 -0
  85. package/dist/judge/restamp.js.map +1 -0
  86. package/dist/nearest.d.ts +46 -0
  87. package/dist/nearest.js +102 -0
  88. package/dist/nearest.js.map +1 -0
  89. package/dist/relate/before.d.ts +19 -24
  90. package/dist/relate/before.js +37 -39
  91. package/dist/relate/before.js.map +1 -1
  92. package/dist/relate/beyond.d.ts +59 -0
  93. package/dist/relate/beyond.js +98 -0
  94. package/dist/relate/beyond.js.map +1 -0
  95. package/dist/relate/carried.d.ts +122 -0
  96. package/dist/relate/carried.js +279 -0
  97. package/dist/relate/carried.js.map +1 -0
  98. package/dist/relate/graph.d.ts +14 -12
  99. package/dist/relate/graph.js +12 -11
  100. package/dist/relate/graph.js.map +1 -1
  101. package/dist/relate/index.d.ts +3 -1
  102. package/dist/relate/index.js +3 -1
  103. package/dist/relate/index.js.map +1 -1
  104. package/dist/relate/merkle.js +2 -11
  105. package/dist/relate/merkle.js.map +1 -1
  106. package/dist/relate/narrow.d.ts +2 -2
  107. package/dist/relate/narrow.js +26 -73
  108. package/dist/relate/narrow.js.map +1 -1
  109. package/dist/relate/reach.d.ts +6 -6
  110. package/dist/relate/reach.js +10 -53
  111. package/dist/relate/reach.js.map +1 -1
  112. package/dist/relate/records.d.ts +6 -5
  113. package/dist/relate/records.js +3 -2
  114. package/dist/relate/records.js.map +1 -1
  115. package/dist/rules/index.d.ts +3 -3
  116. package/dist/rules/index.js +3 -3
  117. package/dist/rules/index.js.map +1 -1
  118. package/dist/rules/normalize/cascade.d.ts +12 -0
  119. package/dist/rules/normalize/cascade.js +34 -16
  120. package/dist/rules/normalize/cascade.js.map +1 -1
  121. package/dist/rules/normalize/fixture.d.ts +3 -0
  122. package/dist/rules/normalize/fixture.js +3 -1
  123. package/dist/rules/normalize/fixture.js.map +1 -1
  124. package/dist/rules/normalize/index.d.ts +2 -2
  125. package/dist/rules/normalize/index.js +9 -2
  126. package/dist/rules/normalize/index.js.map +1 -1
  127. package/dist/rules/normalize/value.d.ts +13 -2
  128. package/dist/rules/normalize/value.js +42 -8
  129. package/dist/rules/normalize/value.js.map +1 -1
  130. package/dist/rules/normalize/wrapper.js +5 -0
  131. package/dist/rules/normalize/wrapper.js.map +1 -1
  132. package/dist/rules/ruleset.d.ts +24 -4
  133. package/dist/rules/ruleset.js +24 -4
  134. package/dist/rules/ruleset.js.map +1 -1
  135. package/dist/segment/index.d.ts +20 -4
  136. package/dist/segment/index.js +28 -7
  137. package/dist/segment/index.js.map +1 -1
  138. package/dist/shard/index.d.ts +11 -0
  139. package/dist/shard/index.js +11 -0
  140. package/dist/shard/index.js.map +1 -0
  141. package/dist/shard/placement.d.ts +119 -0
  142. package/dist/shard/placement.js +156 -0
  143. package/dist/shard/placement.js.map +1 -0
  144. package/dist/share/index.d.ts +1 -1
  145. package/dist/share/index.js +1 -1
  146. package/dist/share/index.js.map +1 -1
  147. package/package.json +8 -1
@@ -1,5 +1,6 @@
1
1
  import { environmentKey } from '../../format/environment.js';
2
2
  import { digestCombine, digestValue } from '../../format/hash.js';
3
+ import { pageOrigin } from '../../format/origin.js';
3
4
  import { relativizeSource } from '../../format/provenance.js';
4
5
  import { ALLOWLIST_VERSION, RULESET_VERSION, admitsAttribute } from '../ruleset.js';
5
6
  import { aliasAttributeValue, aliasStyleValue, buildAliasMap } from './alias.js';
@@ -7,8 +8,8 @@ import { sitesIn, structureOf, styleOf } from './project.js';
7
8
  import { isInertWrapper } from './wrapper.js';
8
9
  import { resolveStyle, } from './cascade.js';
9
10
  export { buildAliasMap, aliasAttributeValue, aliasStyleValue } from './alias.js';
10
- export { resolveStyle, resolveVariables, INHERITED_PROPERTIES, EMPTY_CONTEXT, } from './cascade.js';
11
- export { canonicalizeValue, canonicalizeTokens, canonicalizeDimension } from './value.js';
11
+ export { declaredValue, resolveStyle, resolveVariables, INHERITED_PROPERTIES, EMPTY_CONTEXT, } from './cascade.js';
12
+ export { canonicalizeValue, canonicalizeTokens, canonicalizeDimension, matchingParen } from './value.js';
12
13
  export { canonicalizeColor, parseColor, formatColor } from './color.js';
13
14
  export { expandDeclaration, SHORTHAND_PROPERTIES } from './shorthand.js';
14
15
  export function normalize(capture, options = {}) {
@@ -44,6 +45,7 @@ export function normalize(capture, options = {}) {
44
45
  collapseWrappers,
45
46
  digestText,
46
47
  ...(sourceRoot !== undefined ? { sourceRoot } : {}),
48
+ ...withOrigin(pageOrigin(capture.baseUrl)),
47
49
  styleProvenance,
48
50
  declaredBy: new WeakMap(),
49
51
  };
@@ -128,6 +130,7 @@ function normalizeNode(capture, subtreeRoot, aliases, context, path, state) {
128
130
  ...(node.inlineStyle ? { inlineStyle: node.inlineStyle } : {}),
129
131
  ...(node.computedStyle ? { computedStyle: node.computedStyle } : {}),
130
132
  context: inherited,
133
+ ...(state.origin !== undefined ? { origin: state.origin } : {}),
131
134
  });
132
135
  const style = {};
133
136
  for (const [property, value] of Object.entries(resolved.style)) {
@@ -184,6 +187,7 @@ function normalizeNode(capture, subtreeRoot, aliases, context, path, state) {
184
187
  // they hash by name, so this reaches the snapshot without reaching the
185
188
  // identity — which is the whole contract an ignore is under.
186
189
  ...(node.ignoredBy && node.ignoredBy.length > 0 ? { ignoredBy: node.ignoredBy } : {}),
190
+ ...(node.unread !== undefined ? { unread: node.unread } : {}),
187
191
  children,
188
192
  };
189
193
  state.declaredBy.set(built, declaredHere);
@@ -248,6 +252,9 @@ function normalizeSelector(selector) {
248
252
  .replace(/\s+/g, ' ')
249
253
  .trim();
250
254
  }
255
+ function withOrigin(origin) {
256
+ return origin === undefined ? {} : { origin };
257
+ }
251
258
  /** The seed minus its custom properties: values that actually render. */
252
259
  function renderableOnly(seed) {
253
260
  const renderable = {};
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/normalize/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Diagnostic, RawCapture, RawNode } from '../../format/capture.js';\nimport { environmentKey } from '../../format/environment.js';\nimport { digestCombine, digestValue, type Digest } from '../../format/hash.js';\nimport { relativizeSource, type Provenance } from '../../format/provenance.js';\nimport { ALLOWLIST_VERSION, RULESET_VERSION, admitsAttribute } from '../ruleset.js';\nimport type { SemanticNode, SemanticSnapshot, StyleProvenanceEntry } from '../../format/snapshot.js';\nimport { aliasAttributeValue, aliasStyleValue, buildAliasMap, type AliasResult } from './alias.js';\nimport { sitesIn, structureOf, styleOf } from './project.js';\nimport { isInertWrapper } from './wrapper.js';\nimport {\n \n resolveStyle,\n type InheritContext,\n} from './cascade.js';\n\nexport { buildAliasMap, aliasAttributeValue, aliasStyleValue } from './alias.js';\nexport type { AliasMap, AliasResult } from './alias.js';\nexport {\n resolveStyle,\n resolveVariables,\n INHERITED_PROPERTIES,\n EMPTY_CONTEXT,\n} from './cascade.js';\nexport type { InheritContext, ResolvedStyle, DeclarationOrigin } from './cascade.js';\nexport { canonicalizeValue, canonicalizeTokens, canonicalizeDimension } from './value.js';\nexport { canonicalizeColor, parseColor, formatColor } from './color.js';\nexport { expandDeclaration, SHORTHAND_PROPERTIES } from './shorthand.js';\n\n/**\n * The normalization pipeline: `RawCapture` in, `SemanticSnapshot` out.\n *\n * There is exactly one of these, and both observation profiles enter it (ADR-0001).\n * That is what makes \"the same rules ran for JSDOM and Chromium\" a fact of the\n * call graph rather than a claim maintained by discipline.\n */\n\nexport interface NormalizeOptions {\n /**\n * Collapse `div`/`span` wrappers that carry no role, no attributes, and no\n * non-inherited styling. Defaults to on — CSS-in-JS and layout libraries emit\n * these by the dozen, and their arrival and departure during a refactor is the\n * single largest source of structural churn.\n */\n readonly collapseWrappers?: boolean;\n\n /** Replace text with its digest. For subjects whose copy is volatile by policy. */\n readonly digestText?: boolean;\n\n /**\n * Absolute directory the recorded JSX source locations are made relative to.\n *\n * The transform writes absolute paths, and a snapshot is a stored artifact\n * compared across machines. Normalizing here rather than at the reader is the\n * same reason everything else is normalized here: one pipeline, both profiles.\n */\n readonly sourceRoot?: string;\n}\n\nexport function normalize(capture: RawCapture, options: NormalizeOptions = {}): SemanticSnapshot {\n const { collapseWrappers = true, digestText = false, sourceRoot } = options;\n\n const portals = capture.portals ?? [];\n const aliases = buildAliasMap(capture.root, portals);\n const diagnostics: Diagnostic[] = [...capture.diagnostics];\n\n for (const id of aliases.dangling) {\n diagnostics.push({\n severity: 'warn',\n code: 'dangling-id-reference',\n // Reported rather than normalized away: an accessible-name reference\n // pointing outside the subject is either a genuine break or a subject\n // boundary drawn too tightly. Both need a human.\n message: `reference to id \"${id}\" resolves outside the subject subtree`,\n });\n }\n\n // Custom properties go into the resolution scope, never into `inherited`.\n //\n // The seed arrives with both mixed together, and injecting the whole thing as\n // inherited style put every ancestor-declared token into the subject root's\n // `style` map as if it were a rendered value. Two consequences, both bad: a\n // token nothing in the subject consumes still changed that subject's hash — so\n // editing any token invalidated every baseline in the repository — and the\n // resulting delta had no owner chain, producing an `unattributed` root on a\n // change that was perfectly well understood.\n const seed: InheritContext = {\n inherited: renderableOnly(capture.inheritedSeed),\n customProperties: customPropertiesOf(capture.inheritedSeed),\n };\n\n const styleProvenance: StyleProvenanceEntry[] = [];\n const state: WalkState = {\n collapseWrappers,\n digestText,\n ...(sourceRoot !== undefined ? { sourceRoot } : {}),\n styleProvenance,\n declaredBy: new WeakMap(),\n };\n const container = normalizeNode(capture, capture.root, aliases, seed, '0', state);\n\n // Portalled subtrees are appended as children of the subject root, each\n // flagged `portalled`. Appending rather than splicing in place keeps their\n // paths stable when DOM children are added or removed, and keeps them out of\n // the wrapper-collapse pass, which reasons about siblings.\n const root: SemanticNode =\n portals.length === 0\n ? container\n : {\n ...container,\n children: [\n ...container.children,\n ...portals.map((portal, index) => ({\n ...normalizeNode(\n capture,\n portal,\n aliases,\n { inherited: {}, customProperties: seed.customProperties },\n `0/portal:${index}`,\n state,\n ),\n portalled: true as const,\n })),\n ],\n };\n\n const structureHash = digestValue(structureOf(root));\n const styleHash = digestValue(styleOf(root, capture.profile.layout));\n\n // Resolved from the *finished* tree rather than during the walk. Wrapper\n // collapse re-paths whole subtrees, so a path recorded on the way down names a\n // node that may not be there on the way out — and an ignore that quietly moved\n // one level up absorbs a sibling nobody excluded.\n const ignoreSites = sitesIn(root);\n\n const environment = environmentKey({\n ...capture.environment,\n ruleset: RULESET_VERSION,\n allowlist: ALLOWLIST_VERSION,\n });\n\n return {\n formatVersion: 1,\n subject: capture.subject,\n profile: capture.profile,\n environment,\n // Keyed on the *semantic* digest, not the full one. A snapshot is a semantic\n // artifact, so binding it to inputs that only rasterization can observe would\n // split one baseline across machines that render it identically.\n renderHash: digestCombine('render', [environment.semanticDigest, structureHash, styleHash]),\n structureHash,\n styleHash,\n root,\n styleProvenance,\n ...(ignoreSites.length > 0 ? { ignoreSites } : {}),\n diagnostics,\n };\n}\n\ninterface WalkState {\n readonly collapseWrappers: boolean;\n readonly digestText: boolean;\n readonly sourceRoot?: string;\n readonly styleProvenance: StyleProvenanceEntry[];\n /**\n * Properties each node declared *itself*, as opposed to inheriting.\n *\n * The wrapper-inertness test needs this distinction and cannot recover it from\n * the finished node: by then an inherited `color` and a declared `color` are\n * the same entry in `style`. Keyed by node identity rather than threaded\n * through the return type, so the recursive shape stays a plain tree walk.\n */\n readonly declaredBy: WeakMap<SemanticNode, ReadonlySet<string>>;\n}\n\n/**\n * Provenance carrying a path this machine can quote back to a repository, and\n * carrying nothing that belongs to the machine it was captured on.\n *\n * The location arrives from the page, where nothing knows what the repository\n * root is; the collector does, and this is the one point every node passes\n * through afterwards.\n *\n * **`stack` rides through, unspent and unrooted.** Frames hold absolute URLs\n * with a dev server's port in them, which would make them machine-specific if\n * anything compared or stored them — and nothing does. `structureOf` and\n * `styleOf` in `packages/core/src/rules/normalize/project.ts` are whitelists\n * and neither admits provenance at all, so a frame changes no digest and\n * disagrees with no baseline; and a snapshot itself never reaches disk, because\n * the history record writes the hashes it folds out of one. What a frame is, is\n * a fetch nobody has asked for yet, kept against the moment a region or a\n * finding names this node — which is where `locateSites` spends it.\n *\n * Rooting a frame here would be the wrong move even so. A frame's URL is what\n * the *fetch* needs, and a repository-relative path is not fetchable.\n */\nfunction rooted(provenance: Provenance, root: string | undefined): Provenance {\n if (root === undefined || provenance.source === undefined) return provenance;\n\n const source = relativizeSource(provenance.source, root);\n return source === provenance.source ? provenance : { ...provenance, source };\n}\n\nfunction normalizeNode(\n capture: RawCapture,\n subtreeRoot: RawNode,\n aliases: AliasResult,\n context: InheritContext,\n path: string,\n state: WalkState,\n): SemanticNode {\n return build(subtreeRoot, context, path);\n\n function build(node: RawNode, inherited: InheritContext, nodePath: string): SemanticNode {\n const resolved = resolveStyle({\n matchedRules: node.matchedRules,\n ...(node.inlineStyle ? { inlineStyle: node.inlineStyle } : {}),\n ...(node.computedStyle ? { computedStyle: node.computedStyle } : {}),\n context: inherited,\n });\n\n const style: Record<string, string> = {};\n for (const [property, value] of Object.entries(resolved.style)) {\n style[property] = aliasStyleValue(aliases, value);\n }\n\n for (const [property, origin] of Object.entries(resolved.origins)) {\n state.styleProvenance.push({\n path: nodePath,\n property,\n sheet: origin.sheet,\n selector: normalizeSelector(origin.selector),\n ...(origin.tokenName ? { tokenName: origin.tokenName } : {}),\n ...(origin.source ? { source: origin.source } : {}),\n });\n }\n\n const attributes: Record<string, string> = {};\n for (const [name, value] of Object.entries(node.attributes)) {\n if (!admitsAttribute(name)) continue;\n attributes[name] = aliasAttributeValue(aliases, name, value);\n }\n\n const rawId = node.attributes['id'];\n const alias = rawId !== undefined ? aliases.local.get(rawId) : undefined;\n\n const children = collectChildren(node, resolved.childContext, nodePath);\n const declaredHere = new Set(Object.keys(resolved.origins));\n\n const text =\n node.text === undefined\n ? undefined\n : state.digestText\n ? digestValue(node.text)\n : normalizeText(node.text);\n\n const built: SemanticNode = {\n path: nodePath,\n tag: node.tag.toLowerCase(),\n ...(alias !== undefined ? { alias } : {}),\n ...(node.aria?.role ? { role: node.aria.role } : {}),\n ...(node.aria?.name ? { name: node.aria.name } : {}),\n ...(node.aria?.description ? { description: node.aria.description } : {}),\n ...(node.aria && Object.keys(node.aria.state).length > 0 ? { state: node.aria.state } : {}),\n attributes,\n style,\n ...(Object.keys(resolved.tokens).length > 0 ? { tokens: resolved.tokens } : {}),\n ...(Object.keys(resolved.propertyTokens).length > 0\n ? { styleTokens: resolved.propertyTokens }\n : {}),\n ...(capture.profile.layout && node.rect ? { rect: node.rect } : {}),\n ...(text !== undefined ? { text } : {}),\n ...(node.provenance ? { provenance: rooted(node.provenance, state.sourceRoot) } : {}),\n ...(node.wiring ? { wiring: node.wiring } : {}),\n // Carried and never projected, like `ignoredBy` below it. This is the\n // only field on a node that holds values rather than shapes, and the\n // reason it may is that nothing downstream hashes it.\n ...(node.holding ? { holding: node.holding } : {}),\n // Carried, never acted on. `structureOf` and `styleOf` project the fields\n // they hash by name, so this reaches the snapshot without reaching the\n // identity — which is the whole contract an ignore is under.\n ...(node.ignoredBy && node.ignoredBy.length > 0 ? { ignoredBy: node.ignoredBy } : {}),\n children,\n };\n\n state.declaredBy.set(built, declaredHere);\n return built;\n }\n\n function collectChildren(\n node: RawNode,\n childContext: InheritContext,\n nodePath: string,\n ): readonly SemanticNode[] {\n const source = [...node.children, ...(node.shadowChildren ?? [])];\n const built: SemanticNode[] = [];\n\n for (const child of source) {\n const candidate = build(child, childContext, `${nodePath}/${built.length}`);\n\n // A collapsed wrapper is replaced by its children in place. Re-pathing them\n // is why collapse must happen during the walk rather than as a later pass:\n // paths must reflect the final tree, or every downstream reference is stale.\n if (state.collapseWrappers && isInertWrapper(child, candidate, state.declaredBy)) {\n for (const grandchild of candidate.children) {\n built.push(repath(grandchild, `${nodePath}/${built.length}`));\n }\n continue;\n }\n\n built.push(candidate);\n }\n\n return built;\n }\n}\n\nfunction repath(node: SemanticNode, path: string): SemanticNode {\n return {\n ...node,\n path,\n children: node.children.map((child, index) => repath(child, `${path}/${index}`)),\n };\n}\n\n\n\n/**\n * Collapse runs of whitespace to a single space, without trimming.\n *\n * Collapsing matches what `white-space: normal` does, so indentation depth is\n * not a hash input. Trimming does not: a *boundary* space between inline\n * elements is rendered, and removing it changes \"a b\" to \"ab\". Since the\n * formatting context is unknowable without layout, the space is kept —\n * over-reporting in a block context, rather than reporting a visible text change\n * as `unchanged`.\n *\n * This costs little in practice because subjects are React-rendered, and JSX\n * already strips whitespace-only lines and newline-adjacent indentation at\n * compile time. Hand-written HTML pays more.\n */\nfunction normalizeText(text: string): string {\n return text.replace(/\\s+/g, ' ');\n}\n\n/**\n * Replace generated segments in selector text with a placeholder.\n *\n * Selectors reach the snapshot only through the attribution side-channel, which\n * is outside the hash — but a selector naming `css-1a2b3c` is still useless in a\n * docket, because the name changes whenever any declaration in its file changes.\n * The placeholder keeps the shape, which is what identifies the rule to a reader.\n */\nfunction normalizeSelector(selector: string): string {\n return selector\n .replace(/\\.(?:css|sc|emotion)-[a-z0-9]{4,}/gi, '.«generated»')\n .replace(/\\.[\\w-]*__[a-z0-9]{5,}\\b/gi, '.«generated»')\n .replace(/\\[data-(?:styled|emotion|v)-[^\\]]*\\]/gi, '[«generated»]')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\n/** The seed minus its custom properties: values that actually render. */\nfunction renderableOnly(seed: Readonly<Record<string, string>>): Record<string, string> {\n const renderable: Record<string, string> = {};\n for (const [property, value] of Object.entries(seed)) {\n if (!property.startsWith('--')) renderable[property] = value;\n }\n return renderable;\n}\n\nfunction customPropertiesOf(seed: Readonly<Record<string, string>>): Record<string, string> {\n const custom: Record<string, string> = {};\n for (const [property, value] of Object.entries(seed)) {\n if (property.startsWith('--')) custom[property] = value;\n }\n return custom;\n}\n\nexport type { Digest };\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/rules/normalize/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Diagnostic, RawCapture, RawNode } from '../../format/capture.js';\nimport { environmentKey } from '../../format/environment.js';\nimport { digestCombine, digestValue, type Digest } from '../../format/hash.js';\nimport { pageOrigin } from '../../format/origin.js';\nimport { relativizeSource, type Provenance } from '../../format/provenance.js';\nimport { ALLOWLIST_VERSION, RULESET_VERSION, admitsAttribute } from '../ruleset.js';\nimport type { SemanticNode, SemanticSnapshot, StyleProvenanceEntry } from '../../format/snapshot.js';\nimport { aliasAttributeValue, aliasStyleValue, buildAliasMap, type AliasResult } from './alias.js';\nimport { sitesIn, structureOf, styleOf } from './project.js';\nimport { isInertWrapper } from './wrapper.js';\nimport {\n \n resolveStyle,\n type InheritContext,\n} from './cascade.js';\n\nexport { buildAliasMap, aliasAttributeValue, aliasStyleValue } from './alias.js';\nexport type { AliasMap, AliasResult } from './alias.js';\nexport {\n declaredValue,\n resolveStyle,\n resolveVariables,\n INHERITED_PROPERTIES,\n EMPTY_CONTEXT,\n} from './cascade.js';\nexport type { InheritContext, ResolvedStyle, DeclarationOrigin } from './cascade.js';\nexport { canonicalizeValue, canonicalizeTokens, canonicalizeDimension, matchingParen } from './value.js';\nexport { canonicalizeColor, parseColor, formatColor } from './color.js';\nexport { expandDeclaration, SHORTHAND_PROPERTIES } from './shorthand.js';\n\n/**\n * The normalization pipeline: `RawCapture` in, `SemanticSnapshot` out.\n *\n * There is exactly one of these, and both observation profiles enter it (ADR-0001).\n * That is what makes \"the same rules ran for JSDOM and Chromium\" a fact of the\n * call graph rather than a claim maintained by discipline.\n */\n\nexport interface NormalizeOptions {\n /**\n * Collapse `div`/`span` wrappers that carry no role, no attributes, and no\n * non-inherited styling. Defaults to on — CSS-in-JS and layout libraries emit\n * these by the dozen, and their arrival and departure during a refactor is the\n * single largest source of structural churn.\n */\n readonly collapseWrappers?: boolean;\n\n /** Replace text with its digest. For subjects whose copy is volatile by policy. */\n readonly digestText?: boolean;\n\n /**\n * Absolute directory the recorded JSX source locations are made relative to.\n *\n * The transform writes absolute paths, and a snapshot is a stored artifact\n * compared across machines. Normalizing here rather than at the reader is the\n * same reason everything else is normalized here: one pipeline, both profiles.\n */\n readonly sourceRoot?: string;\n}\n\nexport function normalize(capture: RawCapture, options: NormalizeOptions = {}): SemanticSnapshot {\n const { collapseWrappers = true, digestText = false, sourceRoot } = options;\n\n const portals = capture.portals ?? [];\n const aliases = buildAliasMap(capture.root, portals);\n const diagnostics: Diagnostic[] = [...capture.diagnostics];\n\n for (const id of aliases.dangling) {\n diagnostics.push({\n severity: 'warn',\n code: 'dangling-id-reference',\n // Reported rather than normalized away: an accessible-name reference\n // pointing outside the subject is either a genuine break or a subject\n // boundary drawn too tightly. Both need a human.\n message: `reference to id \"${id}\" resolves outside the subject subtree`,\n });\n }\n\n // Custom properties go into the resolution scope, never into `inherited`.\n //\n // The seed arrives with both mixed together, and injecting the whole thing as\n // inherited style put every ancestor-declared token into the subject root's\n // `style` map as if it were a rendered value. Two consequences, both bad: a\n // token nothing in the subject consumes still changed that subject's hash — so\n // editing any token invalidated every baseline in the repository — and the\n // resulting delta had no owner chain, producing an `unattributed` root on a\n // change that was perfectly well understood.\n const seed: InheritContext = {\n inherited: renderableOnly(capture.inheritedSeed),\n customProperties: customPropertiesOf(capture.inheritedSeed),\n };\n\n const styleProvenance: StyleProvenanceEntry[] = [];\n const state: WalkState = {\n collapseWrappers,\n digestText,\n ...(sourceRoot !== undefined ? { sourceRoot } : {}),\n ...withOrigin(pageOrigin(capture.baseUrl)),\n styleProvenance,\n declaredBy: new WeakMap(),\n };\n const container = normalizeNode(capture, capture.root, aliases, seed, '0', state);\n\n // Portalled subtrees are appended as children of the subject root, each\n // flagged `portalled`. Appending rather than splicing in place keeps their\n // paths stable when DOM children are added or removed, and keeps them out of\n // the wrapper-collapse pass, which reasons about siblings.\n const root: SemanticNode =\n portals.length === 0\n ? container\n : {\n ...container,\n children: [\n ...container.children,\n ...portals.map((portal, index) => ({\n ...normalizeNode(\n capture,\n portal,\n aliases,\n { inherited: {}, customProperties: seed.customProperties },\n `0/portal:${index}`,\n state,\n ),\n portalled: true as const,\n })),\n ],\n };\n\n const structureHash = digestValue(structureOf(root));\n const styleHash = digestValue(styleOf(root, capture.profile.layout));\n\n // Resolved from the *finished* tree rather than during the walk. Wrapper\n // collapse re-paths whole subtrees, so a path recorded on the way down names a\n // node that may not be there on the way out — and an ignore that quietly moved\n // one level up absorbs a sibling nobody excluded.\n const ignoreSites = sitesIn(root);\n\n const environment = environmentKey({\n ...capture.environment,\n ruleset: RULESET_VERSION,\n allowlist: ALLOWLIST_VERSION,\n });\n\n return {\n formatVersion: 1,\n subject: capture.subject,\n profile: capture.profile,\n environment,\n // Keyed on the *semantic* digest, not the full one. A snapshot is a semantic\n // artifact, so binding it to inputs that only rasterization can observe would\n // split one baseline across machines that render it identically.\n renderHash: digestCombine('render', [environment.semanticDigest, structureHash, styleHash]),\n structureHash,\n styleHash,\n root,\n styleProvenance,\n ...(ignoreSites.length > 0 ? { ignoreSites } : {}),\n diagnostics,\n };\n}\n\ninterface WalkState {\n readonly collapseWrappers: boolean;\n readonly digestText: boolean;\n readonly sourceRoot?: string;\n /** The capture's origin, from `baseUrl`; absent for an opaque one. */\n readonly origin?: string;\n readonly styleProvenance: StyleProvenanceEntry[];\n /**\n * Properties each node declared *itself*, as opposed to inheriting.\n *\n * The wrapper-inertness test needs this distinction and cannot recover it from\n * the finished node: by then an inherited `color` and a declared `color` are\n * the same entry in `style`. Keyed by node identity rather than threaded\n * through the return type, so the recursive shape stays a plain tree walk.\n */\n readonly declaredBy: WeakMap<SemanticNode, ReadonlySet<string>>;\n}\n\n/**\n * Provenance carrying a path this machine can quote back to a repository, and\n * carrying nothing that belongs to the machine it was captured on.\n *\n * The location arrives from the page, where nothing knows what the repository\n * root is; the collector does, and this is the one point every node passes\n * through afterwards.\n *\n * **`stack` rides through, unspent and unrooted.** Frames hold absolute URLs\n * with a dev server's port in them, which would make them machine-specific if\n * anything compared or stored them — and nothing does. `structureOf` and\n * `styleOf` in `packages/core/src/rules/normalize/project.ts` are whitelists\n * and neither admits provenance at all, so a frame changes no digest and\n * disagrees with no baseline; and a snapshot itself never reaches disk, because\n * the history record writes the hashes it folds out of one. What a frame is, is\n * a fetch nobody has asked for yet, kept against the moment a region or a\n * finding names this node — which is where `locateSites` spends it.\n *\n * Rooting a frame here would be the wrong move even so. A frame's URL is what\n * the *fetch* needs, and a repository-relative path is not fetchable.\n */\nfunction rooted(provenance: Provenance, root: string | undefined): Provenance {\n if (root === undefined || provenance.source === undefined) return provenance;\n\n const source = relativizeSource(provenance.source, root);\n return source === provenance.source ? provenance : { ...provenance, source };\n}\n\nfunction normalizeNode(\n capture: RawCapture,\n subtreeRoot: RawNode,\n aliases: AliasResult,\n context: InheritContext,\n path: string,\n state: WalkState,\n): SemanticNode {\n return build(subtreeRoot, context, path);\n\n function build(node: RawNode, inherited: InheritContext, nodePath: string): SemanticNode {\n const resolved = resolveStyle({\n matchedRules: node.matchedRules,\n ...(node.inlineStyle ? { inlineStyle: node.inlineStyle } : {}),\n ...(node.computedStyle ? { computedStyle: node.computedStyle } : {}),\n context: inherited,\n ...(state.origin !== undefined ? { origin: state.origin } : {}),\n });\n\n const style: Record<string, string> = {};\n for (const [property, value] of Object.entries(resolved.style)) {\n style[property] = aliasStyleValue(aliases, value);\n }\n\n for (const [property, origin] of Object.entries(resolved.origins)) {\n state.styleProvenance.push({\n path: nodePath,\n property,\n sheet: origin.sheet,\n selector: normalizeSelector(origin.selector),\n ...(origin.tokenName ? { tokenName: origin.tokenName } : {}),\n ...(origin.source ? { source: origin.source } : {}),\n });\n }\n\n const attributes: Record<string, string> = {};\n for (const [name, value] of Object.entries(node.attributes)) {\n if (!admitsAttribute(name)) continue;\n attributes[name] = aliasAttributeValue(aliases, name, value);\n }\n\n const rawId = node.attributes['id'];\n const alias = rawId !== undefined ? aliases.local.get(rawId) : undefined;\n\n const children = collectChildren(node, resolved.childContext, nodePath);\n const declaredHere = new Set(Object.keys(resolved.origins));\n\n const text =\n node.text === undefined\n ? undefined\n : state.digestText\n ? digestValue(node.text)\n : normalizeText(node.text);\n\n const built: SemanticNode = {\n path: nodePath,\n tag: node.tag.toLowerCase(),\n ...(alias !== undefined ? { alias } : {}),\n ...(node.aria?.role ? { role: node.aria.role } : {}),\n ...(node.aria?.name ? { name: node.aria.name } : {}),\n ...(node.aria?.description ? { description: node.aria.description } : {}),\n ...(node.aria && Object.keys(node.aria.state).length > 0 ? { state: node.aria.state } : {}),\n attributes,\n style,\n ...(Object.keys(resolved.tokens).length > 0 ? { tokens: resolved.tokens } : {}),\n ...(Object.keys(resolved.propertyTokens).length > 0\n ? { styleTokens: resolved.propertyTokens }\n : {}),\n ...(capture.profile.layout && node.rect ? { rect: node.rect } : {}),\n ...(text !== undefined ? { text } : {}),\n ...(node.provenance ? { provenance: rooted(node.provenance, state.sourceRoot) } : {}),\n ...(node.wiring ? { wiring: node.wiring } : {}),\n // Carried and never projected, like `ignoredBy` below it. This is the\n // only field on a node that holds values rather than shapes, and the\n // reason it may is that nothing downstream hashes it.\n ...(node.holding ? { holding: node.holding } : {}),\n // Carried, never acted on. `structureOf` and `styleOf` project the fields\n // they hash by name, so this reaches the snapshot without reaching the\n // identity — which is the whole contract an ignore is under.\n ...(node.ignoredBy && node.ignoredBy.length > 0 ? { ignoredBy: node.ignoredBy } : {}),\n ...(node.unread !== undefined ? { unread: node.unread } : {}),\n children,\n };\n\n state.declaredBy.set(built, declaredHere);\n return built;\n }\n\n function collectChildren(\n node: RawNode,\n childContext: InheritContext,\n nodePath: string,\n ): readonly SemanticNode[] {\n const source = [...node.children, ...(node.shadowChildren ?? [])];\n const built: SemanticNode[] = [];\n\n for (const child of source) {\n const candidate = build(child, childContext, `${nodePath}/${built.length}`);\n\n // A collapsed wrapper is replaced by its children in place. Re-pathing them\n // is why collapse must happen during the walk rather than as a later pass:\n // paths must reflect the final tree, or every downstream reference is stale.\n if (state.collapseWrappers && isInertWrapper(child, candidate, state.declaredBy)) {\n for (const grandchild of candidate.children) {\n built.push(repath(grandchild, `${nodePath}/${built.length}`));\n }\n continue;\n }\n\n built.push(candidate);\n }\n\n return built;\n }\n}\n\nfunction repath(node: SemanticNode, path: string): SemanticNode {\n return {\n ...node,\n path,\n children: node.children.map((child, index) => repath(child, `${path}/${index}`)),\n };\n}\n\n\n\n/**\n * Collapse runs of whitespace to a single space, without trimming.\n *\n * Collapsing matches what `white-space: normal` does, so indentation depth is\n * not a hash input. Trimming does not: a *boundary* space between inline\n * elements is rendered, and removing it changes \"a b\" to \"ab\". Since the\n * formatting context is unknowable without layout, the space is kept —\n * over-reporting in a block context, rather than reporting a visible text change\n * as `unchanged`.\n *\n * This costs little in practice because subjects are React-rendered, and JSX\n * already strips whitespace-only lines and newline-adjacent indentation at\n * compile time. Hand-written HTML pays more.\n */\nfunction normalizeText(text: string): string {\n return text.replace(/\\s+/g, ' ');\n}\n\n/**\n * Replace generated segments in selector text with a placeholder.\n *\n * Selectors reach the snapshot only through the attribution side-channel, which\n * is outside the hash — but a selector naming `css-1a2b3c` is still useless in a\n * docket, because the name changes whenever any declaration in its file changes.\n * The placeholder keeps the shape, which is what identifies the rule to a reader.\n */\nfunction normalizeSelector(selector: string): string {\n return selector\n .replace(/\\.(?:css|sc|emotion)-[a-z0-9]{4,}/gi, '.«generated»')\n .replace(/\\.[\\w-]*__[a-z0-9]{5,}\\b/gi, '.«generated»')\n .replace(/\\[data-(?:styled|emotion|v)-[^\\]]*\\]/gi, '[«generated»]')\n .replace(/\\s+/g, ' ')\n .trim();\n}\n\nfunction withOrigin(origin: string | undefined): { readonly origin?: string } {\n return origin === undefined ? {} : { origin };\n}\n\n/** The seed minus its custom properties: values that actually render. */\nfunction renderableOnly(seed: Readonly<Record<string, string>>): Record<string, string> {\n const renderable: Record<string, string> = {};\n for (const [property, value] of Object.entries(seed)) {\n if (!property.startsWith('--')) renderable[property] = value;\n }\n return renderable;\n}\n\nfunction customPropertiesOf(seed: Readonly<Record<string, string>>): Record<string, string> {\n const custom: Record<string, string> = {};\n for (const [property, value] of Object.entries(seed)) {\n if (property.startsWith('--')) custom[property] = value;\n }\n return custom;\n}\n\nexport type { Digest };\n"]}
@@ -8,7 +8,12 @@
8
8
  * size would fabricate precision the tier does not have.
9
9
  */
10
10
  export declare const RELATIVE_UNITS: readonly string[];
11
- export declare function canonicalizeValue(property: string, value: string): string;
11
+ /**
12
+ * `origin` is the page's own, `https://shop.example` with no trailing slash. A
13
+ * `url()` it serves is written as its path, so the port a build was served on
14
+ * is not part of the value.
15
+ */
16
+ export declare function canonicalizeValue(property: string, value: string, origin?: string): string;
12
17
  /**
13
18
  * Rewrite a value token by token.
14
19
  *
@@ -16,8 +21,14 @@ export declare function canonicalizeValue(property: string, value: string): stri
16
21
  * family name and a resource path are opaque data, and normalizing their case
17
22
  * would change what they refer to.
18
23
  */
19
- export declare function canonicalizeTokens(value: string): string;
24
+ export declare function canonicalizeTokens(value: string, origin?: string): string;
20
25
  /** Canonicalize one `<number><unit>` token. */
21
26
  export declare function canonicalizeDimension(token: string): string;
22
27
  export declare function isRelativeUnit(unit: string): boolean;
28
+ /**
29
+ * The index of the `)` that closes the `(` at `openIndex`, or `input.length`
30
+ * when nothing closes it. A parenthesis inside a quoted string is text, and a
31
+ * backslash escapes the character after it inside one.
32
+ */
33
+ export declare function matchingParen(input: string, openIndex: number): number;
23
34
  //# sourceMappingURL=value.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { withoutOrigin } from '../../format/origin.js';
1
2
  import { canonicalizeColor, isColorProperty } from './color.js';
2
3
  /**
3
4
  * Declaration-value canonicalization.
@@ -45,10 +46,15 @@ export const RELATIVE_UNITS = [
45
46
  ];
46
47
  /** Decimal places kept for a length. Absorbs float noise, keeps sub-pixel intent. */
47
48
  const LENGTH_PRECISION = 4;
48
- export function canonicalizeValue(property, value) {
49
+ /**
50
+ * `origin` is the page's own, `https://shop.example` with no trailing slash. A
51
+ * `url()` it serves is written as its path, so the port a build was served on
52
+ * is not part of the value.
53
+ */
54
+ export function canonicalizeValue(property, value, origin) {
49
55
  if (isColorProperty(property))
50
56
  return canonicalizeColor(value);
51
- return canonicalizeTokens(value);
57
+ return canonicalizeTokens(value, origin);
52
58
  }
53
59
  /**
54
60
  * Rewrite a value token by token.
@@ -57,7 +63,7 @@ export function canonicalizeValue(property, value) {
57
63
  * family name and a resource path are opaque data, and normalizing their case
58
64
  * would change what they refer to.
59
65
  */
60
- export function canonicalizeTokens(value) {
66
+ export function canonicalizeTokens(value, origin) {
61
67
  const out = [];
62
68
  let index = 0;
63
69
  const input = value.trim();
@@ -77,7 +83,7 @@ export function canonicalizeTokens(value) {
77
83
  }
78
84
  if (input.slice(index).toLowerCase().startsWith('url(')) {
79
85
  const end = matchingParen(input, index + 3);
80
- out.push(`url${input.slice(index + 3, end + 1)}`);
86
+ out.push(sameOriginPath(`url${input.slice(index + 3, end + 1)}`, origin));
81
87
  index = end + 1;
82
88
  continue;
83
89
  }
@@ -104,6 +110,17 @@ export function canonicalizeTokens(value) {
104
110
  }
105
111
  return out.join('').trim();
106
112
  }
113
+ /** One `url(…)` token, with the page's own origin dropped from its payload. */
114
+ function sameOriginPath(token, origin) {
115
+ if (origin === undefined)
116
+ return token;
117
+ const match = /^url\(\s*(["']?)(.*?)\1\s*\)$/s.exec(token);
118
+ const url = match?.[2];
119
+ if (url === undefined)
120
+ return token;
121
+ const path = withoutOrigin(url, origin);
122
+ return path === url ? token : `url(${match[1]}${path}${match[1]})`;
123
+ }
107
124
  /** Canonicalize one `<number><unit>` token. */
108
125
  export function canonicalizeDimension(token) {
109
126
  const match = /^([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)([a-z%]*)$/i.exec(token);
@@ -146,17 +163,34 @@ function closingQuote(input, start) {
146
163
  }
147
164
  return input.length - 1;
148
165
  }
149
- function matchingParen(input, openIndex) {
166
+ /**
167
+ * The index of the `)` that closes the `(` at `openIndex`, or `input.length`
168
+ * when nothing closes it. A parenthesis inside a quoted string is text, and a
169
+ * backslash escapes the character after it inside one.
170
+ */
171
+ export function matchingParen(input, openIndex) {
150
172
  let depth = 0;
173
+ let quote;
151
174
  for (let i = openIndex; i < input.length; i += 1) {
152
- if (input[i] === '(')
175
+ const char = input[i];
176
+ if (quote !== undefined) {
177
+ if (char === '\\')
178
+ i += 1;
179
+ else if (char === quote)
180
+ quote = undefined;
181
+ }
182
+ else if (char === '"' || char === "'") {
183
+ quote = char;
184
+ }
185
+ else if (char === '(') {
153
186
  depth += 1;
154
- else if (input[i] === ')') {
187
+ }
188
+ else if (char === ')') {
155
189
  depth -= 1;
156
190
  if (depth === 0)
157
191
  return i;
158
192
  }
159
193
  }
160
- return input.length - 1;
194
+ return input.length;
161
195
  }
162
196
  //# sourceMappingURL=value.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"value.js","sourceRoot":"","sources":["../../../src/rules/normalize/value.ts"],"names":[],"mappings":"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","sourcesContent":["import { canonicalizeColor, isColorProperty } from './color.js';\n\n/**\n * Declaration-value canonicalization.\n *\n * Every value entering a snapshot passes through here. The job is to erase every\n * way CSS lets an author write the same thing differently — spacing, casing,\n * absolute unit choice, float formatting — so that reformatting a stylesheet is\n * not a mass-invalidation event.\n *\n * The discipline is one-directional: collapse only differences that provably\n * cannot reach a pixel. Anything that *might* render differently is left alone,\n * because an over-eager equivalence here produces a false `unchanged`, and the\n * cost of the opposite mistake is only a review that did not need to happen.\n */\n\n/** Absolute lengths, in CSS px. Fixed by spec, so conversion is lossless. */\nconst ABSOLUTE_LENGTHS: Readonly<Record<string, number>> = {\n px: 1,\n pt: 96 / 72,\n pc: 16,\n in: 96,\n cm: 96 / 2.54,\n mm: 96 / 25.4,\n q: 96 / 101.6,\n};\n\n/** Angles, in degrees. */\nconst ANGLES: Readonly<Record<string, number>> = {\n deg: 1,\n grad: 0.9,\n rad: 180 / Math.PI,\n turn: 360,\n};\n\n/**\n * Units left symbolic because they cannot be resolved without a layout engine.\n *\n * This is the `declared-only` boundary made concrete (ADR-0002): under a profile\n * without computed style, `padding: 1rem` stays `1rem`. That is honest — it is\n * genuinely all that tier knows — and it is why JSDOM decides the token band\n * `declared-only` rather than `full`. Resolving these by assuming a root font\n * size would fabricate precision the tier does not have.\n */\nexport const RELATIVE_UNITS: readonly string[] = [\n 'em', 'rem', 'ex', 'ch', 'cap', 'ic', 'lh', 'rlh',\n 'vw', 'vh', 'vmin', 'vmax', 'vi', 'vb',\n 'svw', 'svh', 'lvw', 'lvh', 'dvw', 'dvh', '%',\n];\n\n/** Decimal places kept for a length. Absorbs float noise, keeps sub-pixel intent. */\nconst LENGTH_PRECISION = 4;\n\nexport function canonicalizeValue(property: string, value: string): string {\n if (isColorProperty(property)) return canonicalizeColor(value);\n return canonicalizeTokens(value);\n}\n\n/**\n * Rewrite a value token by token.\n *\n * Quoted strings and `url()` payloads are passed through untouched: a font\n * family name and a resource path are opaque data, and normalizing their case\n * would change what they refer to.\n */\nexport function canonicalizeTokens(value: string): string {\n const out: string[] = [];\n let index = 0;\n const input = value.trim();\n\n while (index < input.length) {\n const char = input[index]!;\n\n if (/\\s/.test(char)) {\n if (out.length > 0 && out[out.length - 1] !== ' ') out.push(' ');\n index += 1;\n continue;\n }\n\n if (char === '\"' || char === \"'\") {\n const end = closingQuote(input, index);\n out.push(input.slice(index, end + 1));\n index = end + 1;\n continue;\n }\n\n if (input.slice(index).toLowerCase().startsWith('url(')) {\n const end = matchingParen(input, index + 3);\n out.push(`url${input.slice(index + 3, end + 1)}`);\n index = end + 1;\n continue;\n }\n\n const numeric = /^[+-]?(\\d+\\.?\\d*|\\.\\d+)(e[+-]?\\d+)?([a-z%]*)/i.exec(input.slice(index));\n if (numeric && numeric[0].length > 0) {\n out.push(canonicalizeDimension(numeric[0]));\n index += numeric[0].length;\n continue;\n }\n\n const identifier = /^[a-z_-][\\w-]*/i.exec(input.slice(index));\n if (identifier) {\n // Unquoted identifiers are CSS keywords and function names, both\n // case-insensitive to the parser and therefore safe to fold.\n out.push(identifier[0].toLowerCase());\n index += identifier[0].length;\n continue;\n }\n\n // Punctuation: `,` `(` `)` `/` `*`. Drop whitespace before a comma so\n // `a , b` and `a, b` converge.\n if (char === ',' && out[out.length - 1] === ' ') out.pop();\n out.push(char);\n index += 1;\n }\n\n return out.join('').trim();\n}\n\n/** Canonicalize one `<number><unit>` token. */\nexport function canonicalizeDimension(token: string): string {\n const match = /^([+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:e[+-]?\\d+)?)([a-z%]*)$/i.exec(token);\n if (!match) return token.toLowerCase();\n\n const magnitude = Number.parseFloat(match[1]!);\n const unit = match[2]!.toLowerCase();\n\n if (!Number.isFinite(magnitude)) return token.toLowerCase();\n\n // A zero length is unitless per spec, and `0`, `0px`, `0em` are the same\n // declaration written three ways.\n if (magnitude === 0 && (unit === '' || unit in ABSOLUTE_LENGTHS || isRelativeUnit(unit))) {\n return '0';\n }\n\n const lengthFactor = ABSOLUTE_LENGTHS[unit];\n if (lengthFactor !== undefined) return `${round(magnitude * lengthFactor)}px`;\n\n const angleFactor = ANGLES[unit];\n if (angleFactor !== undefined) return `${round(magnitude * angleFactor)}deg`;\n\n return `${round(magnitude)}${unit}`;\n}\n\nexport function isRelativeUnit(unit: string): boolean {\n return RELATIVE_UNITS.includes(unit.toLowerCase());\n}\n\nfunction round(value: number): number {\n const factor = 10 ** LENGTH_PRECISION;\n const rounded = Math.round(value * factor) / factor;\n return Object.is(rounded, -0) ? 0 : rounded;\n}\n\nfunction closingQuote(input: string, start: number): number {\n const quote = input[start];\n for (let i = start + 1; i < input.length; i += 1) {\n if (input[i] === '\\\\') {\n i += 1;\n continue;\n }\n if (input[i] === quote) return i;\n }\n return input.length - 1;\n}\n\nfunction matchingParen(input: string, openIndex: number): number {\n let depth = 0;\n for (let i = openIndex; i < input.length; i += 1) {\n if (input[i] === '(') depth += 1;\n else if (input[i] === ')') {\n depth -= 1;\n if (depth === 0) return i;\n }\n }\n return input.length - 1;\n}\n"]}
1
+ {"version":3,"file":"value.js","sourceRoot":"","sources":["../../../src/rules/normalize/value.ts"],"names":[],"mappings":"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","sourcesContent":["import { withoutOrigin } from '../../format/origin.js';\nimport { canonicalizeColor, isColorProperty } from './color.js';\n\n/**\n * Declaration-value canonicalization.\n *\n * Every value entering a snapshot passes through here. The job is to erase every\n * way CSS lets an author write the same thing differently — spacing, casing,\n * absolute unit choice, float formatting — so that reformatting a stylesheet is\n * not a mass-invalidation event.\n *\n * The discipline is one-directional: collapse only differences that provably\n * cannot reach a pixel. Anything that *might* render differently is left alone,\n * because an over-eager equivalence here produces a false `unchanged`, and the\n * cost of the opposite mistake is only a review that did not need to happen.\n */\n\n/** Absolute lengths, in CSS px. Fixed by spec, so conversion is lossless. */\nconst ABSOLUTE_LENGTHS: Readonly<Record<string, number>> = {\n px: 1,\n pt: 96 / 72,\n pc: 16,\n in: 96,\n cm: 96 / 2.54,\n mm: 96 / 25.4,\n q: 96 / 101.6,\n};\n\n/** Angles, in degrees. */\nconst ANGLES: Readonly<Record<string, number>> = {\n deg: 1,\n grad: 0.9,\n rad: 180 / Math.PI,\n turn: 360,\n};\n\n/**\n * Units left symbolic because they cannot be resolved without a layout engine.\n *\n * This is the `declared-only` boundary made concrete (ADR-0002): under a profile\n * without computed style, `padding: 1rem` stays `1rem`. That is honest — it is\n * genuinely all that tier knows — and it is why JSDOM decides the token band\n * `declared-only` rather than `full`. Resolving these by assuming a root font\n * size would fabricate precision the tier does not have.\n */\nexport const RELATIVE_UNITS: readonly string[] = [\n 'em', 'rem', 'ex', 'ch', 'cap', 'ic', 'lh', 'rlh',\n 'vw', 'vh', 'vmin', 'vmax', 'vi', 'vb',\n 'svw', 'svh', 'lvw', 'lvh', 'dvw', 'dvh', '%',\n];\n\n/** Decimal places kept for a length. Absorbs float noise, keeps sub-pixel intent. */\nconst LENGTH_PRECISION = 4;\n\n/**\n * `origin` is the page's own, `https://shop.example` with no trailing slash. A\n * `url()` it serves is written as its path, so the port a build was served on\n * is not part of the value.\n */\nexport function canonicalizeValue(property: string, value: string, origin?: string): string {\n if (isColorProperty(property)) return canonicalizeColor(value);\n return canonicalizeTokens(value, origin);\n}\n\n/**\n * Rewrite a value token by token.\n *\n * Quoted strings and `url()` payloads are passed through untouched: a font\n * family name and a resource path are opaque data, and normalizing their case\n * would change what they refer to.\n */\nexport function canonicalizeTokens(value: string, origin?: string): string {\n const out: string[] = [];\n let index = 0;\n const input = value.trim();\n\n while (index < input.length) {\n const char = input[index]!;\n\n if (/\\s/.test(char)) {\n if (out.length > 0 && out[out.length - 1] !== ' ') out.push(' ');\n index += 1;\n continue;\n }\n\n if (char === '\"' || char === \"'\") {\n const end = closingQuote(input, index);\n out.push(input.slice(index, end + 1));\n index = end + 1;\n continue;\n }\n\n if (input.slice(index).toLowerCase().startsWith('url(')) {\n const end = matchingParen(input, index + 3);\n out.push(sameOriginPath(`url${input.slice(index + 3, end + 1)}`, origin));\n index = end + 1;\n continue;\n }\n\n const numeric = /^[+-]?(\\d+\\.?\\d*|\\.\\d+)(e[+-]?\\d+)?([a-z%]*)/i.exec(input.slice(index));\n if (numeric && numeric[0].length > 0) {\n out.push(canonicalizeDimension(numeric[0]));\n index += numeric[0].length;\n continue;\n }\n\n const identifier = /^[a-z_-][\\w-]*/i.exec(input.slice(index));\n if (identifier) {\n // Unquoted identifiers are CSS keywords and function names, both\n // case-insensitive to the parser and therefore safe to fold.\n out.push(identifier[0].toLowerCase());\n index += identifier[0].length;\n continue;\n }\n\n // Punctuation: `,` `(` `)` `/` `*`. Drop whitespace before a comma so\n // `a , b` and `a, b` converge.\n if (char === ',' && out[out.length - 1] === ' ') out.pop();\n out.push(char);\n index += 1;\n }\n\n return out.join('').trim();\n}\n\n/** One `url(…)` token, with the page's own origin dropped from its payload. */\nfunction sameOriginPath(token: string, origin: string | undefined): string {\n if (origin === undefined) return token;\n const match = /^url\\(\\s*([\"']?)(.*?)\\1\\s*\\)$/s.exec(token);\n const url = match?.[2];\n if (url === undefined) return token;\n const path = withoutOrigin(url, origin);\n return path === url ? token : `url(${match![1]}${path}${match![1]})`;\n}\n\n/** Canonicalize one `<number><unit>` token. */\nexport function canonicalizeDimension(token: string): string {\n const match = /^([+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:e[+-]?\\d+)?)([a-z%]*)$/i.exec(token);\n if (!match) return token.toLowerCase();\n\n const magnitude = Number.parseFloat(match[1]!);\n const unit = match[2]!.toLowerCase();\n\n if (!Number.isFinite(magnitude)) return token.toLowerCase();\n\n // A zero length is unitless per spec, and `0`, `0px`, `0em` are the same\n // declaration written three ways.\n if (magnitude === 0 && (unit === '' || unit in ABSOLUTE_LENGTHS || isRelativeUnit(unit))) {\n return '0';\n }\n\n const lengthFactor = ABSOLUTE_LENGTHS[unit];\n if (lengthFactor !== undefined) return `${round(magnitude * lengthFactor)}px`;\n\n const angleFactor = ANGLES[unit];\n if (angleFactor !== undefined) return `${round(magnitude * angleFactor)}deg`;\n\n return `${round(magnitude)}${unit}`;\n}\n\nexport function isRelativeUnit(unit: string): boolean {\n return RELATIVE_UNITS.includes(unit.toLowerCase());\n}\n\nfunction round(value: number): number {\n const factor = 10 ** LENGTH_PRECISION;\n const rounded = Math.round(value * factor) / factor;\n return Object.is(rounded, -0) ? 0 : rounded;\n}\n\nfunction closingQuote(input: string, start: number): number {\n const quote = input[start];\n for (let i = start + 1; i < input.length; i += 1) {\n if (input[i] === '\\\\') {\n i += 1;\n continue;\n }\n if (input[i] === quote) return i;\n }\n return input.length - 1;\n}\n\n/**\n * The index of the `)` that closes the `(` at `openIndex`, or `input.length`\n * when nothing closes it. A parenthesis inside a quoted string is text, and a\n * backslash escapes the character after it inside one.\n */\nexport function matchingParen(input: string, openIndex: number): number {\n let depth = 0;\n let quote: string | undefined;\n\n for (let i = openIndex; i < input.length; i += 1) {\n const char = input[i];\n if (quote !== undefined) {\n if (char === '\\\\') i += 1;\n else if (char === quote) quote = undefined;\n } else if (char === '\"' || char === \"'\") {\n quote = char;\n } else if (char === '(') {\n depth += 1;\n } else if (char === ')') {\n depth -= 1;\n if (depth === 0) return i;\n }\n }\n\n return input.length;\n}\n"]}
@@ -51,6 +51,11 @@ export function isInertWrapper(raw, node, declaredBy) {
51
51
  // already reading wiring, which is a change that needs its own changeset.
52
52
  if (node.holding !== undefined)
53
53
  return false;
54
+ // A box CSS generates inside the wrapper is the wrapper's to paint: its rules
55
+ // were written against this element, and hoisting `::before` onto a parent
56
+ // puts it on a box that generates nothing.
57
+ if (raw.children.some((child) => child.tag.startsWith('::')))
58
+ return false;
54
59
  const declaredHere = declaredBy.get(node) ?? EMPTY_PROPERTIES;
55
60
  for (const [property, value] of Object.entries(node.style)) {
56
61
  // Only what the wrapper *declared* is evidence about the wrapper.
@@ -1 +1 @@
1
- {"version":3,"file":"wrapper.js","sourceRoot":"","sources":["../../../src/rules/normalize/wrapper.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RawNode } from '../../format/capture.js';\nimport type { SemanticNode } from '../../format/snapshot.js';\n\n/**\n * Whether a node exists only to hold its children.\n *\n * Conservative on purpose: this is the one normalization rule that *removes* a\n * node, so a wrong answer here deletes evidence. Anything carrying a role, a\n * name, an id, an admitted attribute, text, or a non-inherited style declaration\n * is kept, whatever it looks like.\n *\n * Inherited properties are excluded from the test because a wrapper inheriting\n * `color` from above passes that same value to its children either way — its\n * presence changes nothing that renders.\n */\nexport function isInertWrapper(\n raw: RawNode,\n node: SemanticNode,\n declaredBy: WeakMap<SemanticNode, ReadonlySet<string>>,\n): boolean {\n if (node.tag !== 'div' && node.tag !== 'span') return false;\n if (node.role !== undefined || node.name !== undefined || node.state !== undefined) return false;\n if (node.alias !== undefined || node.text !== undefined) return false;\n if (Object.keys(node.attributes).length > 0) return false;\n if (raw.shadowChildren !== undefined && raw.shadowChildren.length > 0) return false;\n\n // A wrapper the operator excluded is never inert, whatever it declares.\n //\n // The mark is not an attribute — `data-variance-ignore` is deliberately outside\n // the allowlist so that adding it re-baselines nothing — so the check above\n // cannot see it, and a bare marked `<div>` is exactly the shape this function\n // deletes. Collapsing it drops the mark with it, `sitesIn` finds no site, and\n // the exclusion silently evaporates: the operator reads their config, sees the\n // rule, and the run compares the region anyway. An ignore that stops working\n // without saying so is the same failure as one that absorbs too much, pointed\n // the other way.\n if (node.ignoredBy !== undefined && node.ignoredBy.length > 0) return false;\n\n // A wrapper that roots a component boundary is never inert either, and for the\n // same reason one sentence up: the collapse would take the holding with it, and\n // `function Summary() { return <span>{total}</span> }` is not an exotic shape —\n // it is most of a component library. Losing the boundary there loses the only\n // record of what that component was handed and what it retained, which is the\n // evidence `compare/parting.ts` traces a difference back through.\n //\n // The cost is stated rather than hidden: a run that reads holdings keeps\n // wrappers a run without them collapses, so the two produce different\n // `structureHash`es for one page. That is why holding is opted into per run\n // rather than defaulted on, and why both sides of a comparison must be read\n // the same way — the same bargain `ignoredBy` makes above.\n //\n // FIXME: `wiring` is destroyed by this collapse and is not checked here. It is\n // hashed into a band, so adding it would move an existing baseline for anyone\n // already reading wiring, which is a change that needs its own changeset.\n if (node.holding !== undefined) return false;\n\n const declaredHere = declaredBy.get(node) ?? EMPTY_PROPERTIES;\n\n for (const [property, value] of Object.entries(node.style)) {\n // Only what the wrapper *declared* is evidence about the wrapper.\n //\n // An inherited value passes through unchanged — the children receive it\n // whether or not the wrapper is there. An engine-computed value is worse\n // than uninformative: under a profile with computed style every one of the\n // ~200 allowlisted properties arrives with a resolved value, including used\n // values like `width: 1264px` that describe the *parent's* layout rather\n // than anything the wrapper did. Testing those against an initial-value\n // table meant `isInertDeclaration` returned false on the first unrecognized\n // one and no wrapper anywhere collapsed under `chromium` — the same rule\n // disabled by a different accident under `jsdom` in journal 0005, and\n // invisible until the two profiles were scored against each other (P4).\n //\n // Conservatism is kept where it is evidence: an unrecognized property the\n // wrapper *declared* still blocks the collapse.\n if (!declaredHere.has(property)) continue;\n if (!isInertDeclaration(property, value)) return false;\n }\n\n return true;\n}\n\nconst EMPTY_PROPERTIES: ReadonlySet<string> = new Set();\n\n/**\n * Initial values for the properties a bare `div`/`span` legitimately carries.\n *\n * Needed because a profile with computed style reports *every* property, initial\n * ones included — so \"declares no styling\" cannot be tested by an empty map.\n */\nconst INERT_VALUES: Readonly<Record<string, readonly string[]>> = {\n // `contents` generates no box at all, so a wrapper carrying it is inert by\n // definition: its children already participate in the parent's layout.\n display: ['block', 'inline', 'contents'],\n position: ['static'],\n 'box-sizing': ['content-box', 'border-box'],\n 'overflow-x': ['visible'],\n 'overflow-y': ['visible'],\n opacity: ['1'],\n visibility: ['visible'],\n transform: ['none'],\n filter: ['none'],\n 'backdrop-filter': ['none'],\n 'mix-blend-mode': ['normal'],\n 'background-color': ['rgb(0 0 0 / 0)'],\n 'background-image': ['none'],\n 'z-index': ['auto'],\n float: ['none'],\n clear: ['none'],\n width: ['auto'],\n height: ['auto'],\n 'min-width': ['0', 'auto'],\n 'min-height': ['0', 'auto'],\n 'max-width': ['none'],\n 'max-height': ['none'],\n 'aspect-ratio': ['auto'],\n 'content-visibility': ['visible'],\n 'object-fit': ['fill'],\n 'table-layout': ['auto'],\n};\n\nconst ZERO_PREFIXED = ['margin-', 'padding-', 'border-', 'outline-', 'inset', 'top', 'right', 'bottom', 'left'];\n\nfunction isInertDeclaration(property: string, value: string): boolean {\n const allowed = INERT_VALUES[property];\n if (allowed) return allowed.includes(value);\n\n if (property.startsWith('border-') && property.endsWith('-style')) return value === 'none';\n if (property.startsWith('border-') && property.endsWith('-color')) return true;\n if (property === 'outline-style') return value === 'none';\n if (property === 'outline-color') return true;\n if (property === 'box-shadow' || property === 'text-shadow') return value === 'none';\n\n if (ZERO_PREFIXED.some((prefix) => property.startsWith(prefix))) {\n return value === '0' || value === 'auto';\n }\n\n // An unrecognized property on a wrapper is a reason to keep it. Silence is\n // not evidence of inertness.\n return false;\n}\n"]}
1
+ {"version":3,"file":"wrapper.js","sourceRoot":"","sources":["../../../src/rules/normalize/wrapper.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RawNode } from '../../format/capture.js';\nimport type { SemanticNode } from '../../format/snapshot.js';\n\n/**\n * Whether a node exists only to hold its children.\n *\n * Conservative on purpose: this is the one normalization rule that *removes* a\n * node, so a wrong answer here deletes evidence. Anything carrying a role, a\n * name, an id, an admitted attribute, text, or a non-inherited style declaration\n * is kept, whatever it looks like.\n *\n * Inherited properties are excluded from the test because a wrapper inheriting\n * `color` from above passes that same value to its children either way — its\n * presence changes nothing that renders.\n */\nexport function isInertWrapper(\n raw: RawNode,\n node: SemanticNode,\n declaredBy: WeakMap<SemanticNode, ReadonlySet<string>>,\n): boolean {\n if (node.tag !== 'div' && node.tag !== 'span') return false;\n if (node.role !== undefined || node.name !== undefined || node.state !== undefined) return false;\n if (node.alias !== undefined || node.text !== undefined) return false;\n if (Object.keys(node.attributes).length > 0) return false;\n if (raw.shadowChildren !== undefined && raw.shadowChildren.length > 0) return false;\n\n // A wrapper the operator excluded is never inert, whatever it declares.\n //\n // The mark is not an attribute — `data-variance-ignore` is deliberately outside\n // the allowlist so that adding it re-baselines nothing — so the check above\n // cannot see it, and a bare marked `<div>` is exactly the shape this function\n // deletes. Collapsing it drops the mark with it, `sitesIn` finds no site, and\n // the exclusion silently evaporates: the operator reads their config, sees the\n // rule, and the run compares the region anyway. An ignore that stops working\n // without saying so is the same failure as one that absorbs too much, pointed\n // the other way.\n if (node.ignoredBy !== undefined && node.ignoredBy.length > 0) return false;\n\n // A wrapper that roots a component boundary is never inert either, and for the\n // same reason one sentence up: the collapse would take the holding with it, and\n // `function Summary() { return <span>{total}</span> }` is not an exotic shape —\n // it is most of a component library. Losing the boundary there loses the only\n // record of what that component was handed and what it retained, which is the\n // evidence `compare/parting.ts` traces a difference back through.\n //\n // The cost is stated rather than hidden: a run that reads holdings keeps\n // wrappers a run without them collapses, so the two produce different\n // `structureHash`es for one page. That is why holding is opted into per run\n // rather than defaulted on, and why both sides of a comparison must be read\n // the same way — the same bargain `ignoredBy` makes above.\n //\n // FIXME: `wiring` is destroyed by this collapse and is not checked here. It is\n // hashed into a band, so adding it would move an existing baseline for anyone\n // already reading wiring, which is a change that needs its own changeset.\n if (node.holding !== undefined) return false;\n\n // A box CSS generates inside the wrapper is the wrapper's to paint: its rules\n // were written against this element, and hoisting `::before` onto a parent\n // puts it on a box that generates nothing.\n if (raw.children.some((child) => child.tag.startsWith('::'))) return false;\n\n const declaredHere = declaredBy.get(node) ?? EMPTY_PROPERTIES;\n\n for (const [property, value] of Object.entries(node.style)) {\n // Only what the wrapper *declared* is evidence about the wrapper.\n //\n // An inherited value passes through unchanged — the children receive it\n // whether or not the wrapper is there. An engine-computed value is worse\n // than uninformative: under a profile with computed style every one of the\n // ~200 allowlisted properties arrives with a resolved value, including used\n // values like `width: 1264px` that describe the *parent's* layout rather\n // than anything the wrapper did. Testing those against an initial-value\n // table meant `isInertDeclaration` returned false on the first unrecognized\n // one and no wrapper anywhere collapsed under `chromium` — the same rule\n // disabled by a different accident under `jsdom` in journal 0005, and\n // invisible until the two profiles were scored against each other (P4).\n //\n // Conservatism is kept where it is evidence: an unrecognized property the\n // wrapper *declared* still blocks the collapse.\n if (!declaredHere.has(property)) continue;\n if (!isInertDeclaration(property, value)) return false;\n }\n\n return true;\n}\n\nconst EMPTY_PROPERTIES: ReadonlySet<string> = new Set();\n\n/**\n * Initial values for the properties a bare `div`/`span` legitimately carries.\n *\n * Needed because a profile with computed style reports *every* property, initial\n * ones included — so \"declares no styling\" cannot be tested by an empty map.\n */\nconst INERT_VALUES: Readonly<Record<string, readonly string[]>> = {\n // `contents` generates no box at all, so a wrapper carrying it is inert by\n // definition: its children already participate in the parent's layout.\n display: ['block', 'inline', 'contents'],\n position: ['static'],\n 'box-sizing': ['content-box', 'border-box'],\n 'overflow-x': ['visible'],\n 'overflow-y': ['visible'],\n opacity: ['1'],\n visibility: ['visible'],\n transform: ['none'],\n filter: ['none'],\n 'backdrop-filter': ['none'],\n 'mix-blend-mode': ['normal'],\n 'background-color': ['rgb(0 0 0 / 0)'],\n 'background-image': ['none'],\n 'z-index': ['auto'],\n float: ['none'],\n clear: ['none'],\n width: ['auto'],\n height: ['auto'],\n 'min-width': ['0', 'auto'],\n 'min-height': ['0', 'auto'],\n 'max-width': ['none'],\n 'max-height': ['none'],\n 'aspect-ratio': ['auto'],\n 'content-visibility': ['visible'],\n 'object-fit': ['fill'],\n 'table-layout': ['auto'],\n};\n\nconst ZERO_PREFIXED = ['margin-', 'padding-', 'border-', 'outline-', 'inset', 'top', 'right', 'bottom', 'left'];\n\nfunction isInertDeclaration(property: string, value: string): boolean {\n const allowed = INERT_VALUES[property];\n if (allowed) return allowed.includes(value);\n\n if (property.startsWith('border-') && property.endsWith('-style')) return value === 'none';\n if (property.startsWith('border-') && property.endsWith('-color')) return true;\n if (property === 'outline-style') return value === 'none';\n if (property === 'outline-color') return true;\n if (property === 'box-shadow' || property === 'text-shadow') return value === 'none';\n\n if (ZERO_PREFIXED.some((prefix) => property.startsWith(prefix))) {\n return value === '0' || value === 'auto';\n }\n\n // An unrecognized property on a wrapper is a reason to keep it. Silence is\n // not evidence of inertness.\n return false;\n}\n"]}
@@ -6,8 +6,25 @@
6
6
  * That is correct and intended — a rule change is a render-input change — but it
7
7
  * means edits here are releases, not tweaks. Bump the version in the same commit.
8
8
  */
9
- /** Bump on any change to normalization behavior. */
10
- export declare const RULESET_VERSION = "r1";
9
+ /**
10
+ * Bump on any change to normalization behavior.
11
+ *
12
+ * `r2` drops the page's own origin from a `url()` in a computed value, and the
13
+ * collectors key an asset the page's origin serves by its path. Under `r1` the
14
+ * port a collector happened to serve a build on was part of both, so one build
15
+ * served twice read as two renders in two environments.
16
+ *
17
+ * `r2` also reads the boxes `::before` and `::after` generate as children of
18
+ * the element they hang from, styled by the rules written for them. Under `r1`
19
+ * a rule naming a pseudo-element matched nothing, so restyling or rewording one
20
+ * repainted the page and moved no hash.
21
+ *
22
+ * `r2` also reads a form as it stands: the text typed into an `<input>` or a
23
+ * `<textarea>`, the option chosen in a `<select>` and the box ticked. Under `r1`
24
+ * the capture read the markup the server sent, so a filled form read as an
25
+ * empty one.
26
+ */
27
+ export declare const RULESET_VERSION = "r2";
11
28
  /**
12
29
  * Bump on any change to {@link STYLE_ALLOWLIST}.
13
30
  *
@@ -45,8 +62,11 @@ export declare const ALLOWLIST_VERSION = "a2";
45
62
  * not contain. Including them would make every easing tweak a diff.
46
63
  * - `cursor`, `user-select`, `will-change`, `contain` — invisible in both raster
47
64
  * and the accessibility tree at rest.
48
- * - `content` — resolved pseudo-element text is captured as text, not as a
49
- * declaration, so that it diffs like the content it is.
65
+ * - `content` — the text a `::before` or `::after` says is captured as a text
66
+ * child of that box, not as a declaration, so that it diffs like the text it
67
+ * is. A host without a layout engine reads those words from the winning
68
+ * declared `content` when it is strings and `attr()`, and marks the box
69
+ * `unread` when it holds a part only an engine resolves, such as `counter()`.
50
70
  */
51
71
  export declare const STYLE_ALLOWLIST: readonly string[];
52
72
  export declare function isAllowedProperty(property: string): boolean;
@@ -6,8 +6,25 @@
6
6
  * That is correct and intended — a rule change is a render-input change — but it
7
7
  * means edits here are releases, not tweaks. Bump the version in the same commit.
8
8
  */
9
- /** Bump on any change to normalization behavior. */
10
- export const RULESET_VERSION = 'r1';
9
+ /**
10
+ * Bump on any change to normalization behavior.
11
+ *
12
+ * `r2` drops the page's own origin from a `url()` in a computed value, and the
13
+ * collectors key an asset the page's origin serves by its path. Under `r1` the
14
+ * port a collector happened to serve a build on was part of both, so one build
15
+ * served twice read as two renders in two environments.
16
+ *
17
+ * `r2` also reads the boxes `::before` and `::after` generate as children of
18
+ * the element they hang from, styled by the rules written for them. Under `r1`
19
+ * a rule naming a pseudo-element matched nothing, so restyling or rewording one
20
+ * repainted the page and moved no hash.
21
+ *
22
+ * `r2` also reads a form as it stands: the text typed into an `<input>` or a
23
+ * `<textarea>`, the option chosen in a `<select>` and the box ticked. Under `r1`
24
+ * the capture read the markup the server sent, so a filled form read as an
25
+ * empty one.
26
+ */
27
+ export const RULESET_VERSION = 'r2';
11
28
  /**
12
29
  * Bump on any change to {@link STYLE_ALLOWLIST}.
13
30
  *
@@ -45,8 +62,11 @@ export const ALLOWLIST_VERSION = 'a2';
45
62
  * not contain. Including them would make every easing tweak a diff.
46
63
  * - `cursor`, `user-select`, `will-change`, `contain` — invisible in both raster
47
64
  * and the accessibility tree at rest.
48
- * - `content` — resolved pseudo-element text is captured as text, not as a
49
- * declaration, so that it diffs like the content it is.
65
+ * - `content` — the text a `::before` or `::after` says is captured as a text
66
+ * child of that box, not as a declaration, so that it diffs like the text it
67
+ * is. A host without a layout engine reads those words from the winning
68
+ * declared `content` when it is strings and `attr()`, and marks the box
69
+ * `unread` when it holds a part only an engine resolves, such as `counter()`.
50
70
  */
51
71
  export const STYLE_ALLOWLIST = [
52
72
  // Box model and formatting context
@@ -1 +1 @@
1
- {"version":3,"file":"ruleset.js","sourceRoot":"","sources":["../../src/rules/ruleset.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The normalization ruleset and computed-style allowlist.\n *\n * Both versions are components of the environment key, so **changing anything in\n * this file invalidates every baseline in every repository using it** (spec §7.3).\n * That is correct and intended — a rule change is a render-input change — but it\n * means edits here are releases, not tweaks. Bump the version in the same commit.\n */\n\n/** Bump on any change to normalization behavior. */\nexport const RULESET_VERSION = 'r1';\n\n/**\n * Bump on any change to {@link STYLE_ALLOWLIST}.\n *\n * `a2` added `accent-color` and `-webkit-text-stroke-width`, both found by\n * probing a real Chromium against the semantic snapshot: each moves hundreds of\n * pixels and neither moved the render hash.\n *\n * `-webkit-text-stroke-color` was added alongside them and immediately removed.\n * Its initial value is `currentcolor`, so it moves whenever `color` moves — which\n * split a colour-token root in two, since the derived property resolves through\n * no token and looked like an independent cause. Nothing measured it as a gap;\n * it was added on the assumption that its sibling implied it. Properties whose\n * computed value derives from another property need the derivation modelled\n * before they can be admitted.\n */\nexport const ALLOWLIST_VERSION = 'a2';\n\n/**\n * Properties that enter a snapshot.\n *\n * The allowlist is the direct lever on the product's central tradeoff (spec\n * §11.3): too narrow and real regressions pass unseen; too wide and every\n * snapshot churns, reintroducing the fatigue that makes teams stop reading\n * diffs. So the admission test is deliberately strict —\n *\n * > Can a change to this property alone alter what a user sees or how assistive\n * > technology reports the page?\n *\n * Only longhands appear. Shorthands are expanded before matching, because\n * `margin: 4px` and `margin-top: 4px; …` must produce identical snapshots or\n * refactoring a stylesheet becomes a mass-invalidation event.\n *\n * Notable exclusions, each for a reason:\n * - `transition-*` / `animation-*` — snapshots are taken at a declared settle\n * point with animation disabled, so these describe a journey the snapshot does\n * not contain. Including them would make every easing tweak a diff.\n * - `cursor`, `user-select`, `will-change`, `contain` — invisible in both raster\n * and the accessibility tree at rest.\n * - `content` — resolved pseudo-element text is captured as text, not as a\n * declaration, so that it diffs like the content it is.\n */\nexport const STYLE_ALLOWLIST: readonly string[] = [\n // Box model and formatting context\n 'display', 'position', 'top', 'right', 'bottom', 'left', 'float', 'clear',\n 'width', 'height', 'min-width', 'min-height', 'max-width', 'max-height',\n 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',\n 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',\n 'box-sizing', 'overflow-x', 'overflow-y', 'z-index', 'aspect-ratio',\n\n // Flex and grid\n 'flex-direction', 'flex-wrap', 'flex-grow', 'flex-shrink', 'flex-basis',\n 'justify-content', 'align-items', 'align-self', 'align-content', 'order',\n 'grid-template-columns', 'grid-template-rows', 'grid-template-areas',\n 'grid-auto-columns', 'grid-auto-rows', 'grid-auto-flow',\n 'grid-column-start', 'grid-column-end', 'grid-row-start', 'grid-row-end',\n 'row-gap', 'column-gap',\n\n // Typography\n 'font-family', 'font-size', 'font-weight', 'font-style', 'font-variant',\n 'font-stretch', 'line-height', 'letter-spacing', 'word-spacing',\n 'text-align', 'text-indent', 'text-transform', 'text-overflow',\n 'text-decoration-line', 'text-decoration-color', 'text-decoration-style',\n 'text-decoration-thickness', 'white-space', 'word-break', 'overflow-wrap',\n 'vertical-align', 'writing-mode', 'direction',\n\n // Paint\n 'color', 'opacity', 'background-color', 'background-image',\n // Native-control painting. `accent-color` restyles checkboxes and radios,\n // which is exactly the kind of property the rest of this list misses: it\n // describes what the *engine* paints rather than what the author declares\n // about a box. See the note below.\n 'accent-color', '-webkit-text-stroke-width',\n 'background-position', 'background-size', 'background-repeat',\n 'background-clip', 'background-origin',\n 'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width',\n 'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style',\n 'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color',\n 'border-top-left-radius', 'border-top-right-radius',\n 'border-bottom-right-radius', 'border-bottom-left-radius',\n 'outline-width', 'outline-style', 'outline-color', 'outline-offset',\n 'box-shadow', 'text-shadow', 'filter', 'backdrop-filter', 'mix-blend-mode',\n\n // Visibility and geometry transforms\n 'visibility', 'content-visibility', 'transform', 'transform-origin',\n 'object-fit', 'object-position',\n\n // Tables\n 'table-layout', 'border-collapse', 'border-spacing', 'caption-side',\n];\n\n/**\n * Known weakness, recorded because two additions do not fix it.\n *\n * The list above was assembled by asking what an author declares about a box,\n * and it is good at that. It is poor at properties describing what the *engine*\n * paints — native control appearance, text decoration painted outside the glyph\n * box, platform accent colours. `accent-color` and `-webkit-text-stroke-width`\n * were found in an afternoon of probing a real browser, which is weak evidence\n * that they are the only two.\n *\n * The right response is a systematic audit against the CSS property index rather\n * than more ad-hoc additions, and until that happens this list should be assumed\n * to have holes of this shape. A missing property is a false `unchanged`, so the\n * gap is in the direction that matters.\n */\nconst ALLOWED = new Set(STYLE_ALLOWLIST);\n\nexport function isAllowedProperty(property: string): boolean {\n return ALLOWED.has(property);\n}\n\n/**\n * Custom properties are always admitted, whatever their name.\n *\n * They cannot be enumerated in advance — every project invents its own — and they\n * are the mechanism design tokens travel through, which makes them the highest-\n * value signal the system collects: a custom property resolves a *name* alongside\n * a value, and that name is what lets one token edit collapse into one docket\n * root with counted collateral rather than hundreds of separate colour diffs.\n */\nexport function isCustomProperty(property: string): boolean {\n return property.startsWith('--');\n}\n\n/** Whether a property survives the allowlist projection (ADR-0003 step 4). */\nexport function admits(property: string): boolean {\n return isCustomProperty(property) || isAllowedProperty(property);\n}\n\n/**\n * Attributes kept on a semantic node.\n *\n * An allowlist rather than a denylist. Frameworks emit an open-ended supply of\n * bookkeeping attributes — `data-reactroot`, `data-styled`, `data-testid`,\n * `data-v-7f3a`, Storybook's own markers — and a denylist would need a new entry\n * for each one, silently churning baselines until someone noticed. Anything not\n * named here is dropped.\n *\n * `class` is absent by design (ADR-0003): a class name records *how* a style was\n * applied, and the snapshot already records *what* was applied, resolved.\n * `id` is absent because it has been replaced by a structural alias.\n * `aria-*` is absent because those are resolved into `role`/`name`/`state`,\n * which is what actually reaches a user.\n */\nexport const ATTRIBUTE_ALLOWLIST: readonly string[] = [\n 'type', 'value', 'checked', 'disabled', 'readonly', 'required', 'multiple',\n 'placeholder', 'name', 'href', 'target', 'rel', 'src', 'alt', 'title',\n 'for', 'form', 'list', 'headers', 'colspan', 'rowspan', 'scope',\n 'lang', 'dir', 'hidden', 'open', 'selected', 'download',\n 'min', 'max', 'step', 'pattern', 'maxlength', 'minlength', 'autocomplete',\n 'role', 'tabindex', 'draggable', 'contenteditable',\n 'width', 'height', 'loading', 'decoding', 'srcset', 'sizes', 'poster', 'controls',\n];\n\nconst ATTRIBUTES = new Set(ATTRIBUTE_ALLOWLIST);\n\nexport function admitsAttribute(name: string): boolean {\n return ATTRIBUTES.has(name);\n}\n\n/**\n * Attributes whose values are id references, rewritten to structural aliases.\n *\n * Aliasing rather than masking is what preserves the *relationship* while\n * deleting the volatile *value* (ADR-0003): a `useId` renumbering becomes a\n * no-op, while breaking a `label ↔ input` association stays a real structural\n * change. Masking both to a constant cannot tell those apart.\n */\nexport const ID_REFERENCE_ATTRIBUTES: readonly string[] = [\n 'id', 'for', 'form', 'list', 'headers',\n 'aria-labelledby', 'aria-describedby', 'aria-controls', 'aria-owns',\n 'aria-activedescendant', 'aria-details', 'aria-errormessage', 'aria-flowto',\n];\n\n/** Attributes holding a *space-separated list* of id references, not just one. */\nexport const ID_REFERENCE_LIST_ATTRIBUTES: readonly string[] = [\n 'headers', 'aria-labelledby', 'aria-describedby', 'aria-controls',\n 'aria-owns', 'aria-flowto',\n];\n"]}
1
+ {"version":3,"file":"ruleset.js","sourceRoot":"","sources":["../../src/rules/ruleset.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The normalization ruleset and computed-style allowlist.\n *\n * Both versions are components of the environment key, so **changing anything in\n * this file invalidates every baseline in every repository using it** (spec §7.3).\n * That is correct and intended — a rule change is a render-input change — but it\n * means edits here are releases, not tweaks. Bump the version in the same commit.\n */\n\n/**\n * Bump on any change to normalization behavior.\n *\n * `r2` drops the page's own origin from a `url()` in a computed value, and the\n * collectors key an asset the page's origin serves by its path. Under `r1` the\n * port a collector happened to serve a build on was part of both, so one build\n * served twice read as two renders in two environments.\n *\n * `r2` also reads the boxes `::before` and `::after` generate as children of\n * the element they hang from, styled by the rules written for them. Under `r1`\n * a rule naming a pseudo-element matched nothing, so restyling or rewording one\n * repainted the page and moved no hash.\n *\n * `r2` also reads a form as it stands: the text typed into an `<input>` or a\n * `<textarea>`, the option chosen in a `<select>` and the box ticked. Under `r1`\n * the capture read the markup the server sent, so a filled form read as an\n * empty one.\n */\nexport const RULESET_VERSION = 'r2';\n\n/**\n * Bump on any change to {@link STYLE_ALLOWLIST}.\n *\n * `a2` added `accent-color` and `-webkit-text-stroke-width`, both found by\n * probing a real Chromium against the semantic snapshot: each moves hundreds of\n * pixels and neither moved the render hash.\n *\n * `-webkit-text-stroke-color` was added alongside them and immediately removed.\n * Its initial value is `currentcolor`, so it moves whenever `color` moves — which\n * split a colour-token root in two, since the derived property resolves through\n * no token and looked like an independent cause. Nothing measured it as a gap;\n * it was added on the assumption that its sibling implied it. Properties whose\n * computed value derives from another property need the derivation modelled\n * before they can be admitted.\n */\nexport const ALLOWLIST_VERSION = 'a2';\n\n/**\n * Properties that enter a snapshot.\n *\n * The allowlist is the direct lever on the product's central tradeoff (spec\n * §11.3): too narrow and real regressions pass unseen; too wide and every\n * snapshot churns, reintroducing the fatigue that makes teams stop reading\n * diffs. So the admission test is deliberately strict —\n *\n * > Can a change to this property alone alter what a user sees or how assistive\n * > technology reports the page?\n *\n * Only longhands appear. Shorthands are expanded before matching, because\n * `margin: 4px` and `margin-top: 4px; …` must produce identical snapshots or\n * refactoring a stylesheet becomes a mass-invalidation event.\n *\n * Notable exclusions, each for a reason:\n * - `transition-*` / `animation-*` — snapshots are taken at a declared settle\n * point with animation disabled, so these describe a journey the snapshot does\n * not contain. Including them would make every easing tweak a diff.\n * - `cursor`, `user-select`, `will-change`, `contain` — invisible in both raster\n * and the accessibility tree at rest.\n * - `content` — the text a `::before` or `::after` says is captured as a text\n * child of that box, not as a declaration, so that it diffs like the text it\n * is. A host without a layout engine reads those words from the winning\n * declared `content` when it is strings and `attr()`, and marks the box\n * `unread` when it holds a part only an engine resolves, such as `counter()`.\n */\nexport const STYLE_ALLOWLIST: readonly string[] = [\n // Box model and formatting context\n 'display', 'position', 'top', 'right', 'bottom', 'left', 'float', 'clear',\n 'width', 'height', 'min-width', 'min-height', 'max-width', 'max-height',\n 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',\n 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',\n 'box-sizing', 'overflow-x', 'overflow-y', 'z-index', 'aspect-ratio',\n\n // Flex and grid\n 'flex-direction', 'flex-wrap', 'flex-grow', 'flex-shrink', 'flex-basis',\n 'justify-content', 'align-items', 'align-self', 'align-content', 'order',\n 'grid-template-columns', 'grid-template-rows', 'grid-template-areas',\n 'grid-auto-columns', 'grid-auto-rows', 'grid-auto-flow',\n 'grid-column-start', 'grid-column-end', 'grid-row-start', 'grid-row-end',\n 'row-gap', 'column-gap',\n\n // Typography\n 'font-family', 'font-size', 'font-weight', 'font-style', 'font-variant',\n 'font-stretch', 'line-height', 'letter-spacing', 'word-spacing',\n 'text-align', 'text-indent', 'text-transform', 'text-overflow',\n 'text-decoration-line', 'text-decoration-color', 'text-decoration-style',\n 'text-decoration-thickness', 'white-space', 'word-break', 'overflow-wrap',\n 'vertical-align', 'writing-mode', 'direction',\n\n // Paint\n 'color', 'opacity', 'background-color', 'background-image',\n // Native-control painting. `accent-color` restyles checkboxes and radios,\n // which is exactly the kind of property the rest of this list misses: it\n // describes what the *engine* paints rather than what the author declares\n // about a box. See the note below.\n 'accent-color', '-webkit-text-stroke-width',\n 'background-position', 'background-size', 'background-repeat',\n 'background-clip', 'background-origin',\n 'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width',\n 'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style',\n 'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color',\n 'border-top-left-radius', 'border-top-right-radius',\n 'border-bottom-right-radius', 'border-bottom-left-radius',\n 'outline-width', 'outline-style', 'outline-color', 'outline-offset',\n 'box-shadow', 'text-shadow', 'filter', 'backdrop-filter', 'mix-blend-mode',\n\n // Visibility and geometry transforms\n 'visibility', 'content-visibility', 'transform', 'transform-origin',\n 'object-fit', 'object-position',\n\n // Tables\n 'table-layout', 'border-collapse', 'border-spacing', 'caption-side',\n];\n\n/**\n * Known weakness, recorded because two additions do not fix it.\n *\n * The list above was assembled by asking what an author declares about a box,\n * and it is good at that. It is poor at properties describing what the *engine*\n * paints — native control appearance, text decoration painted outside the glyph\n * box, platform accent colours. `accent-color` and `-webkit-text-stroke-width`\n * were found in an afternoon of probing a real browser, which is weak evidence\n * that they are the only two.\n *\n * The right response is a systematic audit against the CSS property index rather\n * than more ad-hoc additions, and until that happens this list should be assumed\n * to have holes of this shape. A missing property is a false `unchanged`, so the\n * gap is in the direction that matters.\n */\nconst ALLOWED = new Set(STYLE_ALLOWLIST);\n\nexport function isAllowedProperty(property: string): boolean {\n return ALLOWED.has(property);\n}\n\n/**\n * Custom properties are always admitted, whatever their name.\n *\n * They cannot be enumerated in advance — every project invents its own — and they\n * are the mechanism design tokens travel through, which makes them the highest-\n * value signal the system collects: a custom property resolves a *name* alongside\n * a value, and that name is what lets one token edit collapse into one docket\n * root with counted collateral rather than hundreds of separate colour diffs.\n */\nexport function isCustomProperty(property: string): boolean {\n return property.startsWith('--');\n}\n\n/** Whether a property survives the allowlist projection (ADR-0003 step 4). */\nexport function admits(property: string): boolean {\n return isCustomProperty(property) || isAllowedProperty(property);\n}\n\n/**\n * Attributes kept on a semantic node.\n *\n * An allowlist rather than a denylist. Frameworks emit an open-ended supply of\n * bookkeeping attributes — `data-reactroot`, `data-styled`, `data-testid`,\n * `data-v-7f3a`, Storybook's own markers — and a denylist would need a new entry\n * for each one, silently churning baselines until someone noticed. Anything not\n * named here is dropped.\n *\n * `class` is absent by design (ADR-0003): a class name records *how* a style was\n * applied, and the snapshot already records *what* was applied, resolved.\n * `id` is absent because it has been replaced by a structural alias.\n * `aria-*` is absent because those are resolved into `role`/`name`/`state`,\n * which is what actually reaches a user.\n */\nexport const ATTRIBUTE_ALLOWLIST: readonly string[] = [\n 'type', 'value', 'checked', 'disabled', 'readonly', 'required', 'multiple',\n 'placeholder', 'name', 'href', 'target', 'rel', 'src', 'alt', 'title',\n 'for', 'form', 'list', 'headers', 'colspan', 'rowspan', 'scope',\n 'lang', 'dir', 'hidden', 'open', 'selected', 'download',\n 'min', 'max', 'step', 'pattern', 'maxlength', 'minlength', 'autocomplete',\n 'role', 'tabindex', 'draggable', 'contenteditable',\n 'width', 'height', 'loading', 'decoding', 'srcset', 'sizes', 'poster', 'controls',\n];\n\nconst ATTRIBUTES = new Set(ATTRIBUTE_ALLOWLIST);\n\nexport function admitsAttribute(name: string): boolean {\n return ATTRIBUTES.has(name);\n}\n\n/**\n * Attributes whose values are id references, rewritten to structural aliases.\n *\n * Aliasing rather than masking is what preserves the *relationship* while\n * deleting the volatile *value* (ADR-0003): a `useId` renumbering becomes a\n * no-op, while breaking a `label ↔ input` association stays a real structural\n * change. Masking both to a constant cannot tell those apart.\n */\nexport const ID_REFERENCE_ATTRIBUTES: readonly string[] = [\n 'id', 'for', 'form', 'list', 'headers',\n 'aria-labelledby', 'aria-describedby', 'aria-controls', 'aria-owns',\n 'aria-activedescendant', 'aria-details', 'aria-errormessage', 'aria-flowto',\n];\n\n/** Attributes holding a *space-separated list* of id references, not just one. */\nexport const ID_REFERENCE_LIST_ATTRIBUTES: readonly string[] = [\n 'headers', 'aria-labelledby', 'aria-describedby', 'aria-controls',\n 'aria-owns', 'aria-flowto',\n];\n"]}