@variance-authority/core 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +454 -0
  2. package/README.md +13 -10
  3. package/dist/attribute/component-hash.d.ts +3 -2
  4. package/dist/attribute/component-hash.js +3 -2
  5. package/dist/attribute/component-hash.js.map +1 -1
  6. package/dist/attribute/composition.d.ts +16 -0
  7. package/dist/attribute/composition.js +4 -6
  8. package/dist/attribute/composition.js.map +1 -1
  9. package/dist/attribute/index.d.ts +3 -1
  10. package/dist/attribute/index.js +2 -1
  11. package/dist/attribute/index.js.map +1 -1
  12. package/dist/attribute/region.js +1 -5
  13. package/dist/attribute/region.js.map +1 -1
  14. package/dist/attribute/source.d.ts +10 -24
  15. package/dist/attribute/source.js +4 -38
  16. package/dist/attribute/source.js.map +1 -1
  17. package/dist/attribute/subject-pieces.d.ts +37 -0
  18. package/dist/attribute/subject-pieces.js +122 -0
  19. package/dist/attribute/subject-pieces.js.map +1 -0
  20. package/dist/format/capture.d.ts +25 -0
  21. package/dist/format/capture.js +6 -0
  22. package/dist/format/capture.js.map +1 -1
  23. package/dist/format/document.d.ts +2 -2
  24. package/dist/format/document.js +8 -2
  25. package/dist/format/document.js.map +1 -1
  26. package/dist/format/drive.d.ts +191 -0
  27. package/dist/format/drive.js +343 -0
  28. package/dist/format/drive.js.map +1 -0
  29. package/dist/format/environment.d.ts +20 -2
  30. package/dist/format/environment.js +7 -0
  31. package/dist/format/environment.js.map +1 -1
  32. package/dist/format/index.d.ts +6 -3
  33. package/dist/format/index.js +4 -2
  34. package/dist/format/index.js.map +1 -1
  35. package/dist/format/intervention.d.ts +79 -0
  36. package/dist/format/intervention.js.map +1 -1
  37. package/dist/format/origin.d.ts +32 -0
  38. package/dist/format/origin.js +54 -0
  39. package/dist/format/origin.js.map +1 -0
  40. package/dist/format/snapshot.d.ts +2 -0
  41. package/dist/format/snapshot.js.map +1 -1
  42. package/dist/format/stabilize.d.ts +28 -5
  43. package/dist/format/stabilize.js +48 -100
  44. package/dist/format/stabilize.js.map +1 -1
  45. package/dist/format/waits.d.ts +12 -0
  46. package/dist/format/waits.js +156 -0
  47. package/dist/format/waits.js.map +1 -0
  48. package/dist/index.d.ts +2 -1
  49. package/dist/index.js +2 -1
  50. package/dist/index.js.map +1 -1
  51. package/dist/nearest.d.ts +46 -0
  52. package/dist/nearest.js +102 -0
  53. package/dist/nearest.js.map +1 -0
  54. package/dist/relate/before.d.ts +19 -24
  55. package/dist/relate/before.js +35 -37
  56. package/dist/relate/before.js.map +1 -1
  57. package/dist/relate/beyond.d.ts +59 -0
  58. package/dist/relate/beyond.js +98 -0
  59. package/dist/relate/beyond.js.map +1 -0
  60. package/dist/relate/carried.d.ts +122 -0
  61. package/dist/relate/carried.js +279 -0
  62. package/dist/relate/carried.js.map +1 -0
  63. package/dist/relate/dominate.d.ts +19 -0
  64. package/dist/relate/dominate.js +77 -0
  65. package/dist/relate/dominate.js.map +1 -0
  66. package/dist/relate/index.d.ts +3 -0
  67. package/dist/relate/index.js +3 -0
  68. package/dist/relate/index.js.map +1 -1
  69. package/dist/relate/merkle.js +2 -11
  70. package/dist/relate/merkle.js.map +1 -1
  71. package/dist/relate/narrow.d.ts +2 -2
  72. package/dist/relate/narrow.js +26 -73
  73. package/dist/relate/narrow.js.map +1 -1
  74. package/dist/relate/reach.d.ts +1 -1
  75. package/dist/relate/reach.js +10 -53
  76. package/dist/relate/reach.js.map +1 -1
  77. package/dist/relate/records.d.ts +1 -1
  78. package/dist/relate/records.js.map +1 -1
  79. package/dist/rules/index.d.ts +3 -3
  80. package/dist/rules/index.js +3 -3
  81. package/dist/rules/index.js.map +1 -1
  82. package/dist/rules/normalize/cascade.d.ts +12 -0
  83. package/dist/rules/normalize/cascade.js +34 -16
  84. package/dist/rules/normalize/cascade.js.map +1 -1
  85. package/dist/rules/normalize/fixture.d.ts +1 -0
  86. package/dist/rules/normalize/fixture.js +1 -0
  87. package/dist/rules/normalize/fixture.js.map +1 -1
  88. package/dist/rules/normalize/index.d.ts +2 -2
  89. package/dist/rules/normalize/index.js +9 -2
  90. package/dist/rules/normalize/index.js.map +1 -1
  91. package/dist/rules/normalize/value.d.ts +13 -2
  92. package/dist/rules/normalize/value.js +42 -8
  93. package/dist/rules/normalize/value.js.map +1 -1
  94. package/dist/rules/normalize/wrapper.js +5 -0
  95. package/dist/rules/normalize/wrapper.js.map +1 -1
  96. package/dist/rules/ruleset.d.ts +24 -4
  97. package/dist/rules/ruleset.js +24 -4
  98. package/dist/rules/ruleset.js.map +1 -1
  99. package/dist/segment/index.d.ts +20 -4
  100. package/dist/segment/index.js +28 -7
  101. package/dist/segment/index.js.map +1 -1
  102. package/dist/shard/index.d.ts +11 -0
  103. package/dist/shard/index.js +11 -0
  104. package/dist/shard/index.js.map +1 -0
  105. package/dist/shard/placement.d.ts +117 -0
  106. package/dist/shard/placement.js +170 -0
  107. package/dist/shard/placement.js.map +1 -0
  108. package/package.json +8 -1
@@ -1 +1 @@
1
- {"version":3,"file":"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":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAEhE;;;;;;;;;;;;GAYG;AAEH,6EAA6E;AAC7E,MAAM,gBAAgB,GAAqC;IACzD,EAAE,EAAE,CAAC;IACL,EAAE,EAAE,EAAE,GAAG,EAAE;IACX,EAAE,EAAE,EAAE;IACN,EAAE,EAAE,EAAE;IACN,EAAE,EAAE,EAAE,GAAG,IAAI;IACb,EAAE,EAAE,EAAE,GAAG,IAAI;IACb,CAAC,EAAE,EAAE,GAAG,KAAK;CACd,CAAC;AAEF,0BAA0B;AAC1B,MAAM,MAAM,GAAqC;IAC/C,GAAG,EAAE,CAAC;IACN,IAAI,EAAE,GAAG;IACT,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,EAAE;IAClB,IAAI,EAAE,GAAG;CACV,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,cAAc,GAAsB;IAC/C,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK;IACjD,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI;IACtC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG;CAC9C,CAAC;AAEF,qFAAqF;AACrF,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAE3B;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,KAAa,EAAE,MAAe;IAChF,IAAI,eAAe,CAAC,QAAQ,CAAC;QAAE,OAAO,iBAAiB,CAAC,KAAK,CAAC,CAAC;IAC/D,OAAO,kBAAkB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,MAAe;IAC/D,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAE3B,OAAO,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;QAC5B,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAE,CAAC;QAE3B,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACpB,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG;gBAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACjE,KAAK,IAAI,CAAC,CAAC;YACX,SAAS;QACX,CAAC;QAED,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACjC,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YACvC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YACtC,KAAK,GAAG,GAAG,GAAG,CAAC,CAAC;YAChB,SAAS;QACX,CAAC;QAED,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YACxD,MAAM,GAAG,GAAG,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC;YAC5C,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;YAC1E,KAAK,GAAG,GAAG,GAAG,CAAC,CAAC;YAChB,SAAS;QACX,CAAC;QAED,MAAM,OAAO,GAAG,+CAA+C,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACzF,IAAI,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrC,GAAG,CAAC,IAAI,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5C,KAAK,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YAC3B,SAAS;QACX,CAAC;QAED,MAAM,UAAU,GAAG,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9D,IAAI,UAAU,EAAE,CAAC;YACf,iEAAiE;YACjE,6DAA6D;YAC7D,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;YACtC,KAAK,IAAI,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YAC9B,SAAS;QACX,CAAC;QAED,sEAAsE;QACtE,+BAA+B;QAC/B,IAAI,IAAI,KAAK,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG;YAAE,GAAG,CAAC,GAAG,EAAE,CAAC;QAC3D,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,KAAK,IAAI,CAAC,CAAC;IACb,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AAC7B,CAAC;AAED,+EAA+E;AAC/E,SAAS,cAAc,CAAC,KAAa,EAAE,MAA0B;IAC/D,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACvC,MAAM,KAAK,GAAG,gCAAgC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IACvB,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACpC,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACxC,OAAO,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,KAAM,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,KAAM,CAAC,CAAC,CAAC,GAAG,CAAC;AACvE,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,MAAM,KAAK,GAAG,sDAAsD,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACjF,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC;IAEvC,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;IAC/C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,CAAC;IAErC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;QAAE,OAAO,KAAK,CAAC,WAAW,EAAE,CAAC;IAE5D,yEAAyE;IACzE,kCAAkC;IAClC,IAAI,SAAS,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,EAAE,IAAI,IAAI,IAAI,gBAAgB,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;QACzF,OAAO,GAAG,CAAC;IACb,CAAC;IAED,MAAM,YAAY,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IAC5C,IAAI,YAAY,KAAK,SAAS;QAAE,OAAO,GAAG,KAAK,CAAC,SAAS,GAAG,YAAY,CAAC,IAAI,CAAC;IAE9E,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,GAAG,KAAK,CAAC,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC;IAE7E,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,KAAK,CAAC,KAAa;IAC1B,MAAM,MAAM,GAAG,EAAE,IAAI,gBAAgB,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC;IACpD,OAAO,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;AAC9C,CAAC;AAED,SAAS,YAAY,CAAC,KAAa,EAAE,KAAa;IAChD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YACtB,CAAC,IAAI,CAAC,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK;YAAE,OAAO,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;AAC1B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,SAAiB;IAC5D,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,KAAyB,CAAC;IAE9B,KAAK,IAAI,CAAC,GAAG,SAAS,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,IAAI,IAAI,KAAK,IAAI;gBAAE,CAAC,IAAI,CAAC,CAAC;iBACrB,IAAI,IAAI,KAAK,KAAK;gBAAE,KAAK,GAAG,SAAS,CAAC;QAC7C,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACxC,KAAK,GAAG,IAAI,CAAC;QACf,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACxB,KAAK,IAAI,CAAC,CAAC;QACb,CAAC;aAAM,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACxB,KAAK,IAAI,CAAC,CAAC;YACX,IAAI,KAAK,KAAK,CAAC;gBAAE,OAAO,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,MAAM,CAAC;AACtB,CAAC","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":"AAGA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAC5B,GAAY,EACZ,IAAkB,EAClB,UAAsD;IAEtD,IAAI,IAAI,CAAC,GAAG,KAAK,KAAK,IAAI,IAAI,CAAC,GAAG,KAAK,MAAM;QAAE,OAAO,KAAK,CAAC;IAC5D,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACjG,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtE,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1D,IAAI,GAAG,CAAC,cAAc,KAAK,SAAS,IAAI,GAAG,CAAC,cAAc,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAEpF,wEAAwE;IACxE,EAAE;IACF,gFAAgF;IAChF,4EAA4E;IAC5E,8EAA8E;IAC9E,8EAA8E;IAC9E,+EAA+E;IAC/E,6EAA6E;IAC7E,8EAA8E;IAC9E,iBAAiB;IACjB,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAE5E,+EAA+E;IAC/E,gFAAgF;IAChF,gFAAgF;IAChF,8EAA8E;IAC9E,8EAA8E;IAC9E,kEAAkE;IAClE,EAAE;IACF,yEAAyE;IACzE,sEAAsE;IACtE,4EAA4E;IAC5E,4EAA4E;IAC5E,2DAA2D;IAC3D,EAAE;IACF,+EAA+E;IAC/E,8EAA8E;IAC9E,0EAA0E;IAC1E,IAAI,IAAI,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAE7C,MAAM,YAAY,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC;IAE9D,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3D,kEAAkE;QAClE,EAAE;QACF,wEAAwE;QACxE,yEAAyE;QACzE,2EAA2E;QAC3E,4EAA4E;QAC5E,yEAAyE;QACzE,wEAAwE;QACxE,4EAA4E;QAC5E,yEAAyE;QACzE,sEAAsE;QACtE,wEAAwE;QACxE,EAAE;QACF,0EAA0E;QAC1E,gDAAgD;QAChD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,SAAS;QAC1C,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;IACzD,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,gBAAgB,GAAwB,IAAI,GAAG,EAAE,CAAC;AAExD;;;;;GAKG;AACH,MAAM,YAAY,GAAgD;IAChE,2EAA2E;IAC3E,uEAAuE;IACvE,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,CAAC;IACxC,QAAQ,EAAE,CAAC,QAAQ,CAAC;IACpB,YAAY,EAAE,CAAC,aAAa,EAAE,YAAY,CAAC;IAC3C,YAAY,EAAE,CAAC,SAAS,CAAC;IACzB,YAAY,EAAE,CAAC,SAAS,CAAC;IACzB,OAAO,EAAE,CAAC,GAAG,CAAC;IACd,UAAU,EAAE,CAAC,SAAS,CAAC;IACvB,SAAS,EAAE,CAAC,MAAM,CAAC;IACnB,MAAM,EAAE,CAAC,MAAM,CAAC;IAChB,iBAAiB,EAAE,CAAC,MAAM,CAAC;IAC3B,gBAAgB,EAAE,CAAC,QAAQ,CAAC;IAC5B,kBAAkB,EAAE,CAAC,gBAAgB,CAAC;IACtC,kBAAkB,EAAE,CAAC,MAAM,CAAC;IAC5B,SAAS,EAAE,CAAC,MAAM,CAAC;IACnB,KAAK,EAAE,CAAC,MAAM,CAAC;IACf,KAAK,EAAE,CAAC,MAAM,CAAC;IACf,KAAK,EAAE,CAAC,MAAM,CAAC;IACf,MAAM,EAAE,CAAC,MAAM,CAAC;IAChB,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC;IAC1B,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,CAAC,MAAM,CAAC;IACrB,YAAY,EAAE,CAAC,MAAM,CAAC;IACtB,cAAc,EAAE,CAAC,MAAM,CAAC;IACxB,oBAAoB,EAAE,CAAC,SAAS,CAAC;IACjC,YAAY,EAAE,CAAC,MAAM,CAAC;IACtB,cAAc,EAAE,CAAC,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,aAAa,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAEhH,SAAS,kBAAkB,CAAC,QAAgB,EAAE,KAAa;IACzD,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAE5C,IAAI,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,KAAK,MAAM,CAAC;IAC3F,IAAI,QAAQ,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/E,IAAI,QAAQ,KAAK,eAAe;QAAE,OAAO,KAAK,KAAK,MAAM,CAAC;IAC1D,IAAI,QAAQ,KAAK,eAAe;QAAE,OAAO,IAAI,CAAC;IAC9C,IAAI,QAAQ,KAAK,YAAY,IAAI,QAAQ,KAAK,aAAa;QAAE,OAAO,KAAK,KAAK,MAAM,CAAC;IAErF,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QAChE,OAAO,KAAK,KAAK,GAAG,IAAI,KAAK,KAAK,MAAM,CAAC;IAC3C,CAAC;IAED,2EAA2E;IAC3E,6BAA6B;IAC7B,OAAO,KAAK,CAAC;AACf,CAAC","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"]}
@@ -17,9 +17,7 @@
17
17
  *
18
18
  * The reader that matters next is not this one. Every field is a run of
19
19
  * fixed-width little-endian values at a computable offset, so a reader in
20
- * another language takes a slice where this one takes a loop — the same bargain
21
- * [`record-format.ts`](../../../sense/src/test-selection/record-format.ts)
22
- * already made for the coverage segments.
20
+ * another language takes a slice where this one takes a loop.
23
21
  *
24
22
  * ## Why the header is JSON
25
23
  *
@@ -58,9 +56,27 @@ export interface OpenSegment {
58
56
  /** Refuse the file, in the words the caller's format uses. */
59
57
  reject(): Error;
60
58
  }
59
+ /**
60
+ * A segment of the named format, written in a version this reader does not
61
+ * read. Not a damaged file: the bytes are what another release wrote, and a
62
+ * caller that rebuilds or refuses says which release that was.
63
+ */
64
+ export declare class OtherSegmentVersion extends Error {
65
+ /** The version the segment's header names. */
66
+ readonly written: number;
67
+ /** The version this reader reads. */
68
+ readonly reads: number;
69
+ readonly name = "OtherSegmentVersion";
70
+ constructor(what: string,
71
+ /** The version the segment's header names. */
72
+ written: number,
73
+ /** The version this reader reads. */
74
+ reads: number);
75
+ }
61
76
  /**
62
77
  * Open a segment, refusing anything that is not one of the named format and
63
- * version.
78
+ * version. Another version of the named format is refused with
79
+ * {@link OtherSegmentVersion}, so a caller can tell it from a damaged file.
64
80
  *
65
81
  * `what` is the noun a rejection is phrased with — "source index", "suite index"
66
82
  * — because the one thing a caller of this can usefully be told is which reader
@@ -17,9 +17,7 @@
17
17
  *
18
18
  * The reader that matters next is not this one. Every field is a run of
19
19
  * fixed-width little-endian values at a computable offset, so a reader in
20
- * another language takes a slice where this one takes a loop — the same bargain
21
- * [`record-format.ts`](../../../sense/src/test-selection/record-format.ts)
22
- * already made for the coverage segments.
20
+ * another language takes a slice where this one takes a loop.
23
21
  *
24
22
  * ## Why the header is JSON
25
23
  *
@@ -83,9 +81,29 @@ function concat(parts) {
83
81
  }
84
82
  return result;
85
83
  }
84
+ /**
85
+ * A segment of the named format, written in a version this reader does not
86
+ * read. Not a damaged file: the bytes are what another release wrote, and a
87
+ * caller that rebuilds or refuses says which release that was.
88
+ */
89
+ export class OtherSegmentVersion extends Error {
90
+ written;
91
+ reads;
92
+ name = 'OtherSegmentVersion';
93
+ constructor(what,
94
+ /** The version the segment's header names. */
95
+ written,
96
+ /** The version this reader reads. */
97
+ reads) {
98
+ super(`a variance-authority ${what} written in format version ${written}, and this reader reads version ${reads}`);
99
+ this.written = written;
100
+ this.reads = reads;
101
+ }
102
+ }
86
103
  /**
87
104
  * Open a segment, refusing anything that is not one of the named format and
88
- * version.
105
+ * version. Another version of the named format is refused with
106
+ * {@link OtherSegmentVersion}, so a caller can tell it from a damaged file.
89
107
  *
90
108
  * `what` is the noun a rejection is phrased with — "source index", "suite index"
91
109
  * — because the one thing a caller of this can usefully be told is which reader
@@ -112,9 +130,12 @@ export function openSegment(format, version, input, what) {
112
130
  catch {
113
131
  throw reject();
114
132
  }
115
- if (header.format !== format ||
116
- header.version !== version ||
117
- !validSections(header.sections, raw.length - 4 - headerLength))
133
+ if (header.format !== format)
134
+ throw reject();
135
+ if (header.version !== version) {
136
+ throw typeof header.version === 'number' ? new OtherSegmentVersion(what, header.version, version) : reject();
137
+ }
138
+ if (!validSections(header.sections, raw.length - 4 - headerLength))
118
139
  throw reject();
119
140
  const base = 4 + headerLength;
120
141
  const found = new Map(header.sections.map((section) => [section.name, section]));
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/segment/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `@variance-authority/core/segment` — a column of facts, as bytes.\n *\n * Two things in this repository already had to be written down and read back on\n * another machine: the source index a scan reuses, and now the suite index a run\n * leaves behind. Both are the same shape underneath — a pile of repeated strings\n * and a pile of small integers — and both were going to grow the same four\n * hundred lines of offset arithmetic independently.\n *\n * So the arithmetic is here and the schemas are not. This module knows about\n * named sections, alignment, interned strings and the validation that a decode\n * must perform before it believes a file. It knows nothing about what any\n * section means, which is the property that lets one format change without\n * touching the other.\n *\n * ## Why columns\n *\n * The reader that matters next is not this one. Every field is a run of\n * fixed-width little-endian values at a computable offset, so a reader in\n * another language takes a slice where this one takes a loop — the same bargain\n * [`record-format.ts`](../../../sense/src/test-selection/record-format.ts)\n * already made for the coverage segments.\n *\n * ## Why the header is JSON\n *\n * It is the one part that is read before anything is known, it is a few hundred\n * bytes against a payload measured in megabytes, and being self-describing is\n * what lets a decoder reject a file by name instead of by crash. The payload —\n * the part that repeats — never is.\n *\n * ## The one rule\n *\n * A malformed reference rejects the whole file. There is no partial read and no\n * salvage: a segment is a cache or a baseline, both of which may be rebuilt, and\n * half of either is worse than neither.\n */\n\nconst ALIGNMENT = 8;\n\n// Declared rather than imported. This package takes no platform's types — it is\n// the layer every other one is allowed to run wherever it likes — and these two\n// are the standard text codecs that every runtime it targets already has. A\n// `Buffer` would have been shorter and would have made `core` a Node package.\ndeclare const TextEncoder: { new (): { encode(input: string): Uint8Array } };\ndeclare const TextDecoder: { new (): { decode(input: Uint8Array): string } };\n\nconst utf8 = { encode: new TextEncoder(), decode: new TextDecoder() };\n\n/** The sentinel a `u32` column uses for \"no value here\". */\nexport const NONE = 0xffff_ffff;\n\ninterface Section {\n readonly name: string;\n readonly offset: number;\n readonly length: number;\n readonly width: 1 | 4;\n}\n\ninterface Header {\n readonly format: string;\n readonly version: number;\n readonly sections: readonly Section[];\n}\n\n/** A column, by the width its element type already declares. */\nexport type Column = Uint8Array | Uint32Array;\n\n/**\n * Write named columns as one aligned segment.\n *\n * Width is taken from the array's own type rather than from a convention about\n * the section's name, so a column that is read back as `u8` is one that was\n * written as `Uint8Array` and a mismatch is a compile error rather than a\n * decode that succeeds against the wrong stride.\n */\nexport function encodeSegment(\n format: string,\n version: number,\n columns: Readonly<Record<string, Column>>,\n): Uint8Array {\n const chunks: Uint8Array[] = [];\n const sections: Section[] = [];\n let offset = 0;\n\n for (const [name, column] of Object.entries(columns)) {\n const value = new Uint8Array(column.buffer, column.byteOffset, column.byteLength);\n sections.push({\n name,\n offset,\n length: value.length,\n width: column instanceof Uint8Array ? 1 : 4,\n });\n chunks.push(value);\n offset += value.length;\n const padding = aligned(offset) - offset;\n if (padding > 0) chunks.push(new Uint8Array(padding));\n offset += padding;\n }\n\n const header: Header = { format, version, sections };\n const encoded = utf8.encode.encode(JSON.stringify(header));\n // The header is padded rather than merely written, so every section offset it\n // names is an aligned offset into the file and not only into the payload.\n const headerLength = aligned(4 + encoded.length) - 4;\n const prefix = new Uint8Array(4);\n new DataView(prefix.buffer).setUint32(0, headerLength, true);\n return concat([prefix, encoded, new Uint8Array(headerLength - encoded.length), ...chunks]);\n}\n\nfunction concat(parts: readonly Uint8Array[]): Uint8Array {\n const result = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));\n let offset = 0;\n for (const part of parts) {\n result.set(part, offset);\n offset += part.length;\n }\n return result;\n}\n\n/** Columns of one opened segment, by name. Every accessor may reject the file. */\nexport interface OpenSegment {\n /** A `u8` column. Absent or of the wrong width rejects the file. */\n u8(name: string): Uint8Array;\n /** A `u32` column. Absent or of the wrong width rejects the file. */\n u32(name: string): Uint32Array;\n /** A `u32` column a writer of this version may legitimately not have written. */\n maybeU32(name: string): Uint32Array;\n /** Refuse the file, in the words the caller's format uses. */\n reject(): Error;\n}\n\n/**\n * Open a segment, refusing anything that is not one of the named format and\n * version.\n *\n * `what` is the noun a rejection is phrased with — \"source index\", \"suite index\"\n * — because the one thing a caller of this can usefully be told is which reader\n * refused, and a shared message would name none of them.\n */\nexport function openSegment(\n format: string,\n version: number,\n input: Uint8Array,\n what: string,\n): OpenSegment {\n const reject = (): Error => new Error(`not a variance-authority ${what}`);\n // Copied when it does not start on an alignment boundary, because the whole\n // point of the padding is that a `u32` column can be a view rather than a\n // parse, and a view is only legal at a multiple of its own width. A read that\n // lands mid-pool — which is what a small file off a disk usually is — pays one\n // copy for that guarantee instead of losing it.\n const raw = input.byteOffset % ALIGNMENT === 0 ? input : input.slice();\n if (raw.length < 4) throw reject();\n const headerLength = new DataView(raw.buffer, raw.byteOffset, raw.byteLength).getUint32(0, true);\n if (headerLength > raw.length - 4) throw reject();\n\n let header: Header;\n try {\n const text = utf8.decode.decode(raw.subarray(4, 4 + headerLength));\n header = JSON.parse(text.replace(/\\0+$/, '')) as Header;\n } catch {\n throw reject();\n }\n if (\n header.format !== format ||\n header.version !== version ||\n !validSections(header.sections, raw.length - 4 - headerLength)\n ) throw reject();\n\n const base = 4 + headerLength;\n const found = new Map(header.sections.map((section) => [section.name, section]));\n const at = (name: string, width: 1 | 4): Section => {\n const section = found.get(name);\n if (section === undefined || section.width !== width) throw reject();\n return section;\n };\n\n return {\n reject,\n u8(name) {\n const section = at(name, 1);\n return new Uint8Array(raw.buffer, raw.byteOffset + base + section.offset, section.length);\n },\n u32(name) {\n const section = at(name, 4);\n if (section.length % 4 !== 0) throw reject();\n return new Uint32Array(\n raw.buffer,\n raw.byteOffset + base + section.offset,\n section.length / 4,\n );\n },\n maybeU32(name) {\n const section = found.get(name);\n if (section === undefined) return new Uint32Array();\n if (section.width !== 4 || section.length % 4 !== 0) throw reject();\n return new Uint32Array(\n raw.buffer,\n raw.byteOffset + base + section.offset,\n section.length / 4,\n );\n },\n };\n}\n\n/**\n * Every distinct string, sorted, and the number each one is written as.\n *\n * Sorted rather than first-seen because two encodes of equal facts must produce\n * equal bytes: a segment is content-addressed by whoever caches it, and an\n * identical index that hashes differently defeats every transport that would\n * otherwise have skipped the upload.\n */\nexport function intern(values: Iterable<string>): {\n readonly strings: readonly string[];\n readonly id: (value: string) => number;\n /** `NONE` for `undefined`, so an absent field costs a column and not a branch. */\n readonly optionalId: (value: string | undefined) => number;\n} {\n const strings = [...new Set(values)].sort(codeUnitOrder);\n const ids = new Map(strings.map((value, index) => [value, index]));\n const id = (value: string): number => {\n const found = ids.get(value);\n if (found === undefined) throw new Error(`string not interned: ${JSON.stringify(value)}`);\n return found;\n };\n return { strings, id, optionalId: (value) => (value === undefined ? NONE : id(value)) };\n}\n\n/** The two columns a dictionary is written as: the bytes, and where each one ends. */\nexport function stringColumns(strings: readonly string[]): {\n readonly blob: Uint8Array;\n readonly off: Uint32Array;\n} {\n const encoded = strings.map((value) => utf8.encode.encode(value));\n return { blob: concat(encoded), off: offsetsOf(encoded.map((value) => value.length)) };\n}\n\n/** Read strings back out of a dictionary, rejecting any id the blob cannot hold. */\nexport function stringReader(\n blob: Uint8Array,\n off: Uint32Array,\n reject: () => Error,\n): { readonly text: (id: number) => string; readonly optional: (id: number) => string | undefined } {\n validateOffsets(off, blob.length, off.length - 1, reject);\n const text = (id: number): string => {\n const start = off[id];\n const end = off[id + 1];\n if (start === undefined || end === undefined) throw reject();\n return utf8.decode.decode(blob.subarray(start, end));\n };\n return { text, optional: (id) => (id === NONE ? undefined : text(id)) };\n}\n\n/** A running-total column: `result[n]` is where row `n` starts, `result.at(-1)` the end. */\nexport function offsetsOf(lengths: readonly number[]): Uint32Array {\n const result = new Uint32Array(lengths.length + 1);\n for (let index = 0; index < lengths.length; index += 1) {\n result[index + 1] = result[index]! + lengths[index]!;\n }\n return result;\n}\n\n/**\n * Check an offset column before any row is read through it.\n *\n * Checked here rather than per row because an offset column is the one thing in\n * a segment that can turn a corrupt file into a *plausible* value: a pair that\n * runs backwards reads a neighbouring row's data as this row's, and nothing\n * downstream would ever notice.\n */\nexport function validateOffsets(\n column: Uint32Array,\n end: number,\n rows: number,\n reject: () => Error,\n): void {\n if (column.length !== rows + 1 || column[0] !== 0 || column[column.length - 1] !== end) {\n throw reject();\n }\n for (let index = 1; index < column.length; index += 1) {\n if (column[index]! < column[index - 1]!) throw reject();\n }\n}\n\n/** Columns that describe the same rows must agree about how many there are. */\nexport function sameLength(\n length: number,\n columns: readonly { readonly length: number }[],\n reject: () => Error,\n): void {\n if (columns.some((column) => column.length !== length)) throw reject();\n}\n\n/** The indices row `row` owns in the column an offset column points into. */\nexport function rangeOf(offsets: Uint32Array, row: number, reject: () => Error): number[] {\n const start = offsets[row];\n const end = offsets[row + 1];\n if (start === undefined || end === undefined) throw reject();\n return Array.from({ length: end - start }, (_, index) => start + index);\n}\n\n/** A stored boolean, refusing the file for anything that is not one. */\nexport function flagOf(value: number | undefined, reject: () => Error): boolean {\n if (value !== 0 && value !== 1) throw reject();\n return value === 1;\n}\n\nfunction validSections(sections: readonly Section[], available: number): boolean {\n if (!Array.isArray(sections) || sections.length === 0) return false;\n if (new Set(sections.map((section) => section.name)).size !== sections.length) return false;\n const ordered = [...sections].sort((left, right) => left.offset - right.offset);\n let end = 0;\n for (const section of ordered) {\n if (\n typeof section.name !== 'string' ||\n !Number.isSafeInteger(section.offset) ||\n !Number.isSafeInteger(section.length) ||\n section.offset < end ||\n section.offset % ALIGNMENT !== 0 ||\n section.length < 0 ||\n section.length > available - section.offset ||\n (section.width !== 1 && section.width !== 4)\n ) return false;\n end = section.offset + section.length;\n }\n return ordered[0]?.offset === 0;\n}\n\nfunction aligned(value: number): number {\n return Math.ceil(value / ALIGNMENT) * ALIGNMENT;\n}\n\n/**\n * Order two strings by UTF-16 code unit, which is the order a segment is sorted in.\n *\n * Exported because a schema that sorts its own values before interning them has\n * to sort them the same way this does, and `localeCompare` is the trap: it is\n * locale-dependent, so an index written on one machine would interleave\n * differently on another and two encodes of equal facts would stop producing\n * equal bytes.\n */\nexport function codeUnitOrder(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/segment/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `@variance-authority/core/segment` — a column of facts, as bytes.\n *\n * Two things in this repository already had to be written down and read back on\n * another machine: the source index a scan reuses, and now the suite index a run\n * leaves behind. Both are the same shape underneath — a pile of repeated strings\n * and a pile of small integers — and both were going to grow the same four\n * hundred lines of offset arithmetic independently.\n *\n * So the arithmetic is here and the schemas are not. This module knows about\n * named sections, alignment, interned strings and the validation that a decode\n * must perform before it believes a file. It knows nothing about what any\n * section means, which is the property that lets one format change without\n * touching the other.\n *\n * ## Why columns\n *\n * The reader that matters next is not this one. Every field is a run of\n * fixed-width little-endian values at a computable offset, so a reader in\n * another language takes a slice where this one takes a loop.\n *\n * ## Why the header is JSON\n *\n * It is the one part that is read before anything is known, it is a few hundred\n * bytes against a payload measured in megabytes, and being self-describing is\n * what lets a decoder reject a file by name instead of by crash. The payload —\n * the part that repeats — never is.\n *\n * ## The one rule\n *\n * A malformed reference rejects the whole file. There is no partial read and no\n * salvage: a segment is a cache or a baseline, both of which may be rebuilt, and\n * half of either is worse than neither.\n */\n\nconst ALIGNMENT = 8;\n\n// Declared rather than imported. This package takes no platform's types — it is\n// the layer every other one is allowed to run wherever it likes — and these two\n// are the standard text codecs that every runtime it targets already has. A\n// `Buffer` would have been shorter and would have made `core` a Node package.\ndeclare const TextEncoder: { new (): { encode(input: string): Uint8Array } };\ndeclare const TextDecoder: { new (): { decode(input: Uint8Array): string } };\n\nconst utf8 = { encode: new TextEncoder(), decode: new TextDecoder() };\n\n/** The sentinel a `u32` column uses for \"no value here\". */\nexport const NONE = 0xffff_ffff;\n\ninterface Section {\n readonly name: string;\n readonly offset: number;\n readonly length: number;\n readonly width: 1 | 4;\n}\n\ninterface Header {\n readonly format: string;\n readonly version: number;\n readonly sections: readonly Section[];\n}\n\n/** A column, by the width its element type already declares. */\nexport type Column = Uint8Array | Uint32Array;\n\n/**\n * Write named columns as one aligned segment.\n *\n * Width is taken from the array's own type rather than from a convention about\n * the section's name, so a column that is read back as `u8` is one that was\n * written as `Uint8Array` and a mismatch is a compile error rather than a\n * decode that succeeds against the wrong stride.\n */\nexport function encodeSegment(\n format: string,\n version: number,\n columns: Readonly<Record<string, Column>>,\n): Uint8Array {\n const chunks: Uint8Array[] = [];\n const sections: Section[] = [];\n let offset = 0;\n\n for (const [name, column] of Object.entries(columns)) {\n const value = new Uint8Array(column.buffer, column.byteOffset, column.byteLength);\n sections.push({\n name,\n offset,\n length: value.length,\n width: column instanceof Uint8Array ? 1 : 4,\n });\n chunks.push(value);\n offset += value.length;\n const padding = aligned(offset) - offset;\n if (padding > 0) chunks.push(new Uint8Array(padding));\n offset += padding;\n }\n\n const header: Header = { format, version, sections };\n const encoded = utf8.encode.encode(JSON.stringify(header));\n // The header is padded rather than merely written, so every section offset it\n // names is an aligned offset into the file and not only into the payload.\n const headerLength = aligned(4 + encoded.length) - 4;\n const prefix = new Uint8Array(4);\n new DataView(prefix.buffer).setUint32(0, headerLength, true);\n return concat([prefix, encoded, new Uint8Array(headerLength - encoded.length), ...chunks]);\n}\n\nfunction concat(parts: readonly Uint8Array[]): Uint8Array {\n const result = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));\n let offset = 0;\n for (const part of parts) {\n result.set(part, offset);\n offset += part.length;\n }\n return result;\n}\n\n/** Columns of one opened segment, by name. Every accessor may reject the file. */\nexport interface OpenSegment {\n /** A `u8` column. Absent or of the wrong width rejects the file. */\n u8(name: string): Uint8Array;\n /** A `u32` column. Absent or of the wrong width rejects the file. */\n u32(name: string): Uint32Array;\n /** A `u32` column a writer of this version may legitimately not have written. */\n maybeU32(name: string): Uint32Array;\n /** Refuse the file, in the words the caller's format uses. */\n reject(): Error;\n}\n\n/**\n * A segment of the named format, written in a version this reader does not\n * read. Not a damaged file: the bytes are what another release wrote, and a\n * caller that rebuilds or refuses says which release that was.\n */\nexport class OtherSegmentVersion extends Error {\n override readonly name = 'OtherSegmentVersion';\n\n constructor(\n what: string,\n /** The version the segment's header names. */\n readonly written: number,\n /** The version this reader reads. */\n readonly reads: number,\n ) {\n super(`a variance-authority ${what} written in format version ${written}, and this reader reads version ${reads}`);\n }\n}\n\n/**\n * Open a segment, refusing anything that is not one of the named format and\n * version. Another version of the named format is refused with\n * {@link OtherSegmentVersion}, so a caller can tell it from a damaged file.\n *\n * `what` is the noun a rejection is phrased with — \"source index\", \"suite index\"\n * — because the one thing a caller of this can usefully be told is which reader\n * refused, and a shared message would name none of them.\n */\nexport function openSegment(\n format: string,\n version: number,\n input: Uint8Array,\n what: string,\n): OpenSegment {\n const reject = (): Error => new Error(`not a variance-authority ${what}`);\n // Copied when it does not start on an alignment boundary, because the whole\n // point of the padding is that a `u32` column can be a view rather than a\n // parse, and a view is only legal at a multiple of its own width. A read that\n // lands mid-pool — which is what a small file off a disk usually is — pays one\n // copy for that guarantee instead of losing it.\n const raw = input.byteOffset % ALIGNMENT === 0 ? input : input.slice();\n if (raw.length < 4) throw reject();\n const headerLength = new DataView(raw.buffer, raw.byteOffset, raw.byteLength).getUint32(0, true);\n if (headerLength > raw.length - 4) throw reject();\n\n let header: Header;\n try {\n const text = utf8.decode.decode(raw.subarray(4, 4 + headerLength));\n header = JSON.parse(text.replace(/\\0+$/, '')) as Header;\n } catch {\n throw reject();\n }\n if (header.format !== format) throw reject();\n if (header.version !== version) {\n throw typeof header.version === 'number' ? new OtherSegmentVersion(what, header.version, version) : reject();\n }\n if (!validSections(header.sections, raw.length - 4 - headerLength)) throw reject();\n\n const base = 4 + headerLength;\n const found = new Map(header.sections.map((section) => [section.name, section]));\n const at = (name: string, width: 1 | 4): Section => {\n const section = found.get(name);\n if (section === undefined || section.width !== width) throw reject();\n return section;\n };\n\n return {\n reject,\n u8(name) {\n const section = at(name, 1);\n return new Uint8Array(raw.buffer, raw.byteOffset + base + section.offset, section.length);\n },\n u32(name) {\n const section = at(name, 4);\n if (section.length % 4 !== 0) throw reject();\n return new Uint32Array(\n raw.buffer,\n raw.byteOffset + base + section.offset,\n section.length / 4,\n );\n },\n maybeU32(name) {\n const section = found.get(name);\n if (section === undefined) return new Uint32Array();\n if (section.width !== 4 || section.length % 4 !== 0) throw reject();\n return new Uint32Array(\n raw.buffer,\n raw.byteOffset + base + section.offset,\n section.length / 4,\n );\n },\n };\n}\n\n/**\n * Every distinct string, sorted, and the number each one is written as.\n *\n * Sorted rather than first-seen because two encodes of equal facts must produce\n * equal bytes: a segment is content-addressed by whoever caches it, and an\n * identical index that hashes differently defeats every transport that would\n * otherwise have skipped the upload.\n */\nexport function intern(values: Iterable<string>): {\n readonly strings: readonly string[];\n readonly id: (value: string) => number;\n /** `NONE` for `undefined`, so an absent field costs a column and not a branch. */\n readonly optionalId: (value: string | undefined) => number;\n} {\n const strings = [...new Set(values)].sort(codeUnitOrder);\n const ids = new Map(strings.map((value, index) => [value, index]));\n const id = (value: string): number => {\n const found = ids.get(value);\n if (found === undefined) throw new Error(`string not interned: ${JSON.stringify(value)}`);\n return found;\n };\n return { strings, id, optionalId: (value) => (value === undefined ? NONE : id(value)) };\n}\n\n/** The two columns a dictionary is written as: the bytes, and where each one ends. */\nexport function stringColumns(strings: readonly string[]): {\n readonly blob: Uint8Array;\n readonly off: Uint32Array;\n} {\n const encoded = strings.map((value) => utf8.encode.encode(value));\n return { blob: concat(encoded), off: offsetsOf(encoded.map((value) => value.length)) };\n}\n\n/** Read strings back out of a dictionary, rejecting any id the blob cannot hold. */\nexport function stringReader(\n blob: Uint8Array,\n off: Uint32Array,\n reject: () => Error,\n): { readonly text: (id: number) => string; readonly optional: (id: number) => string | undefined } {\n validateOffsets(off, blob.length, off.length - 1, reject);\n const text = (id: number): string => {\n const start = off[id];\n const end = off[id + 1];\n if (start === undefined || end === undefined) throw reject();\n return utf8.decode.decode(blob.subarray(start, end));\n };\n return { text, optional: (id) => (id === NONE ? undefined : text(id)) };\n}\n\n/** A running-total column: `result[n]` is where row `n` starts, `result.at(-1)` the end. */\nexport function offsetsOf(lengths: readonly number[]): Uint32Array {\n const result = new Uint32Array(lengths.length + 1);\n for (let index = 0; index < lengths.length; index += 1) {\n result[index + 1] = result[index]! + lengths[index]!;\n }\n return result;\n}\n\n/**\n * Check an offset column before any row is read through it.\n *\n * Checked here rather than per row because an offset column is the one thing in\n * a segment that can turn a corrupt file into a *plausible* value: a pair that\n * runs backwards reads a neighbouring row's data as this row's, and nothing\n * downstream would ever notice.\n */\nexport function validateOffsets(\n column: Uint32Array,\n end: number,\n rows: number,\n reject: () => Error,\n): void {\n if (column.length !== rows + 1 || column[0] !== 0 || column[column.length - 1] !== end) {\n throw reject();\n }\n for (let index = 1; index < column.length; index += 1) {\n if (column[index]! < column[index - 1]!) throw reject();\n }\n}\n\n/** Columns that describe the same rows must agree about how many there are. */\nexport function sameLength(\n length: number,\n columns: readonly { readonly length: number }[],\n reject: () => Error,\n): void {\n if (columns.some((column) => column.length !== length)) throw reject();\n}\n\n/** The indices row `row` owns in the column an offset column points into. */\nexport function rangeOf(offsets: Uint32Array, row: number, reject: () => Error): number[] {\n const start = offsets[row];\n const end = offsets[row + 1];\n if (start === undefined || end === undefined) throw reject();\n return Array.from({ length: end - start }, (_, index) => start + index);\n}\n\n/** A stored boolean, refusing the file for anything that is not one. */\nexport function flagOf(value: number | undefined, reject: () => Error): boolean {\n if (value !== 0 && value !== 1) throw reject();\n return value === 1;\n}\n\nfunction validSections(sections: readonly Section[], available: number): boolean {\n if (!Array.isArray(sections) || sections.length === 0) return false;\n if (new Set(sections.map((section) => section.name)).size !== sections.length) return false;\n const ordered = [...sections].sort((left, right) => left.offset - right.offset);\n let end = 0;\n for (const section of ordered) {\n if (\n typeof section.name !== 'string' ||\n !Number.isSafeInteger(section.offset) ||\n !Number.isSafeInteger(section.length) ||\n section.offset < end ||\n section.offset % ALIGNMENT !== 0 ||\n section.length < 0 ||\n section.length > available - section.offset ||\n (section.width !== 1 && section.width !== 4)\n ) return false;\n end = section.offset + section.length;\n }\n return ordered[0]?.offset === 0;\n}\n\nfunction aligned(value: number): number {\n return Math.ceil(value / ALIGNMENT) * ALIGNMENT;\n}\n\n/**\n * Order two strings by UTF-16 code unit, which is the order a segment is sorted in.\n *\n * Exported because a schema that sorts its own values before interning them has\n * to sort them the same way this does, and `localeCompare` is the trap: it is\n * locale-dependent, so an index written on one machine would interleave\n * differently on another and two encodes of equal facts would stop producing\n * equal bytes.\n */\nexport function codeUnitOrder(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n"]}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `@variance-authority/core/shard` — which shard runs which group, and how
3
+ * many shards a suite is worth.
4
+ *
5
+ * One rule for every kind of suite: the stories and routes `variance run
6
+ * --shard` collects, and the test files a runner's `--shard` runs under a
7
+ * seam. No I/O: the groups and their recorded times are handed in by whoever
8
+ * read them.
9
+ */
10
+ export { MATRIX_LIMIT, durationText, longestFirst, place, priced, shardCount, type Placeable, type Placement, type ShardCount, type ShardCountOptions, type ShardCountReason, } from './placement.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * `@variance-authority/core/shard` — which shard runs which group, and how
3
+ * many shards a suite is worth.
4
+ *
5
+ * One rule for every kind of suite: the stories and routes `variance run
6
+ * --shard` collects, and the test files a runner's `--shard` runs under a
7
+ * seam. No I/O: the groups and their recorded times are handed in by whoever
8
+ * read them.
9
+ */
10
+ export { MATRIX_LIMIT, durationText, longestFirst, place, priced, shardCount, } from './placement.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/shard/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,KAAK,EACL,MAAM,EACN,UAAU,GAMX,MAAM,gBAAgB,CAAC","sourcesContent":["/**\n * `@variance-authority/core/shard` — which shard runs which group, and how\n * many shards a suite is worth.\n *\n * One rule for every kind of suite: the stories and routes `variance run\n * --shard` collects, and the test files a runner's `--shard` runs under a\n * seam. No I/O: the groups and their recorded times are handed in by whoever\n * read them.\n */\n\nexport {\n MATRIX_LIMIT,\n durationText,\n longestFirst,\n place,\n priced,\n shardCount,\n type Placeable,\n type Placement,\n type ShardCount,\n type ShardCountOptions,\n type ShardCountReason,\n} from './placement.js';\n"]}