@variance-authority/core 0.15.0 → 0.18.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 (82) hide show
  1. package/CHANGELOG.md +428 -0
  2. package/README.md +25 -7
  3. package/dist/attribute/component-hash.d.ts +11 -3
  4. package/dist/attribute/component-hash.js +11 -3
  5. package/dist/attribute/component-hash.js.map +1 -1
  6. package/dist/attribute/movement.d.ts +1 -1
  7. package/dist/attribute/movement.js.map +1 -1
  8. package/dist/attribute/region.d.ts +47 -4
  9. package/dist/attribute/region.js +90 -17
  10. package/dist/attribute/region.js.map +1 -1
  11. package/dist/attribute/source.d.ts +1 -1
  12. package/dist/attribute/source.js +1 -1
  13. package/dist/attribute/source.js.map +1 -1
  14. package/dist/compare/band.d.ts +8 -2
  15. package/dist/compare/band.js +7 -2
  16. package/dist/compare/band.js.map +1 -1
  17. package/dist/compare/diff/attribution.js +6 -4
  18. package/dist/compare/diff/attribution.js.map +1 -1
  19. package/dist/compare/diff/compare-nodes.js +9 -1
  20. package/dist/compare/diff/compare-nodes.js.map +1 -1
  21. package/dist/compare/diff/match.d.ts +2 -1
  22. package/dist/compare/diff/match.js +52 -0
  23. package/dist/compare/diff/match.js.map +1 -1
  24. package/dist/compare/parting.js +6 -4
  25. package/dist/compare/parting.js.map +1 -1
  26. package/dist/format/document.d.ts +26 -3
  27. package/dist/format/document.js.map +1 -1
  28. package/dist/format/index.d.ts +7 -0
  29. package/dist/format/index.js +4 -0
  30. package/dist/format/index.js.map +1 -1
  31. package/dist/format/packed.d.ts +32 -0
  32. package/dist/format/packed.js +23 -0
  33. package/dist/format/packed.js.map +1 -0
  34. package/dist/format/shape.d.ts +31 -0
  35. package/dist/format/shape.js +269 -0
  36. package/dist/format/shape.js.map +1 -0
  37. package/dist/format/stabilize.d.ts +7 -1
  38. package/dist/format/stabilize.js +10 -3
  39. package/dist/format/stabilize.js.map +1 -1
  40. package/dist/format/steps.d.ts +44 -0
  41. package/dist/format/steps.js +67 -0
  42. package/dist/format/steps.js.map +1 -0
  43. package/dist/format/width.d.ts +35 -0
  44. package/dist/format/width.js +38 -0
  45. package/dist/format/width.js.map +1 -0
  46. package/dist/judge/docket.js +1 -0
  47. package/dist/judge/docket.js.map +1 -1
  48. package/dist/judge/index.d.ts +1 -0
  49. package/dist/judge/index.js +1 -0
  50. package/dist/judge/index.js.map +1 -1
  51. package/dist/judge/restamp.d.ts +20 -0
  52. package/dist/judge/restamp.js +20 -0
  53. package/dist/judge/restamp.js.map +1 -0
  54. package/dist/relate/before.js +2 -2
  55. package/dist/relate/before.js.map +1 -1
  56. package/dist/relate/carried.d.ts +1 -1
  57. package/dist/relate/carried.js +5 -5
  58. package/dist/relate/carried.js.map +1 -1
  59. package/dist/relate/graph.d.ts +14 -12
  60. package/dist/relate/graph.js +12 -11
  61. package/dist/relate/graph.js.map +1 -1
  62. package/dist/relate/index.d.ts +1 -2
  63. package/dist/relate/index.js +1 -2
  64. package/dist/relate/index.js.map +1 -1
  65. package/dist/relate/reach.d.ts +5 -5
  66. package/dist/relate/reach.js.map +1 -1
  67. package/dist/relate/records.d.ts +5 -4
  68. package/dist/relate/records.js +3 -2
  69. package/dist/relate/records.js.map +1 -1
  70. package/dist/rules/normalize/fixture.d.ts +2 -0
  71. package/dist/rules/normalize/fixture.js +2 -1
  72. package/dist/rules/normalize/fixture.js.map +1 -1
  73. package/dist/shard/placement.d.ts +10 -8
  74. package/dist/shard/placement.js +7 -21
  75. package/dist/shard/placement.js.map +1 -1
  76. package/dist/share/index.d.ts +1 -1
  77. package/dist/share/index.js +1 -1
  78. package/dist/share/index.js.map +1 -1
  79. package/package.json +2 -2
  80. package/dist/relate/dominate.d.ts +0 -19
  81. package/dist/relate/dominate.js +0 -77
  82. package/dist/relate/dominate.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"match.js","sourceRoot":"","sources":["../../../src/compare/diff/match.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SemanticNode } from '../../format/snapshot.js';\n\n/**\n * Node correspondence.\n *\n * Paths are positional, so inserting one sibling renumbers everything after it.\n * Diffing by path alone would report a single insertion as \"every following node\n * changed\" — the classic snapshot-test failure that trains people to stop\n * reading diffs. Matching therefore happens by identity first, content second,\n * and position last.\n *\n * The middle pass is the one that took three attempts to find. Identity keys\n * collapse whenever a node has no alias, no accessible name and no provenance —\n * five bare `<li>`s bucket together as `tag:li` — and pairing inside that bucket\n * by position turns a rotation into \"every item's text changed\". The corpus case\n * `reorder/list` was written to catch exactly that and could not: its assertions\n * were the verdict and the root count, and both are correct while the report is\n * useless. Scoring the *band* is what exposed it — five `text-changed` deltas\n * band as `content` where a rotation bands as `geometry`.\n */\n\nexport interface Matching {\n /** Baseline node → candidate node. */\n readonly pairs: ReadonlyMap<SemanticNode, SemanticNode>;\n readonly added: readonly SemanticNode[];\n readonly removed: readonly SemanticNode[];\n /**\n * Pairs whose position among their siblings changed *relative to each other*.\n *\n * Not \"whose index differs\". Prepending one item shifts every index after it\n * without reordering anything, and reporting three moves for one insertion is\n * the same fatigue that positional diffing produces. The moved set is the\n * complement of the longest run that stayed in order, which is the smallest\n * set of nodes whose movement explains the new sequence.\n */\n readonly moved: ReadonlySet<SemanticNode>;\n}\n\n/**\n * Identity key, most specific first.\n *\n * Ordering matters: a structural alias is the strongest signal available because\n * it survives reordering and is stable across renders (that being the point of\n * ADR-0003's aliasing). Role plus accessible name is next, since it is what a\n * user of assistive technology would call the node. Owner chain plus tag comes\n * third — two `<span>`s rendered by the same component are interchangeable in a\n * way two spans from different components are not. Bare tag is the last resort.\n */\nfunction matchKey(node: SemanticNode): string {\n if (node.alias !== undefined) return `alias:${node.alias}`;\n if (node.role !== undefined && node.name !== undefined) {\n return `aria:${node.role}|${node.name}`;\n }\n if (node.provenance && node.provenance.owners.length > 0) {\n const chain = node.provenance.owners.map((owner) => owner.name).join('>');\n return `own:${chain}:${node.tag}`;\n }\n return `tag:${node.tag}`;\n}\n\nconst SIGNATURES = new WeakMap<SemanticNode, string>();\n\n/**\n * What this subtree *says*, ignoring where it sits.\n *\n * Deliberately narrow: tag, the three ARIA fields, text, and the same of every\n * descendant. Style is excluded, because a node that was restyled and a node\n * that was replaced must not be confused — pairing by appearance would let a\n * recolour look like a substitution. Position is excluded because that is the\n * whole point.\n *\n * Cached per node: the recursion is O(subtree) and a parent computing it for\n * every child at every level would be quadratic in depth.\n */\nfunction contentSignature(node: SemanticNode): string {\n const cached = SIGNATURES.get(node);\n if (cached !== undefined) return cached;\n\n const own = [node.tag, node.role ?? '', node.name ?? '', node.description ?? '', node.text ?? ''];\n const signature = `${own.join('\u0001')}(${node.children.map(contentSignature).join('\u0002')})`;\n\n SIGNATURES.set(node, signature);\n return signature;\n}\n\n/**\n * Content pass key. Scoped by identity so the refinement never crosses a\n * boundary the identity key drew — two components rendering the same string are\n * not the same node however identical their subtrees.\n */\nfunction contentKey(node: SemanticNode): string {\n return `${matchKey(node)}\\u0000${contentSignature(node)}`;\n}\n\nexport function matchTrees(baseline: SemanticNode, candidate: SemanticNode): Matching {\n const pairs = new Map<SemanticNode, SemanticNode>();\n const added: SemanticNode[] = [];\n const removed: SemanticNode[] = [];\n const moved = new Set<SemanticNode>();\n\n pairs.set(baseline, candidate);\n matchChildren(baseline, candidate);\n\n return { pairs, added, removed, moved };\n\n function matchChildren(before: SemanticNode, after: SemanticNode): void {\n const byContent = index(before.children, contentKey);\n const byIdentity = index(before.children, matchKey);\n const taken = new Set<number>();\n\n /** Baseline index each candidate paired with, indexed by candidate position. */\n const partner: (number | undefined)[] = Array.from(\n { length: after.children.length },\n () => undefined,\n );\n\n // Pass 1 — identical content. A node that says the same thing is the same\n // node, wherever it moved to.\n after.children.forEach((child, afterIndex) => {\n const claimed = claim(byContent.get(contentKey(child)), taken);\n if (claimed !== undefined) partner[afterIndex] = claimed;\n });\n\n // Pass 2 — whatever is left, by identity and then by order of appearance.\n // This is what turns `[a, b, c]` → `[a, x, c]` into one changed string\n // rather than one removal and one addition: `b` and `x` are the only two\n // nodes their bucket has left, so they are each other's counterpart.\n after.children.forEach((child, afterIndex) => {\n if (partner[afterIndex] !== undefined) return;\n const claimed = claim(byIdentity.get(matchKey(child)), taken);\n if (claimed !== undefined) partner[afterIndex] = claimed;\n });\n\n const sequence: number[] = [];\n after.children.forEach((child, afterIndex) => {\n const beforeIndex = partner[afterIndex];\n if (beforeIndex === undefined) {\n // Unmatched candidates are recorded whole. Their descendants are not\n // walked: a new subtree is one change to review, not one per node in it.\n added.push(child);\n return;\n }\n\n sequence.push(beforeIndex);\n const counterpart = before.children[beforeIndex]!;\n pairs.set(counterpart, child);\n matchChildren(counterpart, child);\n });\n\n for (const beforeIndex of outOfOrder(sequence)) {\n moved.add(before.children[beforeIndex]!);\n }\n\n before.children.forEach((child, index) => {\n if (!taken.has(index)) removed.push(child);\n });\n }\n}\n\nfunction index(\n children: readonly SemanticNode[],\n key: (node: SemanticNode) => string,\n): Map<string, number[]> {\n const buckets = new Map<string, number[]>();\n\n children.forEach((child, position) => {\n const bucket = buckets.get(key(child));\n if (bucket) bucket.push(position);\n else buckets.set(key(child), [position]);\n });\n\n return buckets;\n}\n\n/** Lowest unclaimed index in a bucket, marking it claimed. */\nfunction claim(bucket: number[] | undefined, taken: Set<number>): number | undefined {\n const free = bucket?.find((position) => !taken.has(position));\n if (free !== undefined) taken.add(free);\n return free;\n}\n\n/**\n * Indices that must have moved for `sequence` to be in this order.\n *\n * The complement of a longest increasing subsequence: everything in that\n * subsequence kept its relative order and needs no explanation, and what is left\n * is the smallest set of movements that produces the observed sequence. A\n * prepend leaves the sequence increasing and therefore reports nothing moved,\n * which is the answer `prepend/list` asks for — one added node, and not three\n * displaced siblings alongside it.\n */\nfunction outOfOrder(sequence: readonly number[]): readonly number[] {\n if (sequence.length < 2) return [];\n\n // Patience sorting. `tails[k]` is the smallest tail of an increasing run of\n // length k+1; `previous` reconstructs which elements formed the longest one.\n const tails: number[] = [];\n const tailIndex: number[] = [];\n const previous: (number | undefined)[] = Array.from(\n { length: sequence.length },\n () => undefined,\n );\n\n for (let position = 0; position < sequence.length; position += 1) {\n const value = sequence[position]!;\n\n let low = 0;\n let high = tails.length;\n while (low < high) {\n const mid = (low + high) >> 1;\n if (tails[mid]! < value) low = mid + 1;\n else high = mid;\n }\n\n tails[low] = value;\n tailIndex[low] = position;\n previous[position] = low > 0 ? tailIndex[low - 1] : undefined;\n }\n\n const kept = new Set<number>();\n let cursor: number | undefined = tailIndex[tails.length - 1];\n while (cursor !== undefined) {\n kept.add(cursor);\n cursor = previous[cursor];\n }\n\n return sequence.filter((_, position) => !kept.has(position));\n}\n"]}
1
+ {"version":3,"file":"match.js","sourceRoot":"","sources":["../../../src/compare/diff/match.ts"],"names":[],"mappings":"AAuCA;;;;;;;;;GASG;AACH,SAAS,QAAQ,CAAC,IAAkB;IAClC,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,IAAI,CAAC,KAAK,EAAE,CAAC;IAC3D,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QACvD,OAAO,QAAQ,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;IAC1C,CAAC;IACD,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC1E,OAAO,OAAO,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,CAAC;IACpC,CAAC;IACD,OAAO,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,GAAG,IAAI,OAAO,EAAwB,CAAC;AAEvD;;;;;;;;;;;GAWG;AACH,SAAS,gBAAgB,CAAC,IAAkB;IAC1C,MAAM,MAAM,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAExC,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,IAAI,CAAC,WAAW,IAAI,EAAE,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAClG,MAAM,SAAS,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAEvF,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;IAChC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,SAAS,UAAU,CAAC,IAAkB;IACpC,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,SAAS,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAsB,EAAE,SAAuB;IACxE,MAAM,KAAK,GAAG,IAAI,GAAG,EAA8B,CAAC;IACpD,MAAM,KAAK,GAAmB,EAAE,CAAC;IACjC,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAgB,CAAC;IAEtC,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAC/B,aAAa,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAEnC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAExC,SAAS,aAAa,CAAC,MAAoB,EAAE,KAAmB;QAC9D,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACrD,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACpD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;QAEhC,gFAAgF;QAChF,MAAM,OAAO,GAA2B,KAAK,CAAC,IAAI,CAChD,EAAE,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,EACjC,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;QAEF,0EAA0E;QAC1E,8BAA8B;QAC9B,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE;YAC3C,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;YAC/D,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC;QAC3D,CAAC,CAAC,CAAC;QAEH,0EAA0E;QAC1E,uEAAuE;QACvE,yEAAyE;QACzE,qEAAqE;QACrE,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE;YAC3C,IAAI,OAAO,CAAC,UAAU,CAAC,KAAK,SAAS;gBAAE,OAAO;YAC9C,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;YAC9D,IAAI,OAAO,KAAK,SAAS;gBAAE,OAAO,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC;QAC3D,CAAC,CAAC,CAAC;QAEH,yEAAyE;QACzE,yEAAyE;QACzE,uEAAuE;QACvE,2EAA2E;QAC3E,yEAAyE;QACzE,kEAAkE;QAClE,4EAA4E;QAC5E,0EAA0E;QAC1E,sEAAsE;QACtE,4BAA4B;QAC5B,EAAE;QACF,2EAA2E;QAC3E,0EAA0E;QAC1E,qEAAqE;QACrE,4EAA4E;QAC5E,wEAAwE;QACxE,wEAAwE;QACxE,yBAAyB;QACzB,YAAY,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAE9D,MAAM,QAAQ,GAAa,EAAE,CAAC;QAC9B,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE;YAC3C,MAAM,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;YACxC,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;gBAC9B,qEAAqE;gBACrE,yEAAyE;gBACzE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBAClB,OAAO;YACT,CAAC;YAED,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAC3B,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAE,CAAC;YAClD,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;YAC9B,aAAa,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;QAEH,KAAK,MAAM,WAAW,IAAI,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC/C,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAE,CAAC,CAAC;QAC3C,CAAC;QAED,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YACvC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,oFAAoF;AACpF,SAAS,YAAY,CACnB,MAA+B,EAC/B,KAA8B,EAC9B,KAAkB,EAClB,OAA+B;IAE/B,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClE,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,SAAS,CAAC,CAAC;IAE/E,KAAK,MAAM,CAAC,MAAM,EAAE,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QACpD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACxC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,WAAW,EAAE,MAAM,KAAK,CAAC;YAAE,SAAS;QAC3D,IAAI,QAAQ,CAAC,MAAM,CAAC,WAAY,CAAE,CAAC,IAAI,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAE,CAAE,CAAC;YAAE,SAAS;QACnF,KAAK,CAAC,GAAG,CAAC,WAAY,CAAC,CAAC;QACxB,OAAO,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC,GAAG,WAAW,CAAC;IACzC,CAAC;AACH,CAAC;AAED,4EAA4E;AAC5E,SAAS,QAAQ,CAAC,IAAkB;IAClC,OAAO,IAAI,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC;AAC1F,CAAC;AAED,gFAAgF;AAChF,SAAS,QAAQ,CACf,QAAiC,EACjC,IAAmC;IAEnC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE5C,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;QACnC,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,EAAE,SAAS,CAAC;QAC3C,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO;QACpD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACtC,IAAI,SAAS;YAAE,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;;YACnC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,KAAK,CACZ,QAAiC,EACjC,GAAmC;IAEnC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE5C,QAAQ,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;QACnC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QACvC,IAAI,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;;YAC7B,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,8DAA8D;AAC9D,SAAS,KAAK,CAAC,MAA4B,EAAE,KAAkB;IAC7D,MAAM,IAAI,GAAG,MAAM,EAAE,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC9D,IAAI,IAAI,KAAK,SAAS;QAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACxC,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,UAAU,CAAC,QAA2B;IAC7C,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,4EAA4E;IAC5E,6EAA6E;IAC7E,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,MAAM,QAAQ,GAA2B,KAAK,CAAC,IAAI,CACjD,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,EAC3B,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;IAEF,KAAK,IAAI,QAAQ,GAAG,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAC,MAAM,EAAE,QAAQ,IAAI,CAAC,EAAE,CAAC;QACjE,MAAM,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAE,CAAC;QAElC,IAAI,GAAG,GAAG,CAAC,CAAC;QACZ,IAAI,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC;QACxB,OAAO,GAAG,GAAG,IAAI,EAAE,CAAC;YAClB,MAAM,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;YAC9B,IAAI,KAAK,CAAC,GAAG,CAAE,GAAG,KAAK;gBAAE,GAAG,GAAG,GAAG,GAAG,CAAC,CAAC;;gBAClC,IAAI,GAAG,GAAG,CAAC;QAClB,CAAC;QAED,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACnB,SAAS,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;QAC1B,QAAQ,CAAC,QAAQ,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAChE,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,IAAI,MAAM,GAAuB,SAAS,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC7D,OAAO,MAAM,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjB,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IAED,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC/D,CAAC","sourcesContent":["import type { SemanticNode } from '../../format/snapshot.js';\n\n/**\n * Node correspondence.\n *\n * Paths are positional, so inserting one sibling renumbers everything after it.\n * Diffing by path alone would report a single insertion as \"every following node\n * changed\" — the classic snapshot-test failure that trains people to stop\n * reading diffs. Matching therefore happens by identity first, content second,\n * and position last; a node no key relates is paired only by the author that\n * recorded writing it.\n *\n * The middle pass is the one that took three attempts to find. Identity keys\n * collapse whenever a node has no alias, no accessible name and no provenance —\n * five bare `<li>`s bucket together as `tag:li` — and pairing inside that bucket\n * by position turns a rotation into \"every item's text changed\". The corpus case\n * `reorder/list` was written to catch exactly that and could not: its assertions\n * were the verdict and the root count, and both are correct while the report is\n * useless. Scoring the *band* is what exposed it — five `text-changed` deltas\n * band as `content` where a rotation bands as `geometry`.\n */\n\nexport interface Matching {\n /** Baseline node → candidate node. */\n readonly pairs: ReadonlyMap<SemanticNode, SemanticNode>;\n readonly added: readonly SemanticNode[];\n readonly removed: readonly SemanticNode[];\n /**\n * Pairs whose position among their siblings changed *relative to each other*.\n *\n * Not \"whose index differs\". Prepending one item shifts every index after it\n * without reordering anything, and reporting three moves for one insertion is\n * the same fatigue that positional diffing produces. The moved set is the\n * complement of the longest run that stayed in order, which is the smallest\n * set of nodes whose movement explains the new sequence.\n */\n readonly moved: ReadonlySet<SemanticNode>;\n}\n\n/**\n * Identity key, most specific first.\n *\n * Ordering matters: a structural alias is the strongest signal available because\n * it survives reordering and is stable across renders (that being the point of\n * ADR-0003's aliasing). Role plus accessible name is next, since it is what a\n * user of assistive technology would call the node. Owner chain plus tag comes\n * third — two `<span>`s rendered by the same component are interchangeable in a\n * way two spans from different components are not. Bare tag is the last resort.\n */\nfunction matchKey(node: SemanticNode): string {\n if (node.alias !== undefined) return `alias:${node.alias}`;\n if (node.role !== undefined && node.name !== undefined) {\n return `aria:${node.role}|${node.name}`;\n }\n if (node.provenance && node.provenance.owners.length > 0) {\n const chain = node.provenance.owners.map((owner) => owner.name).join('>');\n return `own:${chain}:${node.tag}`;\n }\n return `tag:${node.tag}`;\n}\n\nconst SIGNATURES = new WeakMap<SemanticNode, string>();\n\n/**\n * What this subtree *says*, ignoring where it sits.\n *\n * Deliberately narrow: tag, the three ARIA fields, text, and the same of every\n * descendant. Style is excluded, because a node that was restyled and a node\n * that was replaced must not be confused — pairing by appearance would let a\n * recolour look like a substitution. Position is excluded because that is the\n * whole point.\n *\n * Cached per node: the recursion is O(subtree) and a parent computing it for\n * every child at every level would be quadratic in depth.\n */\nfunction contentSignature(node: SemanticNode): string {\n const cached = SIGNATURES.get(node);\n if (cached !== undefined) return cached;\n\n const own = [node.tag, node.role ?? '', node.name ?? '', node.description ?? '', node.text ?? ''];\n const signature = `${own.join('\u0001')}(${node.children.map(contentSignature).join('\u0002')})`;\n\n SIGNATURES.set(node, signature);\n return signature;\n}\n\n/**\n * Content pass key. Scoped by identity so the refinement never crosses a\n * boundary the identity key drew — two components rendering the same string are\n * not the same node however identical their subtrees.\n */\nfunction contentKey(node: SemanticNode): string {\n return `${matchKey(node)}\\u0000${contentSignature(node)}`;\n}\n\nexport function matchTrees(baseline: SemanticNode, candidate: SemanticNode): Matching {\n const pairs = new Map<SemanticNode, SemanticNode>();\n const added: SemanticNode[] = [];\n const removed: SemanticNode[] = [];\n const moved = new Set<SemanticNode>();\n\n pairs.set(baseline, candidate);\n matchChildren(baseline, candidate);\n\n return { pairs, added, removed, moved };\n\n function matchChildren(before: SemanticNode, after: SemanticNode): void {\n const byContent = index(before.children, contentKey);\n const byIdentity = index(before.children, matchKey);\n const taken = new Set<number>();\n\n /** Baseline index each candidate paired with, indexed by candidate position. */\n const partner: (number | undefined)[] = Array.from(\n { length: after.children.length },\n () => undefined,\n );\n\n // Pass 1 — identical content. A node that says the same thing is the same\n // node, wherever it moved to.\n after.children.forEach((child, afterIndex) => {\n const claimed = claim(byContent.get(contentKey(child)), taken);\n if (claimed !== undefined) partner[afterIndex] = claimed;\n });\n\n // Pass 2 — whatever is left, by identity and then by order of appearance.\n // This is what turns `[a, b, c]` → `[a, x, c]` into one changed string\n // rather than one removal and one addition: `b` and `x` are the only two\n // nodes their bucket has left, so they are each other's counterpart.\n after.children.forEach((child, afterIndex) => {\n if (partner[afterIndex] !== undefined) return;\n const claimed = claim(byIdentity.get(matchKey(child)), taken);\n if (claimed !== undefined) partner[afterIndex] = claimed;\n });\n\n // Pass 3 — a replacement its author recorded. A component that swaps its\n // own output, `Toggle` rendering a `<div>` where it rendered a checkbox,\n // leaves two nodes no key relates: they share no content, and one is a\n // named role where the other is a bare tag. What relates them is who wrote\n // them. Under one matched parent, one node left and one arrived, and the\n // same component created both, so the second is read as what that\n // component rendered instead of the first. The record cannot tell that from\n // an element dropped and an unrelated one drawn elsewhere in the row, and\n // the pair's position is not asked: the deltas then say what the slot\n // became and that it moved.\n //\n // Exactly one and one, or nothing. Two that left and two that arrived from\n // one author could pair either way, and choosing by position is the guess\n // pass 2 already declines across keys. And never two nodes that each\n // declare who they are: `createdBy` names a component, and every `<Button>`\n // is created by `Button`, so a Save button and an Export link under one\n // toolbar share an author while their names, which pass 2 has read, say\n // they are two elements.\n replacements(before.children, after.children, taken, partner);\n\n const sequence: number[] = [];\n after.children.forEach((child, afterIndex) => {\n const beforeIndex = partner[afterIndex];\n if (beforeIndex === undefined) {\n // Unmatched candidates are recorded whole. Their descendants are not\n // walked: a new subtree is one change to review, not one per node in it.\n added.push(child);\n return;\n }\n\n sequence.push(beforeIndex);\n const counterpart = before.children[beforeIndex]!;\n pairs.set(counterpart, child);\n matchChildren(counterpart, child);\n });\n\n for (const beforeIndex of outOfOrder(sequence)) {\n moved.add(before.children[beforeIndex]!);\n }\n\n before.children.forEach((child, index) => {\n if (!taken.has(index)) removed.push(child);\n });\n }\n}\n\n/** Pairs, in place, the one unclaimed node on each side that one author created. */\nfunction replacements(\n before: readonly SemanticNode[],\n after: readonly SemanticNode[],\n taken: Set<number>,\n partner: (number | undefined)[],\n): void {\n const left = byAuthor(before, (position) => !taken.has(position));\n const arrived = byAuthor(after, (position) => partner[position] === undefined);\n\n for (const [author, [beforeIndex, ...more]] of left) {\n const counterpart = arrived.get(author);\n if (more.length > 0 || counterpart?.length !== 1) continue;\n if (declared(before[beforeIndex!]!) && declared(after[counterpart[0]!]!)) continue;\n taken.add(beforeIndex!);\n partner[counterpart[0]!] = beforeIndex;\n }\n}\n\n/** Whether a node says who it is, by the first two of `matchKey`'s keys. */\nfunction declared(node: SemanticNode): boolean {\n return node.alias !== undefined || (node.role !== undefined && node.name !== undefined);\n}\n\n/** Positions of the nodes `open` admits, by the component that created each. */\nfunction byAuthor(\n children: readonly SemanticNode[],\n open: (position: number) => boolean,\n): Map<string, number[]> {\n const authors = new Map<string, number[]>();\n\n children.forEach((child, position) => {\n const author = child.provenance?.createdBy;\n if (author === undefined || !open(position)) return;\n const positions = authors.get(author);\n if (positions) positions.push(position);\n else authors.set(author, [position]);\n });\n\n return authors;\n}\n\nfunction index(\n children: readonly SemanticNode[],\n key: (node: SemanticNode) => string,\n): Map<string, number[]> {\n const buckets = new Map<string, number[]>();\n\n children.forEach((child, position) => {\n const bucket = buckets.get(key(child));\n if (bucket) bucket.push(position);\n else buckets.set(key(child), [position]);\n });\n\n return buckets;\n}\n\n/** Lowest unclaimed index in a bucket, marking it claimed. */\nfunction claim(bucket: number[] | undefined, taken: Set<number>): number | undefined {\n const free = bucket?.find((position) => !taken.has(position));\n if (free !== undefined) taken.add(free);\n return free;\n}\n\n/**\n * Indices that must have moved for `sequence` to be in this order.\n *\n * The complement of a longest increasing subsequence: everything in that\n * subsequence kept its relative order and needs no explanation, and what is left\n * is the smallest set of movements that produces the observed sequence. A\n * prepend leaves the sequence increasing and therefore reports nothing moved,\n * which is the answer `prepend/list` asks for — one added node, and not three\n * displaced siblings alongside it.\n */\nfunction outOfOrder(sequence: readonly number[]): readonly number[] {\n if (sequence.length < 2) return [];\n\n // Patience sorting. `tails[k]` is the smallest tail of an increasing run of\n // length k+1; `previous` reconstructs which elements formed the longest one.\n const tails: number[] = [];\n const tailIndex: number[] = [];\n const previous: (number | undefined)[] = Array.from(\n { length: sequence.length },\n () => undefined,\n );\n\n for (let position = 0; position < sequence.length; position += 1) {\n const value = sequence[position]!;\n\n let low = 0;\n let high = tails.length;\n while (low < high) {\n const mid = (low + high) >> 1;\n if (tails[mid]! < value) low = mid + 1;\n else high = mid;\n }\n\n tails[low] = value;\n tailIndex[low] = position;\n previous[position] = low > 0 ? tailIndex[low - 1] : undefined;\n }\n\n const kept = new Set<number>();\n let cursor: number | undefined = tailIndex[tails.length - 1];\n while (cursor !== undefined) {\n kept.add(cursor);\n cursor = previous[cursor];\n }\n\n return sequence.filter((_, position) => !kept.has(position));\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { boundaries as componentBoundaries, UNATTRIBUTED } from '../attribute/boundary.js';
2
- import { BANDS, bandOf } from './band.js';
2
+ import { BANDS } from './band.js';
3
3
  import { compareTrees, matchTrees } from './diff/index.js';
4
4
  import { cascadeInputs, declaredIn } from './cascade.js';
5
5
  import { compareHoldings } from './holding-diff.js';
@@ -127,8 +127,10 @@ function boundaryNodes(root) {
127
127
  *
128
128
  * The fallback is not a nicety, it is the case the whole module exists for. A
129
129
  * component that took the other branch returns a `<span>` where it returned a
130
- * `<p>`, and no host-level matcher pairs those: the tag is the thing that
131
- * changed, and every identity key ends in it. Reporting the boundary as present
130
+ * `<p>`, and the host-level matcher pairs those only when the snapshot recorded
131
+ * that component as the author of both and it replaced exactly one element under
132
+ * one parent (`match.ts`). Otherwise the tag is the thing that changed, and every
133
+ * identity key that names neither an alias nor a role ends in it. Reporting the boundary as present
132
134
  * on one side only would throw away the props and the hook cells at exactly the
133
135
  * moment they are the answer — `Summary` did not appear, it *chose differently*,
134
136
  * and the evidence for that is sitting in a holding nobody joined.
@@ -205,7 +207,7 @@ function attribute(drafts, deltas) {
205
207
  }
206
208
  /** Apply the precedence in {@link PartingRung} and freeze the row. */
207
209
  function settle(draft, place) {
208
- const bands = BANDS.filter((band) => draft.owned.some((delta) => bandOf(delta.kind) === band));
210
+ const bands = BANDS.filter((band) => draft.owned.some((delta) => delta.band === band));
209
211
  const moved = [
210
212
  ...new Set(draft.owned.map((delta) => delta.property).filter((name) => name !== undefined)),
211
213
  ].sort();
@@ -1 +1 @@
1
- {"version":3,"file":"parting.js","sourceRoot":"","sources":["../../src/compare/parting.ts"],"names":[],"mappings":"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","sourcesContent":["import { boundaries as componentBoundaries, UNATTRIBUTED } from '../attribute/boundary.js';\nimport type { Digest } from '../format/hash.js';\nimport type { NodePath, SemanticNode, SemanticSnapshot } from '../format/snapshot.js';\nimport { BANDS, bandOf, type Band } from './band.js';\nimport { compareTrees, matchTrees, type Delta } from './diff/index.js';\nimport { cascadeInputs, declaredIn } from './cascade.js';\nimport { compareHoldings } from './holding-diff.js';\nimport { sameTree, sliceOf, type PartingSlice } from './slice.js';\n\n/**\n * Where two readings of one page parted.\n *\n * A diff says a `<div>` rendered a `<p>` on one side and a `<span>` on the\n * other. That is a symptom, and acting on it means reading the component,\n * guessing which branch ran, and guessing why. This says: `Cart`'s `useState` at\n * call 2 held different values, so `Summary` was handed a different `total`, so\n * the tag moved. Same evidence, three rungs up.\n *\n * The rung it can reach is decided by how much of the boundary was readable, and\n * the difference between the top two rungs is the whole point:\n *\n * - **collateral** — a prop or a context this boundary *received* moved, so the\n * difference arrived from outside and something above it is the cause.\n * - **origin** — every input this boundary received held, and its own retained\n * state moved. Nothing above it explains this; it is the answer.\n *\n * The mechanism is one rule applied at every boundary: *a component whose\n * inputs agreed and whose output moved decided differently.* Walking the tree\n * for the shallowest boundary where that holds is what turns a page of deltas\n * into one sentence — and the same rule, run over two readings of one page\n * rather than two arms of an experiment, is what separates a flake from a\n * change. A flake is the case where nothing readable moved at all\n * ({@link PartingRung} `undetermined`), and it is only nameable because\n * `unread` is a rung of its own: an accusation of nondeterminism may only be\n * made about inputs somebody actually read (ADR-0002). It is also only nameable\n * when the two readings were taken at one address — see {@link PartingPlace}.\n *\n * Not `attribute/composition.ts`'s `Divergence`, which is the same suspicion\n * arrived at from the other side: one component rendering two ways from one\n * props digest *within* a run, found by counting sites. That one can say a\n * component's inputs do not determine its output; this one can say which input.\n *\n * Nothing here reaches a hash. {@link Holding} is evidence carried beside the\n * snapshot, exactly as `styleProvenance` is, and for the reason `wiring.ts`\n * gives: a hook's value is the thing that legitimately differs between two\n * readings of one page, so a band carrying it would be a flake generator wearing\n * a band's name. This module is the reader that value was kept for.\n */\nexport interface Parting {\n /**\n * What kind of parting this is, decided before which input moved.\n *\n * The triage line. `refactor` and `settled` are pages nobody needs to open,\n * `flake` is a page whose baseline is the problem, and `variation` is the\n * only one where the rungs below are worth reading.\n */\n readonly slice: PartingSlice;\n\n /** `true` when both render hashes agree. Inputs may still have moved. */\n readonly identical: boolean;\n\n /** Every delta, unchanged, so a caller need not run the comparison twice. */\n readonly deltas: readonly Delta[];\n\n /**\n * Boundaries where something moved — an input, an output, or both — in\n * document order.\n *\n * Absent, never `[]`, when no node on either side started a component: a page\n * read with no framework adapter has not been found to agree, it has not been\n * asked (ADR-0002). Present with every boundary at rung `unread` is the next\n * reading up — the components were found and their inputs were not.\n */\n readonly boundaries?: readonly PartedBoundary[];\n\n /**\n * The boundaries the difference started at, shallowest first.\n *\n * A subset of {@link boundaries}: those whose output moved without an\n * incoming input to explain it. Usually one. More than one means the two\n * renders differ in more than one place, which is a real answer rather than a\n * failure to narrow.\n */\n readonly origins?: readonly PartedBoundary[];\n}\n\n/**\n * One component boundary, and what moved at it.\n *\n * A boundary is a component's root host node — `componentFiberOf`'s definition,\n * shared with the wiring band so the two can never disagree about where a\n * component starts. A component that vanished entirely has no boundary here: its\n * nodes leave `node-removed` deltas that attribute to the *parent* boundary,\n * which is the correct answer, because deciding not to render a child is\n * something the parent did.\n */\nexport interface PartedBoundary {\n /** `displayName`, or `(anonymous)` when provenance did not name it. */\n readonly component: string;\n\n /** Path in the candidate tree. */\n readonly path: NodePath;\n\n /** Depth of {@link path}, so callers can order without re-parsing it. */\n readonly depth: number;\n\n readonly rung: PartingRung;\n\n /**\n * Inputs that differ: props, then contexts, then hook calls in order, then\n * the inherited properties no declaration at this boundary accounts for.\n */\n readonly inputs: readonly MovedInput[];\n\n /**\n * Deltas at or under this boundary that no nested boundary owns.\n *\n * Zero is meaningful and is not a filtered-out row: a boundary with moved\n * inputs and no deltas of its own is the negative result an experiment\n * usually wants — this arm was assigned differently and rendered the same.\n */\n readonly deltas: number;\n\n /** Bands those deltas fall in, in band order. */\n readonly bands: readonly Band[];\n\n /**\n * The properties the owned deltas named, deduplicated and in code-unit order.\n *\n * The last joint of the chain the rungs above climb: a hook cell moved, a prop\n * carried it down, and *this* is what the prop turned into on the page —\n * `color`, `padding-top`, `width`. A reader chasing a visual regression is\n * looking for this list, and `deltas: 7` alone sends them back to the delta\n * array to assemble it.\n *\n * Absent when no owned delta named a property, which is a reading rather than\n * a gap: a removed node and a changed accessible name are whole-node facts and\n * have no property to name. `deltas` and `bands` still say what happened\n * there.\n */\n readonly moved?: readonly string[];\n}\n\n/**\n * How far up the difference could be traced at one boundary.\n *\n * Ordered by precedence, and the ordering is the reasoning. Any input that\n * arrived from outside settles the question — this boundary is downstream of\n * the cause, whatever else it may also be holding — so `handed` and `provided`\n * outrank the state rungs. `unread` outranks `undetermined` for the same reason\n * pointed the other way: silence is not agreement.\n */\nexport type PartingRung =\n /** A named prop differs. The parent decided this; look up. */\n | 'handed'\n /** A context value differs. A provider above decided this; look up. */\n | 'provided'\n /**\n * An inherited style value differs, and this boundary declares none of it.\n * An ancestor's cascade decided this; look up.\n */\n | 'inherited'\n /** A `useSyncExternalStore` snapshot differs — the store moved, outside React. */\n | 'external'\n /** An own hook cell differs. **This is the cause.** */\n | 'stateful'\n /** The output moved and something this boundary depends on could not be read. */\n | 'unread'\n /** Every input was read, every input agreed, and the output moved anyway. */\n | 'undetermined'\n /**\n * The same, between two readings taken in different places. Where a component\n * sits is decided by the boxes around it and is handed to it as nothing, so\n * two instances that agreed on every input and landed apart have contradicted\n * nothing. {@link PartingPlace} is how a caller says which it is asking.\n */\n | 'placed'\n /** The boundary exists on one side only. */\n | 'unpaired';\n\n/**\n * Whether two readings were taken at one address.\n *\n * `same` is a subject read twice — across two revisions, or across two moments\n * of one scenario. There, an output that moved with every input holding is the\n * accusation this system is careful about: the reading is not repeatable.\n *\n * `elsewhere` is two instances lifted out of two subjects at one commit, which\n * is `attribute/divergence.ts`' whole shape. The same evidence means something\n * different there: position, and everything a measured box decides downstream of\n * it, is a function of the context an instance was mounted in, not of the props\n * it received. Calling that nondeterminism blames a component for its page.\n */\nexport type PartingPlace = 'same' | 'elsewhere';\n\nexport interface MovedInput {\n readonly kind: 'prop' | 'context' | 'hook' | 'inherited';\n\n /**\n * Prop name, context display name, hook name (`useState`), or — for\n * `inherited` — the CSS property an ancestor decided.\n */\n readonly name: string;\n\n /** Hook call position — the index a reader gets counting down the component. */\n readonly index?: number;\n\n /**\n * Explicitly `| undefined`, on {@link Delta}'s rule: an input present on one\n * side only is the reading, not a field somebody forgot.\n */\n readonly from?: Digest | undefined;\n readonly to?: Digest | undefined;\n}\n\n/**\n * Two snapshots of the same page, read for where they parted.\n *\n * Takes snapshots rather than trees, and takes them without refusing a subject\n * mismatch, for `compareTrees`' stated reason: this produces an *explanation*,\n * not a verdict, and two arms of an experiment are two subjects on purpose.\n */\nexport function partingOf(\n baseline: SemanticSnapshot,\n candidate: SemanticSnapshot,\n place: PartingPlace = 'same',\n): Parting {\n const comparison = compareTrees(baseline, candidate);\n const matching = matchTrees(baseline.root, candidate.root);\n\n const partner = new Map<SemanticNode, SemanticNode>();\n for (const [left, right] of matching.pairs) partner.set(right, left);\n\n const tree = sameTree(baseline.root, candidate.root);\n const moved = comparison.deltas.length > 0;\n\n const found = boundariesOf(baseline.root, candidate.root, partner, {\n there: declaredIn(baseline),\n here: declaredIn(candidate),\n });\n if (found === undefined) {\n return {\n slice: sliceOf(tree, undefined, moved, place),\n identical: comparison.identical,\n deltas: comparison.deltas,\n };\n }\n\n attribute(found, comparison.deltas);\n\n const boundaries = found\n .filter((entry) => entry.inputs.length > 0 || entry.owned.length > 0)\n .map((draft) => settle(draft, place));\n\n const origins = boundaries.filter(\n (entry) =>\n entry.rung === 'stateful' ||\n entry.rung === 'external' ||\n entry.rung === 'undetermined' ||\n entry.rung === 'placed',\n );\n\n return {\n slice: sliceOf(tree, boundaries, moved, place),\n identical: comparison.identical,\n deltas: comparison.deltas,\n boundaries,\n ...(origins.length > 0 ? { origins: [...origins].sort((a, b) => a.depth - b.depth) } : {}),\n };\n}\n\n/** A boundary mid-construction, before its deltas are counted. */\ninterface Draft {\n readonly component: string;\n readonly path: NodePath;\n readonly basePath?: NodePath;\n readonly depth: number;\n readonly inputs: readonly MovedInput[];\n readonly unread: boolean;\n readonly paired: boolean;\n readonly owned: Delta[];\n}\n\n/**\n * Every boundary in the candidate tree, in document order, paired with its\n * baseline counterpart.\n *\n * Undefined when neither tree carried a holding at all, which is the ADR-0002\n * distinction this whole module rests on: nothing to read is not nothing to\n * report.\n */\nfunction boundariesOf(\n baselineRoot: SemanticNode,\n candidateRoot: SemanticNode,\n partner: ReadonlyMap<SemanticNode, SemanticNode>,\n declared: { readonly there: ReadonlySet<string>; readonly here: ReadonlySet<string> },\n): Draft[] | undefined {\n const mine = boundaryNodes(candidateRoot);\n const theirs = boundaryNodes(baselineRoot);\n if (mine.length === 0 && theirs.length === 0) return undefined;\n\n const paired = pairBoundaries(mine, theirs, partner);\n\n return mine.map((node) => {\n const other = paired.get(node);\n const moved = compareHoldings(other?.holding, node.holding);\n const inputs = [\n ...moved.inputs,\n ...cascadeInputs(other, node, declared.there, declared.here),\n ];\n return {\n component: node.provenance?.owners[0]?.name ?? '(anonymous)',\n path: node.path,\n ...(other === undefined ? {} : { basePath: other.path }),\n depth: depthOf(node.path),\n inputs,\n unread: moved.unread,\n paired: other !== undefined,\n owned: [],\n };\n });\n}\n\n/**\n * Every node where a component starts, in document order.\n *\n * Read from provenance and not from `holding`, which is the difference between\n * this module working on one collector and working on all of them. A holding is\n * what a *framework adapter* managed to read at a boundary — props values, hook\n * cells — and only `@variance-authority/unit-test` supplies one today. Keying the\n * boundary set on it made every rung unreachable from a browser run, including\n * the two that need no adapter at all: the ancestor cascade, which is read from\n * `styleProvenance`, and the tree comparison, which is read from the owner\n * chains. Those runs got `unread` — \"nothing can be said about why\" — while\n * holding the evidence to say it.\n *\n * {@link componentBoundaries} is the shared definition, so a boundary here is\n * the same node the component hashes and the wiring band call one. It reports a\n * node once per component that opens there; the nodes are what this needs, and\n * `boundariesOf` names each by its innermost owner exactly as before.\n *\n * A node with a holding and no provenance is still kept. That is an adapter that\n * read the fiber's values but not its owner chain, and dropping its evidence\n * because of a second failure would lose the one rung it can still reach.\n */\nfunction boundaryNodes(root: SemanticNode): readonly SemanticNode[] {\n const found = new Set<SemanticNode>();\n for (const boundary of componentBoundaries(root)) {\n // `(unattributed)` is the bucket for nodes whose owner chain broke, not a\n // component, and `causesBetween` refuses it for the same reason: a boundary\n // reported there would name a component nobody wrote and pool unrelated\n // parts of the page under one name.\n if (boundary.component === UNATTRIBUTED) continue;\n found.add(boundary.node);\n }\n\n const holders = (node: SemanticNode): void => {\n if (node.holding !== undefined) found.add(node);\n for (const child of node.children) holders(child);\n };\n holders(root);\n\n return [...found].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n}\n\n/**\n * Pair boundaries, preferring the tree matcher and falling back to the owner\n * chain.\n *\n * The fallback is not a nicety, it is the case the whole module exists for. A\n * component that took the other branch returns a `<span>` where it returned a\n * `<p>`, and no host-level matcher pairs those: the tag is the thing that\n * changed, and every identity key ends in it. Reporting the boundary as present\n * on one side only would throw away the props and the hook cells at exactly the\n * moment they are the answer — `Summary` did not appear, it *chose differently*,\n * and the evidence for that is sitting in a holding nobody joined.\n *\n * The chain — the boundary's owner names, outermost component included — is\n * stable under precisely that change, since neither the branch nor the tag\n * appears in it. Ties inside one chain are broken by document order, which is\n * positional and therefore shifts when a sibling is inserted; that is the same\n * cost `match.ts` pays for the same reason, and it is bounded here to\n * components sharing one chain that the matcher already failed to pair.\n */\nfunction pairBoundaries(\n mine: readonly SemanticNode[],\n theirs: readonly SemanticNode[],\n partner: ReadonlyMap<SemanticNode, SemanticNode>,\n): ReadonlyMap<SemanticNode, SemanticNode> {\n const paired = new Map<SemanticNode, SemanticNode>();\n const taken = new Set<SemanticNode>();\n\n for (const node of mine) {\n const other = partner.get(node);\n if (other?.holding === undefined || taken.has(other)) continue;\n paired.set(node, other);\n taken.add(other);\n }\n\n const spare = new Map<string, SemanticNode[]>();\n for (const node of theirs) {\n if (taken.has(node)) continue;\n const key = chainOf(node);\n const bucket = spare.get(key);\n if (bucket === undefined) spare.set(key, [node]);\n else bucket.push(node);\n }\n\n for (const node of mine) {\n if (paired.has(node)) continue;\n const bucket = spare.get(chainOf(node));\n const other = bucket?.shift();\n if (other !== undefined) paired.set(node, other);\n }\n\n return paired;\n}\n\nfunction chainOf(node: SemanticNode): string {\n return (node.provenance?.owners ?? []).map((owner) => owner.name).join('>');\n}\n\n/**\n * Give each delta to the deepest boundary that encloses it.\n *\n * By prefix rather than by scanning every boundary: a node's path *is* its\n * ancestry, so walking it up one segment at a time reaches the owning boundary\n * in as many steps as the node is deep. A `node-removed` delta carries a\n * baseline path, so it is resolved in baseline space — mixing the two spaces\n * would silently hand a removal to whichever candidate node now sits at that\n * address, which is the exact misattribution positional diffing is famous for.\n */\nfunction attribute(drafts: readonly Draft[], deltas: readonly Delta[]): void {\n const here = new Map<NodePath, Draft>();\n const there = new Map<NodePath, Draft>();\n for (const draft of drafts) {\n here.set(draft.path, draft);\n if (draft.basePath !== undefined) there.set(draft.basePath, draft);\n }\n\n for (const delta of deltas) {\n const index = delta.kind === 'node-removed' ? there : here;\n for (let path: NodePath | undefined = delta.path; path !== undefined; path = parentOf(path)) {\n const owner = index.get(path);\n if (owner !== undefined) {\n owner.owned.push(delta);\n break;\n }\n }\n }\n}\n\n/** Apply the precedence in {@link PartingRung} and freeze the row. */\nfunction settle(draft: Draft, place: PartingPlace): PartedBoundary {\n const bands = BANDS.filter((band) => draft.owned.some((delta) => bandOf(delta.kind) === band));\n\n const moved = [\n ...new Set(draft.owned.map((delta) => delta.property).filter((name) => name !== undefined)),\n ].sort();\n\n return {\n component: draft.component,\n path: draft.path,\n depth: draft.depth,\n rung: rungOf(draft, place),\n inputs: draft.inputs,\n deltas: draft.owned.length,\n bands,\n ...(moved.length > 0 ? { moved } : {}),\n };\n}\n\nfunction rungOf(draft: Draft, place: PartingPlace): PartingRung {\n if (!draft.paired) return 'unpaired';\n if (draft.inputs.some((input) => input.kind === 'prop')) return 'handed';\n if (draft.inputs.some((input) => input.kind === 'context')) return 'provided';\n if (draft.inputs.some((input) => input.kind === 'inherited')) return 'inherited';\n if (draft.inputs.some((input) => input.name === 'useSyncExternalStore')) return 'external';\n if (draft.inputs.length > 0) return 'stateful';\n if (draft.unread) return 'unread';\n return place === 'same' ? 'undetermined' : 'placed';\n}\n\n\nfunction depthOf(path: NodePath): number {\n return path === '' ? 0 : path.split('/').length;\n}\n\nfunction parentOf(path: NodePath): NodePath | undefined {\n if (path === '') return undefined;\n const cut = path.lastIndexOf('/');\n return cut === -1 ? '' : path.slice(0, cut);\n}\n"]}
1
+ {"version":3,"file":"parting.js","sourceRoot":"","sources":["../../src/compare/parting.ts"],"names":[],"mappings":"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","sourcesContent":["import { boundaries as componentBoundaries, UNATTRIBUTED } from '../attribute/boundary.js';\nimport type { Digest } from '../format/hash.js';\nimport type { NodePath, SemanticNode, SemanticSnapshot } from '../format/snapshot.js';\nimport { BANDS, type Band } from './band.js';\nimport { compareTrees, matchTrees, type Delta } from './diff/index.js';\nimport { cascadeInputs, declaredIn } from './cascade.js';\nimport { compareHoldings } from './holding-diff.js';\nimport { sameTree, sliceOf, type PartingSlice } from './slice.js';\n\n/**\n * Where two readings of one page parted.\n *\n * A diff says a `<div>` rendered a `<p>` on one side and a `<span>` on the\n * other. That is a symptom, and acting on it means reading the component,\n * guessing which branch ran, and guessing why. This says: `Cart`'s `useState` at\n * call 2 held different values, so `Summary` was handed a different `total`, so\n * the tag moved. Same evidence, three rungs up.\n *\n * The rung it can reach is decided by how much of the boundary was readable, and\n * the difference between the top two rungs is the whole point:\n *\n * - **collateral** — a prop or a context this boundary *received* moved, so the\n * difference arrived from outside and something above it is the cause.\n * - **origin** — every input this boundary received held, and its own retained\n * state moved. Nothing above it explains this; it is the answer.\n *\n * The mechanism is one rule applied at every boundary: *a component whose\n * inputs agreed and whose output moved decided differently.* Walking the tree\n * for the shallowest boundary where that holds is what turns a page of deltas\n * into one sentence — and the same rule, run over two readings of one page\n * rather than two arms of an experiment, is what separates a flake from a\n * change. A flake is the case where nothing readable moved at all\n * ({@link PartingRung} `undetermined`), and it is only nameable because\n * `unread` is a rung of its own: an accusation of nondeterminism may only be\n * made about inputs somebody actually read (ADR-0002). It is also only nameable\n * when the two readings were taken at one address — see {@link PartingPlace}.\n *\n * Not `attribute/composition.ts`'s `Divergence`, which is the same suspicion\n * arrived at from the other side: one component rendering two ways from one\n * props digest *within* a run, found by counting sites. That one can say a\n * component's inputs do not determine its output; this one can say which input.\n *\n * Nothing here reaches a hash. {@link Holding} is evidence carried beside the\n * snapshot, exactly as `styleProvenance` is, and for the reason `wiring.ts`\n * gives: a hook's value is the thing that legitimately differs between two\n * readings of one page, so a band carrying it would be a flake generator wearing\n * a band's name. This module is the reader that value was kept for.\n */\nexport interface Parting {\n /**\n * What kind of parting this is, decided before which input moved.\n *\n * The triage line. `refactor` and `settled` are pages nobody needs to open,\n * `flake` is a page whose baseline is the problem, and `variation` is the\n * only one where the rungs below are worth reading.\n */\n readonly slice: PartingSlice;\n\n /** `true` when both render hashes agree. Inputs may still have moved. */\n readonly identical: boolean;\n\n /** Every delta, unchanged, so a caller need not run the comparison twice. */\n readonly deltas: readonly Delta[];\n\n /**\n * Boundaries where something moved — an input, an output, or both — in\n * document order.\n *\n * Absent, never `[]`, when no node on either side started a component: a page\n * read with no framework adapter has not been found to agree, it has not been\n * asked (ADR-0002). Present with every boundary at rung `unread` is the next\n * reading up — the components were found and their inputs were not.\n */\n readonly boundaries?: readonly PartedBoundary[];\n\n /**\n * The boundaries the difference started at, shallowest first.\n *\n * A subset of {@link boundaries}: those whose output moved without an\n * incoming input to explain it. Usually one. More than one means the two\n * renders differ in more than one place, which is a real answer rather than a\n * failure to narrow.\n */\n readonly origins?: readonly PartedBoundary[];\n}\n\n/**\n * One component boundary, and what moved at it.\n *\n * A boundary is a component's root host node — `componentFiberOf`'s definition,\n * shared with the wiring band so the two can never disagree about where a\n * component starts. A component that vanished entirely has no boundary here: its\n * nodes leave `node-removed` deltas that attribute to the *parent* boundary,\n * which is the correct answer, because deciding not to render a child is\n * something the parent did.\n */\nexport interface PartedBoundary {\n /** `displayName`, or `(anonymous)` when provenance did not name it. */\n readonly component: string;\n\n /** Path in the candidate tree. */\n readonly path: NodePath;\n\n /** Depth of {@link path}, so callers can order without re-parsing it. */\n readonly depth: number;\n\n readonly rung: PartingRung;\n\n /**\n * Inputs that differ: props, then contexts, then hook calls in order, then\n * the inherited properties no declaration at this boundary accounts for.\n */\n readonly inputs: readonly MovedInput[];\n\n /**\n * Deltas at or under this boundary that no nested boundary owns.\n *\n * Zero is meaningful and is not a filtered-out row: a boundary with moved\n * inputs and no deltas of its own is the negative result an experiment\n * usually wants — this arm was assigned differently and rendered the same.\n */\n readonly deltas: number;\n\n /** Bands those deltas fall in, in band order. */\n readonly bands: readonly Band[];\n\n /**\n * The properties the owned deltas named, deduplicated and in code-unit order.\n *\n * The last joint of the chain the rungs above climb: a hook cell moved, a prop\n * carried it down, and *this* is what the prop turned into on the page —\n * `color`, `padding-top`, `width`. A reader chasing a visual regression is\n * looking for this list, and `deltas: 7` alone sends them back to the delta\n * array to assemble it.\n *\n * Absent when no owned delta named a property, which is a reading rather than\n * a gap: a removed node and a changed accessible name are whole-node facts and\n * have no property to name. `deltas` and `bands` still say what happened\n * there.\n */\n readonly moved?: readonly string[];\n}\n\n/**\n * How far up the difference could be traced at one boundary.\n *\n * Ordered by precedence, and the ordering is the reasoning. Any input that\n * arrived from outside settles the question — this boundary is downstream of\n * the cause, whatever else it may also be holding — so `handed` and `provided`\n * outrank the state rungs. `unread` outranks `undetermined` for the same reason\n * pointed the other way: silence is not agreement.\n */\nexport type PartingRung =\n /** A named prop differs. The parent decided this; look up. */\n | 'handed'\n /** A context value differs. A provider above decided this; look up. */\n | 'provided'\n /**\n * An inherited style value differs, and this boundary declares none of it.\n * An ancestor's cascade decided this; look up.\n */\n | 'inherited'\n /** A `useSyncExternalStore` snapshot differs — the store moved, outside React. */\n | 'external'\n /** An own hook cell differs. **This is the cause.** */\n | 'stateful'\n /** The output moved and something this boundary depends on could not be read. */\n | 'unread'\n /** Every input was read, every input agreed, and the output moved anyway. */\n | 'undetermined'\n /**\n * The same, between two readings taken in different places. Where a component\n * sits is decided by the boxes around it and is handed to it as nothing, so\n * two instances that agreed on every input and landed apart have contradicted\n * nothing. {@link PartingPlace} is how a caller says which it is asking.\n */\n | 'placed'\n /** The boundary exists on one side only. */\n | 'unpaired';\n\n/**\n * Whether two readings were taken at one address.\n *\n * `same` is a subject read twice — across two revisions, or across two moments\n * of one scenario. There, an output that moved with every input holding is the\n * accusation this system is careful about: the reading is not repeatable.\n *\n * `elsewhere` is two instances lifted out of two subjects at one commit, which\n * is `attribute/divergence.ts`' whole shape. The same evidence means something\n * different there: position, and everything a measured box decides downstream of\n * it, is a function of the context an instance was mounted in, not of the props\n * it received. Calling that nondeterminism blames a component for its page.\n */\nexport type PartingPlace = 'same' | 'elsewhere';\n\nexport interface MovedInput {\n readonly kind: 'prop' | 'context' | 'hook' | 'inherited';\n\n /**\n * Prop name, context display name, hook name (`useState`), or — for\n * `inherited` — the CSS property an ancestor decided.\n */\n readonly name: string;\n\n /** Hook call position — the index a reader gets counting down the component. */\n readonly index?: number;\n\n /**\n * Explicitly `| undefined`, on {@link Delta}'s rule: an input present on one\n * side only is the reading, not a field somebody forgot.\n */\n readonly from?: Digest | undefined;\n readonly to?: Digest | undefined;\n}\n\n/**\n * Two snapshots of the same page, read for where they parted.\n *\n * Takes snapshots rather than trees, and takes them without refusing a subject\n * mismatch, for `compareTrees`' stated reason: this produces an *explanation*,\n * not a verdict, and two arms of an experiment are two subjects on purpose.\n */\nexport function partingOf(\n baseline: SemanticSnapshot,\n candidate: SemanticSnapshot,\n place: PartingPlace = 'same',\n): Parting {\n const comparison = compareTrees(baseline, candidate);\n const matching = matchTrees(baseline.root, candidate.root);\n\n const partner = new Map<SemanticNode, SemanticNode>();\n for (const [left, right] of matching.pairs) partner.set(right, left);\n\n const tree = sameTree(baseline.root, candidate.root);\n const moved = comparison.deltas.length > 0;\n\n const found = boundariesOf(baseline.root, candidate.root, partner, {\n there: declaredIn(baseline),\n here: declaredIn(candidate),\n });\n if (found === undefined) {\n return {\n slice: sliceOf(tree, undefined, moved, place),\n identical: comparison.identical,\n deltas: comparison.deltas,\n };\n }\n\n attribute(found, comparison.deltas);\n\n const boundaries = found\n .filter((entry) => entry.inputs.length > 0 || entry.owned.length > 0)\n .map((draft) => settle(draft, place));\n\n const origins = boundaries.filter(\n (entry) =>\n entry.rung === 'stateful' ||\n entry.rung === 'external' ||\n entry.rung === 'undetermined' ||\n entry.rung === 'placed',\n );\n\n return {\n slice: sliceOf(tree, boundaries, moved, place),\n identical: comparison.identical,\n deltas: comparison.deltas,\n boundaries,\n ...(origins.length > 0 ? { origins: [...origins].sort((a, b) => a.depth - b.depth) } : {}),\n };\n}\n\n/** A boundary mid-construction, before its deltas are counted. */\ninterface Draft {\n readonly component: string;\n readonly path: NodePath;\n readonly basePath?: NodePath;\n readonly depth: number;\n readonly inputs: readonly MovedInput[];\n readonly unread: boolean;\n readonly paired: boolean;\n readonly owned: Delta[];\n}\n\n/**\n * Every boundary in the candidate tree, in document order, paired with its\n * baseline counterpart.\n *\n * Undefined when neither tree carried a holding at all, which is the ADR-0002\n * distinction this whole module rests on: nothing to read is not nothing to\n * report.\n */\nfunction boundariesOf(\n baselineRoot: SemanticNode,\n candidateRoot: SemanticNode,\n partner: ReadonlyMap<SemanticNode, SemanticNode>,\n declared: { readonly there: ReadonlySet<string>; readonly here: ReadonlySet<string> },\n): Draft[] | undefined {\n const mine = boundaryNodes(candidateRoot);\n const theirs = boundaryNodes(baselineRoot);\n if (mine.length === 0 && theirs.length === 0) return undefined;\n\n const paired = pairBoundaries(mine, theirs, partner);\n\n return mine.map((node) => {\n const other = paired.get(node);\n const moved = compareHoldings(other?.holding, node.holding);\n const inputs = [\n ...moved.inputs,\n ...cascadeInputs(other, node, declared.there, declared.here),\n ];\n return {\n component: node.provenance?.owners[0]?.name ?? '(anonymous)',\n path: node.path,\n ...(other === undefined ? {} : { basePath: other.path }),\n depth: depthOf(node.path),\n inputs,\n unread: moved.unread,\n paired: other !== undefined,\n owned: [],\n };\n });\n}\n\n/**\n * Every node where a component starts, in document order.\n *\n * Read from provenance and not from `holding`, which is the difference between\n * this module working on one collector and working on all of them. A holding is\n * what a *framework adapter* managed to read at a boundary — props values, hook\n * cells — and only `@variance-authority/unit-test` supplies one today. Keying the\n * boundary set on it made every rung unreachable from a browser run, including\n * the two that need no adapter at all: the ancestor cascade, which is read from\n * `styleProvenance`, and the tree comparison, which is read from the owner\n * chains. Those runs got `unread` — \"nothing can be said about why\" — while\n * holding the evidence to say it.\n *\n * {@link componentBoundaries} is the shared definition, so a boundary here is\n * the same node the component hashes and the wiring band call one. It reports a\n * node once per component that opens there; the nodes are what this needs, and\n * `boundariesOf` names each by its innermost owner exactly as before.\n *\n * A node with a holding and no provenance is still kept. That is an adapter that\n * read the fiber's values but not its owner chain, and dropping its evidence\n * because of a second failure would lose the one rung it can still reach.\n */\nfunction boundaryNodes(root: SemanticNode): readonly SemanticNode[] {\n const found = new Set<SemanticNode>();\n for (const boundary of componentBoundaries(root)) {\n // `(unattributed)` is the bucket for nodes whose owner chain broke, not a\n // component, and `causesBetween` refuses it for the same reason: a boundary\n // reported there would name a component nobody wrote and pool unrelated\n // parts of the page under one name.\n if (boundary.component === UNATTRIBUTED) continue;\n found.add(boundary.node);\n }\n\n const holders = (node: SemanticNode): void => {\n if (node.holding !== undefined) found.add(node);\n for (const child of node.children) holders(child);\n };\n holders(root);\n\n return [...found].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));\n}\n\n/**\n * Pair boundaries, preferring the tree matcher and falling back to the owner\n * chain.\n *\n * The fallback is not a nicety, it is the case the whole module exists for. A\n * component that took the other branch returns a `<span>` where it returned a\n * `<p>`, and the host-level matcher pairs those only when the snapshot recorded\n * that component as the author of both and it replaced exactly one element under\n * one parent (`match.ts`). Otherwise the tag is the thing that changed, and every\n * identity key that names neither an alias nor a role ends in it. Reporting the boundary as present\n * on one side only would throw away the props and the hook cells at exactly the\n * moment they are the answer — `Summary` did not appear, it *chose differently*,\n * and the evidence for that is sitting in a holding nobody joined.\n *\n * The chain — the boundary's owner names, outermost component included — is\n * stable under precisely that change, since neither the branch nor the tag\n * appears in it. Ties inside one chain are broken by document order, which is\n * positional and therefore shifts when a sibling is inserted; that is the same\n * cost `match.ts` pays for the same reason, and it is bounded here to\n * components sharing one chain that the matcher already failed to pair.\n */\nfunction pairBoundaries(\n mine: readonly SemanticNode[],\n theirs: readonly SemanticNode[],\n partner: ReadonlyMap<SemanticNode, SemanticNode>,\n): ReadonlyMap<SemanticNode, SemanticNode> {\n const paired = new Map<SemanticNode, SemanticNode>();\n const taken = new Set<SemanticNode>();\n\n for (const node of mine) {\n const other = partner.get(node);\n if (other?.holding === undefined || taken.has(other)) continue;\n paired.set(node, other);\n taken.add(other);\n }\n\n const spare = new Map<string, SemanticNode[]>();\n for (const node of theirs) {\n if (taken.has(node)) continue;\n const key = chainOf(node);\n const bucket = spare.get(key);\n if (bucket === undefined) spare.set(key, [node]);\n else bucket.push(node);\n }\n\n for (const node of mine) {\n if (paired.has(node)) continue;\n const bucket = spare.get(chainOf(node));\n const other = bucket?.shift();\n if (other !== undefined) paired.set(node, other);\n }\n\n return paired;\n}\n\nfunction chainOf(node: SemanticNode): string {\n return (node.provenance?.owners ?? []).map((owner) => owner.name).join('>');\n}\n\n/**\n * Give each delta to the deepest boundary that encloses it.\n *\n * By prefix rather than by scanning every boundary: a node's path *is* its\n * ancestry, so walking it up one segment at a time reaches the owning boundary\n * in as many steps as the node is deep. A `node-removed` delta carries a\n * baseline path, so it is resolved in baseline space — mixing the two spaces\n * would silently hand a removal to whichever candidate node now sits at that\n * address, which is the exact misattribution positional diffing is famous for.\n */\nfunction attribute(drafts: readonly Draft[], deltas: readonly Delta[]): void {\n const here = new Map<NodePath, Draft>();\n const there = new Map<NodePath, Draft>();\n for (const draft of drafts) {\n here.set(draft.path, draft);\n if (draft.basePath !== undefined) there.set(draft.basePath, draft);\n }\n\n for (const delta of deltas) {\n const index = delta.kind === 'node-removed' ? there : here;\n for (let path: NodePath | undefined = delta.path; path !== undefined; path = parentOf(path)) {\n const owner = index.get(path);\n if (owner !== undefined) {\n owner.owned.push(delta);\n break;\n }\n }\n }\n}\n\n/** Apply the precedence in {@link PartingRung} and freeze the row. */\nfunction settle(draft: Draft, place: PartingPlace): PartedBoundary {\n const bands = BANDS.filter((band) => draft.owned.some((delta) => delta.band === band));\n\n const moved = [\n ...new Set(draft.owned.map((delta) => delta.property).filter((name) => name !== undefined)),\n ].sort();\n\n return {\n component: draft.component,\n path: draft.path,\n depth: draft.depth,\n rung: rungOf(draft, place),\n inputs: draft.inputs,\n deltas: draft.owned.length,\n bands,\n ...(moved.length > 0 ? { moved } : {}),\n };\n}\n\nfunction rungOf(draft: Draft, place: PartingPlace): PartingRung {\n if (!draft.paired) return 'unpaired';\n if (draft.inputs.some((input) => input.kind === 'prop')) return 'handed';\n if (draft.inputs.some((input) => input.kind === 'context')) return 'provided';\n if (draft.inputs.some((input) => input.kind === 'inherited')) return 'inherited';\n if (draft.inputs.some((input) => input.name === 'useSyncExternalStore')) return 'external';\n if (draft.inputs.length > 0) return 'stateful';\n if (draft.unread) return 'unread';\n return place === 'same' ? 'undetermined' : 'placed';\n}\n\n\nfunction depthOf(path: NodePath): number {\n return path === '' ? 0 : path.split('/').length;\n}\n\nfunction parentOf(path: NodePath): NodePath | undefined {\n if (path === '') return undefined;\n const cut = path.lastIndexOf('/');\n return cut === -1 ? '' : path.slice(0, cut);\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import type { Diagnostic, SubjectRef } from './capture.js';
2
2
  import type { Viewport } from './environment.js';
3
3
  import { type Digest } from './hash.js';
4
+ import type { PackedSnapshot } from './packed.js';
4
5
  import type { ComponentHash } from './snapshot.js';
5
6
  /**
6
7
  * The render document: what gets *sent* somewhere to become an image.
@@ -127,8 +128,9 @@ export interface RenderFrame {
127
128
  /** Elements between `<body>` and the subject root, outermost first. */
128
129
  readonly ancestors: readonly FrameElement[];
129
130
  /**
130
- * Used width of the subject's parent box, in CSS pixels, when layout was
131
- * observable.
131
+ * Used `width` of the subject's parent, in CSS pixels and in the parent's own
132
+ * `box-sizing`, when layout was observable. Written back on the same box, it
133
+ * gives the content box a percentage on the subject resolves against.
132
134
  *
133
135
  * A subject declaring `width: 100%` is a different size in a 1024px page and
134
136
  * inside a 600px panel, and nothing else in this document says which it was.
@@ -261,7 +263,7 @@ export interface Raster {
261
263
  * change from the components the change merely moved needs both revisions, and
262
264
  * a stored baseline is an image: without this, a run has one document and ranks
263
265
  * by area — which
264
- * [journal 0013](../../../../docs/context/journal/0013-observability.md)
266
+ * [journal 0013](https://github.com/Variance-Authority/variance-authority/blob/main/docs/context/journal/0013-observability.md)
265
267
  * measured as backwards.
266
268
  *
267
269
  * Text, and small: one line per component boundary. It rides in the sidecar
@@ -298,6 +300,27 @@ export interface Raster {
298
300
  * defect in the suite as newly introduced on the first run after an upgrade.
299
301
  */
300
302
  readonly findingMarks?: readonly string[];
303
+ /**
304
+ * The semantic snapshot of the document that painted this, packed.
305
+ *
306
+ * The region join needs to know where each element was in the render the
307
+ * pixels are compared against. A changed region is the union of what left and
308
+ * what arrived, and a stored baseline is an image: without its own tree, a
309
+ * control replaced by a larger element changes pixels outside every box this
310
+ * render has, and the join names the row around it. With it, the join pairs
311
+ * the two renders' nodes with the matcher the semantic diff reads, so a
312
+ * region and a delta agree about which element a node was.
313
+ *
314
+ * Packed (see {@link PackedSnapshot}) because the tree is an order of
315
+ * magnitude larger than the component hashes, and it rides in every sidecar.
316
+ * Every store and transport carries the text as it is, and only the run that
317
+ * decides unpacks it.
318
+ *
319
+ * **Absent means not recorded.** A baseline written before this existed, or
320
+ * from a collector that supplied no snapshot, has none, and a verdict whose
321
+ * regions were joined without it says so.
322
+ */
323
+ readonly snapshot?: PackedSnapshot;
301
324
  }
302
325
  /**
303
326
  * A raster whose image is in hand.
@@ -1 +1 @@
1
- {"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/format/document.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Diagnostic, SubjectRef } from './capture.js';\nimport type { Viewport } from './environment.js';\nimport { digestValue, type Digest } from './hash.js';\nimport { pageOrigin, withoutOrigin } from './origin.js';\nimport type { ComponentHash } from './snapshot.js';\n\n/**\n * The render document: what gets *sent* somewhere to become an image.\n *\n * This is the acquisition phase's output and the only thing the rendering side\n * ever sees. It is plain serializable data for the same reason `RawCapture` is\n * (ADR-0006): the renderer may be a page in this process, a browser on this\n * machine, or a pinned container two networks away, and no code upstream of it\n * may assume which.\n *\n * Two routes produce one of these, and the point of the type is that they are\n * interchangeable:\n *\n * 1. **jsdom → document → render elsewhere.** jsdom cannot rasterize. It can\n * describe exactly what to rasterize, which is the sub-renderer split ADR-0002\n * asks for: the cheap tier decides almost everything, and the residue is\n * handed to something that owns a GPU.\n * 2. **playwright → document → render on a server.** The same payload, acquired\n * from a real engine. Used when the *deciding* machine and the *pinned*\n * machine are different, which is the case every CI setup actually has.\n *\n * A browser may instead capture a `Raster` in place. That is the sibling branch\n * of `CaptureMaterial`, not a third route that somehow produces this type.\n *\n * ## Why this is small\n *\n * `css` holds the *applicable* rules only — the pruning of ADR-0003, which on\n * `@variance-authority/dom`'s accretion fixture takes 1010 rules to 1. That\n * number is a cost claim as much as a\n * correctness one: shipping a document over a network hop is only sensible if the\n * document is not the entire design system plus Storybook's chrome.\n */\nexport interface RenderDocument {\n readonly documentVersion: 1;\n readonly subject: SubjectRef;\n\n /**\n * The subject subtree, serialized.\n *\n * Serialized rather than referenced by URL: a URL makes the renderer\n * responsible for reproducing the application's state, which it cannot do and\n * should not try. What is sent is the markup that existed at the moment of\n * capture, which is the only definition of \"this render\" that survives a hop.\n */\n readonly html: string;\n\n /**\n * The ancestor context the subject was rendered inside.\n *\n * Not decoration. Pruning keeps rules like `html.dark .card` and\n * `.app .list > li`, whose left-hand side lives *above* the subject — render\n * the subtree bare and those rules match nothing, so the image is missing\n * exactly the styling the collector went to the trouble of proving applies.\n *\n * Reproduced as empty open tags rather than by re-rendering the application:\n * what a selector needs is a chain of elements with the right tags, ids,\n * classes, and attributes, and nothing above the subject contributes anything\n * else to it that {@link RenderDocument.inherited} does not already carry.\n */\n readonly frame: RenderFrame;\n\n /**\n * Applicable stylesheet text, in cascade order.\n *\n * Order is load-bearing and is the caller's to preserve — the cascade breaks\n * specificity ties by document order, so a set that arrives shuffled paints\n * differently while hashing the same if this were a set rather than a list.\n */\n readonly css: readonly string[];\n\n readonly viewport: Viewport;\n\n /**\n * Inherited values in force at the subject root, applied to its wrapper.\n *\n * The same requirement as `RawCapture.inheritedSeed` and for the same reason:\n * pruning drops rules on ancestors outside the subtree, and a font-size\n * inherited from `<html>` is not optional decoration — dropping it changes\n * every metric in the image.\n */\n readonly inherited: Readonly<Record<string, string>>;\n\n /**\n * Fonts the acquiring side observed as loaded.\n *\n * Carried, not resolved. A renderer that lacks one of these will substitute\n * and produce different metrics, and that has to be *detectable* rather than\n * silently absorbed — see {@link RenderIdentity}.\n */\n readonly fonts: readonly string[];\n\n /** External references, url → content hash. Present so a swap is visible. */\n readonly assets?: Readonly<Record<string, string>>;\n\n /**\n * Base against which relative resource references were resolved.\n *\n * Preserves relative-reference resolution for either document kind. Resource\n * closure is denoted by `resources`, not by the presence of this field.\n */\n readonly baseUrl?: string;\n\n /**\n * Immutable responses needed to paint this document, keyed by absolute URL.\n *\n * Presence — including an empty object — means the document is\n * resource-closed: a renderer must not consult the network for a missing URL.\n * Absence preserves the older network-capable document contract and must not\n * be described as portable to another machine.\n */\n readonly resources?: Readonly<Record<string, RenderResource>>;\n\n readonly diagnostics: readonly Diagnostic[];\n}\n\nexport interface RenderResource {\n readonly contentType: string;\n /** Response bytes, base64. */\n readonly bytes: string;\n readonly digest: Digest;\n\n /**\n * The status a renderer answers this URL with. Absent means 200.\n *\n * A subject is allowed to reference something that is not there: a fallback\n * test does it on purpose, and the browser it was acquired in painted the\n * broken state rather than the image. Recording the failure as a failure is\n * what lets a renderer reproduce that state without asking the network.\n */\n readonly status?: number;\n}\n\nexport interface RenderFrame {\n /** Attributes on `<html>`. `class=\"dark\"` here decides half a design system. */\n readonly html: Readonly<Record<string, string>>;\n readonly body: Readonly<Record<string, string>>;\n /** Elements between `<body>` and the subject root, outermost first. */\n readonly ancestors: readonly FrameElement[];\n\n /**\n * Used width of the subject's parent box, in CSS pixels, when layout was\n * observable.\n *\n * A subject declaring `width: 100%` is a different size in a 1024px page and\n * inside a 600px panel, and nothing else in this document says which it was.\n * Absent under a profile with no layout engine, in which case the render falls\n * back to the viewport and the difference, if any, is real rather than hidden.\n */\n readonly containerWidth?: number;\n}\n\nexport interface FrameElement {\n readonly tag: string;\n readonly attributes: Readonly<Record<string, string>>;\n}\n\n/**\n * Content address of a document: *what is to be painted*.\n *\n * This is the deferral lever. Rendering is the expensive phase, and a document\n * whose digest has been rendered before under the same identity has an image\n * already — so the cheapest render is the one that is skipped. Nothing about the\n * branch, the commit, or the file path enters this (Principle 4), so moving a\n * story between files does not cost a re-render.\n *\n * Deliberately covers `viewport` in full, `deviceScaleFactor` included: unlike\n * the semantic key (ADR-0010), a raster genuinely differs at 2x and the whole\n * purpose of this digest is to address the raster.\n *\n * **Limit, stated rather than discovered.** Declaration values arrive serialized\n * by the acquiring engine's CSSOM — jsdom writes `rgb(18, 52, 86)` where the\n * author wrote `#123456` — and this phase deliberately does not canonicalize\n * them, because a collector that normalizes is a second ruleset versioned by\n * nothing (ADR-0001). So two engines can address the same page differently. That\n * makes a render cache *per acquiring engine*, which is a missed cache hit and\n * never a wrong image.\n */\nexport function documentDigest(document: RenderDocument): Digest {\n const origin = pageOrigin(document.baseUrl);\n return digestValue({\n documentVersion: document.documentVersion,\n html: document.html,\n frame: {\n html: { ...document.frame.html },\n body: { ...document.frame.body },\n ancestors: document.frame.ancestors.map((element) => ({\n tag: element.tag,\n attributes: { ...element.attributes },\n })),\n ...(document.frame.containerWidth !== undefined\n ? { containerWidth: document.frame.containerWidth }\n : {}),\n },\n css: [...document.css],\n viewport: { ...document.viewport },\n inherited: { ...document.inherited },\n fonts: [...document.fonts],\n ...(document.assets ? { assets: { ...document.assets } } : {}),\n // The base and the page's own resources as paths: a collector serves the\n // build on a free port, and a digest holding it would miss the render cache\n // on every run. The document keeps both absolute, because the renderer\n // resolves against them; a resource another origin serves stays absolute.\n ...(document.baseUrl !== undefined ? { baseUrl: withoutOrigin(document.baseUrl, origin) } : {}),\n ...(document.resources !== undefined\n ? {\n resources: Object.fromEntries(\n Object.entries(document.resources).map(([url, resource]) => [\n withoutOrigin(url, origin),\n { ...resource },\n ]),\n ),\n }\n : {}),\n });\n}\n\n/**\n * Who painted it.\n *\n * The durable half of this system rests on one requirement — *images compared\n * against each other must come from the same machine* — and a requirement that\n * is merely stated gets violated by a CI runner upgrade nobody announced. This\n * makes it a value, so it can be compared rather than assumed.\n *\n * Every field is something that has been observed to move pixels without moving\n * markup. None of them is a guess about what *might* matter.\n */\nexport interface RenderIdentity {\n /** `playwright-chromium@1.49.0`, `remote:render.internal`, … */\n readonly renderer: string;\n /** Engine build. A Chromium bump repaints text; that is not a regression. */\n readonly engine: string;\n /** OS and architecture. Font rasterization differs across both. */\n readonly platform: string;\n readonly deviceScaleFactor: number;\n /**\n * Fonts the *renderer* actually has.\n *\n * Compared against {@link RenderDocument.fonts} at render time, because a\n * substituted font is the single most common way two machines disagree, and\n * it is invisible in every artifact except the image itself.\n */\n readonly fonts: readonly string[];\n\n /**\n * Digest of what the renderer did *to* the page so it could be observed.\n *\n * Pausing animations, hiding a caret, waiting for fonts — each is necessary\n * and each widens the gap between what was measured and what a person sees.\n * The gap itself is acceptable; a *difference* in it between two runs is not,\n * because the images then differ for a reason that is not the code and the\n * report blames whichever component sits under the pixels. Folding it in here\n * names that gap as the cause of the change instead, by the same mechanism\n * that names a moved machine.\n *\n * Optional only because a renderer may predate the field. Absent means the\n * renderer did not record what it did, which is emphatically not the same as\n * \"it did nothing\" — and two such renderers will compare, which is the hazard\n * that argues for every renderer setting it.\n */\n readonly stabilization?: Digest;\n\n /**\n * Digest of pixel-affecting browser launch and raster settings.\n *\n * Separate from stabilization: one describes how the browser paints, the\n * other what was done to the page before it was read.\n */\n readonly rasterization?: Digest;\n}\n\nexport function identityDigest(identity: RenderIdentity): Digest {\n return digestValue({\n renderer: identity.renderer,\n engine: identity.engine,\n platform: identity.platform,\n ...(identity.stabilization !== undefined ? { stabilization: identity.stabilization } : {}),\n ...(identity.rasterization !== undefined ? { rasterization: identity.rasterization } : {}),\n deviceScaleFactor: identity.deviceScaleFactor,\n fonts: [...identity.fonts],\n });\n}\n\n/**\n * A rendered image and the conditions it was produced under.\n *\n * `bytes` is base64 rather than a `Buffer` so the whole value survives the same\n * hops the document does. A raster that could only exist in Node would make the\n * remote route a special case in every function that touches one.\n *\n * The image is optional, and the three fields that describe it move together.\n * A subject that occupies no pixels — a wrapper whose only child went to a\n * portal, a `describeConformance` mount with no children — still has a\n * document, rules, components and an accessibility tree, all of which compare.\n * Refusing to photograph it is right; refusing the subject reported a quarter\n * of Material UI's unit tier as unobserved over an axis nothing was asking\n * about. Absent here means *this subject has no pixels to observe*, which is a\n * measurement; it never means *the image was lost*.\n */\nexport interface Raster {\n readonly documentDigest: Digest;\n readonly identity: RenderIdentity;\n /**\n * Device pixels. `width / viewport.width` is the scale, and is asserted.\n *\n * Absent exactly when {@link bytes} is, and {@link pictured} is the one place\n * that reads the three together.\n */\n readonly width?: number;\n readonly height?: number;\n /** PNG, base64. Absent when the subject occupies no pixels. */\n readonly bytes?: string;\n /** Fonts the document declared that the renderer did not have. */\n readonly missingFonts: readonly string[];\n\n /**\n * The browser-computed accessibility tree from the acquisition that produced\n * this candidate. It is independent evidence: pixel equality cannot settle it.\n * Absent means the acquisition host did not observe this boundary.\n */\n readonly accessibility?: import('./accessibility.js').AccessibilitySnapshot;\n\n /**\n * What the document that painted this said about its own components (ADR-0018).\n *\n * The field that makes a baseline self-describing, and the reason it is here\n * rather than only in a history store. Separating the component that *caused* a\n * change from the components the change merely moved needs both revisions, and\n * a stored baseline is an image: without this, a run has one document and ranks\n * by area — which\n * [journal 0013](../../../../docs/context/journal/0013-observability.md)\n * measured as backwards.\n *\n * Text, and small: one line per component boundary. It rides in the sidecar\n * beside a PNG that dominates it, so a baseline carries its own explanation\n * wherever it is copied, and a store or a transport that drops it degrades to\n * ranking by area rather than to a wrong answer.\n *\n * Optional because a baseline written before this existed does not have it, and\n * because a profile or a collector that supplies no snapshot cannot produce it.\n * Absent means *unknown*, never *no components*.\n */\n readonly components?: readonly ComponentHash[];\n\n /**\n * What inspection found in the document that painted this, as marks.\n *\n * The field that lets a later run say *this defect was already here* instead of\n * printing every defect it finds with no indication of when it arrived. A\n * finding is produced from one render with no baseline consulted — that is the\n * point of having one — and the consequence is that the list is identical on\n * the run that introduced a defect and on the two hundred runs after it. A\n * reviewer reading `a control inside another control` has no way to tell an\n * inherited condition from something they just wrote, and the two are different\n * decisions.\n *\n * Marks rather than findings, from {@link findingMark}. The sentence, the\n * landmark phrase and the source line are all rewritten by edits that do not\n * touch the defect, so storing them would report a defect as new every time the\n * copy beside it changed — and they are the bulk of the bytes, in a sidecar\n * that is committed beside every baseline.\n *\n * **Absent means nothing inspected it, never that it was clean.** A baseline\n * written before this existed, or by a collector that supplies no snapshot, has\n * no list, and a run that read absence as `[]` would announce every standing\n * defect in the suite as newly introduced on the first run after an upgrade.\n */\n readonly findingMarks?: readonly string[];\n}\n\n/**\n * A raster whose image is in hand.\n *\n * Every pixel comparison in the codebase needs all three of `bytes`, `width`\n * and `height`, and they are absent together or present together. Narrowing\n * them one at a time is how a caller ends up reading the bytes of a subject\n * that was never photographed, so the narrowing lives here and is done once.\n */\nexport type Pictured = Raster & {\n readonly width: number;\n readonly height: number;\n readonly bytes: string;\n};\n\n/** Whether this record carries an image, narrowing all three fields together. */\nexport function pictured(raster: Raster): raster is Pictured {\n return raster.bytes !== undefined && raster.width !== undefined && raster.height !== undefined;\n}\n\n/**\n * Whether the subject occupied any pixels, answered without the image.\n *\n * The question {@link pictured} cannot be asked of a sidecar. Bytes live in a\n * separate file, row or object, so a record read without them carries the width\n * and the height and nothing else — and those *are* the store's record of\n * whether there was ever anything to photograph, which is why `sidecarFrom`\n * refuses a sidecar carrying one of them.\n *\n * So: this for a record, {@link pictured} for an image in hand. A caller that\n * reached for `pictured` here would read every sidecar in the store as a subject\n * with no pixels, because none of them have bytes.\n */\nexport function occupiesPixels(raster: Omit<Raster, 'bytes'>): boolean {\n return raster.width !== undefined && raster.height !== undefined;\n}\n\n/**\n * The image, or a refusal naming the subject that has none.\n *\n * For the paths where a missing image is a programming error rather than a\n * measurement — a diff, a PNG encode — so they read as assertions instead of\n * as a non-null assertion nobody can audit.\n */\nexport function picture(raster: Raster, subject: string): Pictured {\n if (!pictured(raster)) {\n throw new Error(\n `\\`${subject}\\` occupies no pixels, and this path needs an image; ` +\n 'a subject with no raster compares on its document alone',\n );\n }\n return raster;\n}\n"]}
1
+ {"version":3,"file":"document.js","sourceRoot":"","sources":["../../src/format/document.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Diagnostic, SubjectRef } from './capture.js';\nimport type { Viewport } from './environment.js';\nimport { digestValue, type Digest } from './hash.js';\nimport { pageOrigin, withoutOrigin } from './origin.js';\nimport type { PackedSnapshot } from './packed.js';\nimport type { ComponentHash } from './snapshot.js';\n\n/**\n * The render document: what gets *sent* somewhere to become an image.\n *\n * This is the acquisition phase's output and the only thing the rendering side\n * ever sees. It is plain serializable data for the same reason `RawCapture` is\n * (ADR-0006): the renderer may be a page in this process, a browser on this\n * machine, or a pinned container two networks away, and no code upstream of it\n * may assume which.\n *\n * Two routes produce one of these, and the point of the type is that they are\n * interchangeable:\n *\n * 1. **jsdom → document → render elsewhere.** jsdom cannot rasterize. It can\n * describe exactly what to rasterize, which is the sub-renderer split ADR-0002\n * asks for: the cheap tier decides almost everything, and the residue is\n * handed to something that owns a GPU.\n * 2. **playwright → document → render on a server.** The same payload, acquired\n * from a real engine. Used when the *deciding* machine and the *pinned*\n * machine are different, which is the case every CI setup actually has.\n *\n * A browser may instead capture a `Raster` in place. That is the sibling branch\n * of `CaptureMaterial`, not a third route that somehow produces this type.\n *\n * ## Why this is small\n *\n * `css` holds the *applicable* rules only — the pruning of ADR-0003, which on\n * `@variance-authority/dom`'s accretion fixture takes 1010 rules to 1. That\n * number is a cost claim as much as a\n * correctness one: shipping a document over a network hop is only sensible if the\n * document is not the entire design system plus Storybook's chrome.\n */\nexport interface RenderDocument {\n readonly documentVersion: 1;\n readonly subject: SubjectRef;\n\n /**\n * The subject subtree, serialized.\n *\n * Serialized rather than referenced by URL: a URL makes the renderer\n * responsible for reproducing the application's state, which it cannot do and\n * should not try. What is sent is the markup that existed at the moment of\n * capture, which is the only definition of \"this render\" that survives a hop.\n */\n readonly html: string;\n\n /**\n * The ancestor context the subject was rendered inside.\n *\n * Not decoration. Pruning keeps rules like `html.dark .card` and\n * `.app .list > li`, whose left-hand side lives *above* the subject — render\n * the subtree bare and those rules match nothing, so the image is missing\n * exactly the styling the collector went to the trouble of proving applies.\n *\n * Reproduced as empty open tags rather than by re-rendering the application:\n * what a selector needs is a chain of elements with the right tags, ids,\n * classes, and attributes, and nothing above the subject contributes anything\n * else to it that {@link RenderDocument.inherited} does not already carry.\n */\n readonly frame: RenderFrame;\n\n /**\n * Applicable stylesheet text, in cascade order.\n *\n * Order is load-bearing and is the caller's to preserve — the cascade breaks\n * specificity ties by document order, so a set that arrives shuffled paints\n * differently while hashing the same if this were a set rather than a list.\n */\n readonly css: readonly string[];\n\n readonly viewport: Viewport;\n\n /**\n * Inherited values in force at the subject root, applied to its wrapper.\n *\n * The same requirement as `RawCapture.inheritedSeed` and for the same reason:\n * pruning drops rules on ancestors outside the subtree, and a font-size\n * inherited from `<html>` is not optional decoration — dropping it changes\n * every metric in the image.\n */\n readonly inherited: Readonly<Record<string, string>>;\n\n /**\n * Fonts the acquiring side observed as loaded.\n *\n * Carried, not resolved. A renderer that lacks one of these will substitute\n * and produce different metrics, and that has to be *detectable* rather than\n * silently absorbed — see {@link RenderIdentity}.\n */\n readonly fonts: readonly string[];\n\n /** External references, url → content hash. Present so a swap is visible. */\n readonly assets?: Readonly<Record<string, string>>;\n\n /**\n * Base against which relative resource references were resolved.\n *\n * Preserves relative-reference resolution for either document kind. Resource\n * closure is denoted by `resources`, not by the presence of this field.\n */\n readonly baseUrl?: string;\n\n /**\n * Immutable responses needed to paint this document, keyed by absolute URL.\n *\n * Presence — including an empty object — means the document is\n * resource-closed: a renderer must not consult the network for a missing URL.\n * Absence preserves the older network-capable document contract and must not\n * be described as portable to another machine.\n */\n readonly resources?: Readonly<Record<string, RenderResource>>;\n\n readonly diagnostics: readonly Diagnostic[];\n}\n\nexport interface RenderResource {\n readonly contentType: string;\n /** Response bytes, base64. */\n readonly bytes: string;\n readonly digest: Digest;\n\n /**\n * The status a renderer answers this URL with. Absent means 200.\n *\n * A subject is allowed to reference something that is not there: a fallback\n * test does it on purpose, and the browser it was acquired in painted the\n * broken state rather than the image. Recording the failure as a failure is\n * what lets a renderer reproduce that state without asking the network.\n */\n readonly status?: number;\n}\n\nexport interface RenderFrame {\n /** Attributes on `<html>`. `class=\"dark\"` here decides half a design system. */\n readonly html: Readonly<Record<string, string>>;\n readonly body: Readonly<Record<string, string>>;\n /** Elements between `<body>` and the subject root, outermost first. */\n readonly ancestors: readonly FrameElement[];\n\n /**\n * Used `width` of the subject's parent, in CSS pixels and in the parent's own\n * `box-sizing`, when layout was observable. Written back on the same box, it\n * gives the content box a percentage on the subject resolves against.\n *\n * A subject declaring `width: 100%` is a different size in a 1024px page and\n * inside a 600px panel, and nothing else in this document says which it was.\n * Absent under a profile with no layout engine, in which case the render falls\n * back to the viewport and the difference, if any, is real rather than hidden.\n */\n readonly containerWidth?: number;\n}\n\nexport interface FrameElement {\n readonly tag: string;\n readonly attributes: Readonly<Record<string, string>>;\n}\n\n/**\n * Content address of a document: *what is to be painted*.\n *\n * This is the deferral lever. Rendering is the expensive phase, and a document\n * whose digest has been rendered before under the same identity has an image\n * already — so the cheapest render is the one that is skipped. Nothing about the\n * branch, the commit, or the file path enters this (Principle 4), so moving a\n * story between files does not cost a re-render.\n *\n * Deliberately covers `viewport` in full, `deviceScaleFactor` included: unlike\n * the semantic key (ADR-0010), a raster genuinely differs at 2x and the whole\n * purpose of this digest is to address the raster.\n *\n * **Limit, stated rather than discovered.** Declaration values arrive serialized\n * by the acquiring engine's CSSOM — jsdom writes `rgb(18, 52, 86)` where the\n * author wrote `#123456` — and this phase deliberately does not canonicalize\n * them, because a collector that normalizes is a second ruleset versioned by\n * nothing (ADR-0001). So two engines can address the same page differently. That\n * makes a render cache *per acquiring engine*, which is a missed cache hit and\n * never a wrong image.\n */\nexport function documentDigest(document: RenderDocument): Digest {\n const origin = pageOrigin(document.baseUrl);\n return digestValue({\n documentVersion: document.documentVersion,\n html: document.html,\n frame: {\n html: { ...document.frame.html },\n body: { ...document.frame.body },\n ancestors: document.frame.ancestors.map((element) => ({\n tag: element.tag,\n attributes: { ...element.attributes },\n })),\n ...(document.frame.containerWidth !== undefined\n ? { containerWidth: document.frame.containerWidth }\n : {}),\n },\n css: [...document.css],\n viewport: { ...document.viewport },\n inherited: { ...document.inherited },\n fonts: [...document.fonts],\n ...(document.assets ? { assets: { ...document.assets } } : {}),\n // The base and the page's own resources as paths: a collector serves the\n // build on a free port, and a digest holding it would miss the render cache\n // on every run. The document keeps both absolute, because the renderer\n // resolves against them; a resource another origin serves stays absolute.\n ...(document.baseUrl !== undefined ? { baseUrl: withoutOrigin(document.baseUrl, origin) } : {}),\n ...(document.resources !== undefined\n ? {\n resources: Object.fromEntries(\n Object.entries(document.resources).map(([url, resource]) => [\n withoutOrigin(url, origin),\n { ...resource },\n ]),\n ),\n }\n : {}),\n });\n}\n\n/**\n * Who painted it.\n *\n * The durable half of this system rests on one requirement — *images compared\n * against each other must come from the same machine* — and a requirement that\n * is merely stated gets violated by a CI runner upgrade nobody announced. This\n * makes it a value, so it can be compared rather than assumed.\n *\n * Every field is something that has been observed to move pixels without moving\n * markup. None of them is a guess about what *might* matter.\n */\nexport interface RenderIdentity {\n /** `playwright-chromium@1.49.0`, `remote:render.internal`, … */\n readonly renderer: string;\n /** Engine build. A Chromium bump repaints text; that is not a regression. */\n readonly engine: string;\n /** OS and architecture. Font rasterization differs across both. */\n readonly platform: string;\n readonly deviceScaleFactor: number;\n /**\n * Fonts the *renderer* actually has.\n *\n * Compared against {@link RenderDocument.fonts} at render time, because a\n * substituted font is the single most common way two machines disagree, and\n * it is invisible in every artifact except the image itself.\n */\n readonly fonts: readonly string[];\n\n /**\n * Digest of what the renderer did *to* the page so it could be observed.\n *\n * Pausing animations, hiding a caret, waiting for fonts — each is necessary\n * and each widens the gap between what was measured and what a person sees.\n * The gap itself is acceptable; a *difference* in it between two runs is not,\n * because the images then differ for a reason that is not the code and the\n * report blames whichever component sits under the pixels. Folding it in here\n * names that gap as the cause of the change instead, by the same mechanism\n * that names a moved machine.\n *\n * Optional only because a renderer may predate the field. Absent means the\n * renderer did not record what it did, which is emphatically not the same as\n * \"it did nothing\" — and two such renderers will compare, which is the hazard\n * that argues for every renderer setting it.\n */\n readonly stabilization?: Digest;\n\n /**\n * Digest of pixel-affecting browser launch and raster settings.\n *\n * Separate from stabilization: one describes how the browser paints, the\n * other what was done to the page before it was read.\n */\n readonly rasterization?: Digest;\n}\n\nexport function identityDigest(identity: RenderIdentity): Digest {\n return digestValue({\n renderer: identity.renderer,\n engine: identity.engine,\n platform: identity.platform,\n ...(identity.stabilization !== undefined ? { stabilization: identity.stabilization } : {}),\n ...(identity.rasterization !== undefined ? { rasterization: identity.rasterization } : {}),\n deviceScaleFactor: identity.deviceScaleFactor,\n fonts: [...identity.fonts],\n });\n}\n\n/**\n * A rendered image and the conditions it was produced under.\n *\n * `bytes` is base64 rather than a `Buffer` so the whole value survives the same\n * hops the document does. A raster that could only exist in Node would make the\n * remote route a special case in every function that touches one.\n *\n * The image is optional, and the three fields that describe it move together.\n * A subject that occupies no pixels — a wrapper whose only child went to a\n * portal, a `describeConformance` mount with no children — still has a\n * document, rules, components and an accessibility tree, all of which compare.\n * Refusing to photograph it is right; refusing the subject reported a quarter\n * of Material UI's unit tier as unobserved over an axis nothing was asking\n * about. Absent here means *this subject has no pixels to observe*, which is a\n * measurement; it never means *the image was lost*.\n */\nexport interface Raster {\n readonly documentDigest: Digest;\n readonly identity: RenderIdentity;\n /**\n * Device pixels. `width / viewport.width` is the scale, and is asserted.\n *\n * Absent exactly when {@link bytes} is, and {@link pictured} is the one place\n * that reads the three together.\n */\n readonly width?: number;\n readonly height?: number;\n /** PNG, base64. Absent when the subject occupies no pixels. */\n readonly bytes?: string;\n /** Fonts the document declared that the renderer did not have. */\n readonly missingFonts: readonly string[];\n\n /**\n * The browser-computed accessibility tree from the acquisition that produced\n * this candidate. It is independent evidence: pixel equality cannot settle it.\n * Absent means the acquisition host did not observe this boundary.\n */\n readonly accessibility?: import('./accessibility.js').AccessibilitySnapshot;\n\n /**\n * What the document that painted this said about its own components (ADR-0018).\n *\n * The field that makes a baseline self-describing, and the reason it is here\n * rather than only in a history store. Separating the component that *caused* a\n * change from the components the change merely moved needs both revisions, and\n * a stored baseline is an image: without this, a run has one document and ranks\n * by area — which\n * [journal 0013](https://github.com/Variance-Authority/variance-authority/blob/main/docs/context/journal/0013-observability.md)\n * measured as backwards.\n *\n * Text, and small: one line per component boundary. It rides in the sidecar\n * beside a PNG that dominates it, so a baseline carries its own explanation\n * wherever it is copied, and a store or a transport that drops it degrades to\n * ranking by area rather than to a wrong answer.\n *\n * Optional because a baseline written before this existed does not have it, and\n * because a profile or a collector that supplies no snapshot cannot produce it.\n * Absent means *unknown*, never *no components*.\n */\n readonly components?: readonly ComponentHash[];\n\n /**\n * What inspection found in the document that painted this, as marks.\n *\n * The field that lets a later run say *this defect was already here* instead of\n * printing every defect it finds with no indication of when it arrived. A\n * finding is produced from one render with no baseline consulted — that is the\n * point of having one — and the consequence is that the list is identical on\n * the run that introduced a defect and on the two hundred runs after it. A\n * reviewer reading `a control inside another control` has no way to tell an\n * inherited condition from something they just wrote, and the two are different\n * decisions.\n *\n * Marks rather than findings, from {@link findingMark}. The sentence, the\n * landmark phrase and the source line are all rewritten by edits that do not\n * touch the defect, so storing them would report a defect as new every time the\n * copy beside it changed — and they are the bulk of the bytes, in a sidecar\n * that is committed beside every baseline.\n *\n * **Absent means nothing inspected it, never that it was clean.** A baseline\n * written before this existed, or by a collector that supplies no snapshot, has\n * no list, and a run that read absence as `[]` would announce every standing\n * defect in the suite as newly introduced on the first run after an upgrade.\n */\n readonly findingMarks?: readonly string[];\n\n /**\n * The semantic snapshot of the document that painted this, packed.\n *\n * The region join needs to know where each element was in the render the\n * pixels are compared against. A changed region is the union of what left and\n * what arrived, and a stored baseline is an image: without its own tree, a\n * control replaced by a larger element changes pixels outside every box this\n * render has, and the join names the row around it. With it, the join pairs\n * the two renders' nodes with the matcher the semantic diff reads, so a\n * region and a delta agree about which element a node was.\n *\n * Packed (see {@link PackedSnapshot}) because the tree is an order of\n * magnitude larger than the component hashes, and it rides in every sidecar.\n * Every store and transport carries the text as it is, and only the run that\n * decides unpacks it.\n *\n * **Absent means not recorded.** A baseline written before this existed, or\n * from a collector that supplied no snapshot, has none, and a verdict whose\n * regions were joined without it says so.\n */\n readonly snapshot?: PackedSnapshot;\n}\n\n/**\n * A raster whose image is in hand.\n *\n * Every pixel comparison in the codebase needs all three of `bytes`, `width`\n * and `height`, and they are absent together or present together. Narrowing\n * them one at a time is how a caller ends up reading the bytes of a subject\n * that was never photographed, so the narrowing lives here and is done once.\n */\nexport type Pictured = Raster & {\n readonly width: number;\n readonly height: number;\n readonly bytes: string;\n};\n\n/** Whether this record carries an image, narrowing all three fields together. */\nexport function pictured(raster: Raster): raster is Pictured {\n return raster.bytes !== undefined && raster.width !== undefined && raster.height !== undefined;\n}\n\n/**\n * Whether the subject occupied any pixels, answered without the image.\n *\n * The question {@link pictured} cannot be asked of a sidecar. Bytes live in a\n * separate file, row or object, so a record read without them carries the width\n * and the height and nothing else — and those *are* the store's record of\n * whether there was ever anything to photograph, which is why `sidecarFrom`\n * refuses a sidecar carrying one of them.\n *\n * So: this for a record, {@link pictured} for an image in hand. A caller that\n * reached for `pictured` here would read every sidecar in the store as a subject\n * with no pixels, because none of them have bytes.\n */\nexport function occupiesPixels(raster: Omit<Raster, 'bytes'>): boolean {\n return raster.width !== undefined && raster.height !== undefined;\n}\n\n/**\n * The image, or a refusal naming the subject that has none.\n *\n * For the paths where a missing image is a programming error rather than a\n * measurement — a diff, a PNG encode — so they read as assertions instead of\n * as a non-null assertion nobody can audit.\n */\nexport function picture(raster: Raster, subject: string): Pictured {\n if (!pictured(raster)) {\n throw new Error(\n `\\`${subject}\\` occupies no pixels, and this path needs an image; ` +\n 'a subject with no raster compares on its document alone',\n );\n }\n return raster;\n}\n"]}
@@ -28,6 +28,8 @@ export type { EnvironmentInputs, EnvironmentKey, EnvironmentDelta, EnvironmentFi
28
28
  export { environmentKey, diffEnvironments } from './environment.js';
29
29
  export { INTERVENTIONS, SEMANTIC_RECIPE, COLLECT_RECIPE, DEFAULT_STABILIZE, LAYOUT_RECIPE, RASTER_RECIPE, holdAnimations, pinAnimations, hideCaret, hideScrollbars, hidePresentationalImages, waitForFonts, waitForImages, forTier, conflicts, interventionById, recipeOf, recipeToDrive, recipeCss, recipeScreenshot, recipeDigest, settleRecipe, describeRecipe, } from './stabilize.js';
30
30
  export type { Intervention, Recipe, Trick, ScreenshotOptions, SettleTarget, } from './stabilize.js';
31
+ export type { Step, StepClock, StepTotal } from './steps.js';
32
+ export { stepClock, timeOn, totalsOf } from './steps.js';
31
33
  export { DEFAULT_NOW, driveBeforeLoad, driveBeforeRead, featuresOf, forDriver } from './drive.js';
32
34
  export type { DriverState, DriverTarget, MotionPreference } from './intervention.js';
33
35
  export type { Provenance, OwnerFrame, SourceLocation, StackFrame } from './provenance.js';
@@ -38,6 +40,11 @@ export { keyedByPosition } from './wiring.js';
38
40
  export type { RawCapture, RawNode, RawAria, MatchedRule, Declaration, Rect, SubjectRef, Diagnostic, } from './capture.js';
39
41
  export { intersectionArea, mergeDiagnostics } from './capture.js';
40
42
  export type { SemanticSnapshot, SemanticNode, NodePath, StyleProvenanceEntry, IgnoreSite, ComponentHash, } from './snapshot.js';
43
+ export { isRenderDocument, isSemanticSnapshot, isSubjectRef } from './shape.js';
41
44
  export { documentDigest, identityDigest, occupiesPixels, pictured, picture } from './document.js';
42
45
  export type { RenderDocument, RenderFrame, FrameElement, RenderResource, RenderIdentity, Raster, Pictured, } from './document.js';
46
+ export { keepsSnapshot, PACKED_SNAPSHOT_HEADER } from './packed.js';
47
+ export type { PackedSnapshot } from './packed.js';
48
+ export { atWidth, realizes, widenedFrom } from './width.js';
49
+ export type { Widenable } from './width.js';
43
50
  //# sourceMappingURL=index.d.ts.map
@@ -20,9 +20,13 @@ export { assetUrl, pageOrigin, withoutOrigin } from './origin.js';
20
20
  export { tierReaches } from './tier.js';
21
21
  export { environmentKey, diffEnvironments } from './environment.js';
22
22
  export { INTERVENTIONS, SEMANTIC_RECIPE, COLLECT_RECIPE, DEFAULT_STABILIZE, LAYOUT_RECIPE, RASTER_RECIPE, holdAnimations, pinAnimations, hideCaret, hideScrollbars, hidePresentationalImages, waitForFonts, waitForImages, forTier, conflicts, interventionById, recipeOf, recipeToDrive, recipeCss, recipeScreenshot, recipeDigest, settleRecipe, describeRecipe, } from './stabilize.js';
23
+ export { stepClock, timeOn, totalsOf } from './steps.js';
23
24
  export { DEFAULT_NOW, driveBeforeLoad, driveBeforeRead, featuresOf, forDriver } from './drive.js';
24
25
  export { propsDigest, heldDigest, jsxSourceOf, relativizeSource, JSX_SOURCE } from './provenance.js';
25
26
  export { keyedByPosition } from './wiring.js';
26
27
  export { intersectionArea, mergeDiagnostics } from './capture.js';
28
+ export { isRenderDocument, isSemanticSnapshot, isSubjectRef } from './shape.js';
27
29
  export { documentDigest, identityDigest, occupiesPixels, pictured, picture } from './document.js';
30
+ export { keepsSnapshot, PACKED_SNAPSHOT_HEADER } from './packed.js';
31
+ export { atWidth, realizes, widenedFrom } from './width.js';
28
32
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/format/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAG/D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG3F,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGjJ,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE3F,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAUxC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpE,OAAO,EACL,aAAa,EACb,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,cAAc,EACd,aAAa,EACb,SAAS,EACT,cAAc,EACd,wBAAwB,EACxB,YAAY,EACZ,aAAa,EACb,OAAO,EACP,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,cAAc,GACf,MAAM,gBAAgB,CAAC;AAQxB,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIlG,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGrG,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAY9C,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAWlE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC","sourcesContent":["/**\n * `@variance-authority/core/format` — what a subject *is*, as a value.\n *\n * The shapes every other group reads and none of them may redefine: a raw\n * capture as a collector hands it over, the normalized snapshot it becomes, the\n * document a renderer paints, the identity that addresses the result, and the\n * hashing that makes any of it comparable.\n *\n * A consumer writing their own collector needs exactly this and nothing else.\n * That is the entrypoint's reason to exist: the format is a contract other people\n * implement, and implementing it should not require reading the rules that\n * interpret it or the verdicts drawn from it.\n */\n\n// compass: variance-authority.normalization\n\nexport type { CanonicalValue } from './canonical.js';\nexport { canonicalize, canonicalNumber } from './canonical.js';\n\nexport type { CapturedValue, ShapeValueOptions, ValueShaping } from './value.js';\nexport { shapeValue, pointerShape, pointerToken, DROPPED, VALUE_RECIPE } from './value.js';\n\nexport type { Digest } from './hash.js';\nexport { digestString, digestBytes, digestValue, digestCombine, digestOfSha256, fileNameFor, digestFileName, digestOfFileName } from './hash.js';\n\nexport type { AccessibilitySnapshot } from './accessibility.js';\nexport { accessibilitySnapshot } from './accessibility.js';\n\nexport type { ObservationProfile, ProfileId } from './profile.js';\nexport { JSDOM_PROFILE, CHROMIUM_PROFILE, profileById, tierOfProfile } from './profile.js';\n\nexport { assetUrl, pageOrigin, withoutOrigin } from './origin.js';\n\nexport type { Tier } from './tier.js';\nexport { tierReaches } from './tier.js';\n\nexport type {\n EnvironmentInputs,\n EnvironmentKey,\n EnvironmentDelta,\n EnvironmentField,\n PageRegion,\n Viewport,\n} from './environment.js';\nexport { environmentKey, diffEnvironments } from './environment.js';\n\nexport {\n INTERVENTIONS,\n SEMANTIC_RECIPE,\n COLLECT_RECIPE,\n DEFAULT_STABILIZE,\n LAYOUT_RECIPE,\n RASTER_RECIPE,\n holdAnimations,\n pinAnimations,\n hideCaret,\n hideScrollbars,\n hidePresentationalImages,\n waitForFonts,\n waitForImages,\n forTier,\n conflicts,\n interventionById,\n recipeOf,\n recipeToDrive,\n recipeCss,\n recipeScreenshot,\n recipeDigest,\n settleRecipe,\n describeRecipe,\n} from './stabilize.js';\nexport type {\n Intervention,\n Recipe,\n Trick,\n ScreenshotOptions,\n SettleTarget,\n} from './stabilize.js';\nexport { DEFAULT_NOW, driveBeforeLoad, driveBeforeRead, featuresOf, forDriver } from './drive.js';\nexport type { DriverState, DriverTarget, MotionPreference } from './intervention.js';\n\nexport type { Provenance, OwnerFrame, SourceLocation, StackFrame } from './provenance.js';\nexport { propsDigest, heldDigest, jsxSourceOf, relativizeSource, JSX_SOURCE } from './provenance.js';\nexport type { Holding, HeldCell, HeldValue } from './holding.js';\nexport type { Wiring } from './wiring.js';\nexport { keyedByPosition } from './wiring.js';\n\nexport type {\n RawCapture,\n RawNode,\n RawAria,\n MatchedRule,\n Declaration,\n Rect,\n SubjectRef,\n Diagnostic,\n} from './capture.js';\nexport { intersectionArea, mergeDiagnostics } from './capture.js';\n\nexport type {\n SemanticSnapshot,\n SemanticNode,\n NodePath,\n StyleProvenanceEntry,\n IgnoreSite,\n ComponentHash,\n} from './snapshot.js';\n\nexport { documentDigest, identityDigest, occupiesPixels, pictured, picture } from './document.js';\nexport type {\n RenderDocument,\n RenderFrame,\n FrameElement,\n RenderResource,\n RenderIdentity,\n Raster,\n Pictured,\n} from './document.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/format/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAG/D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAG3F,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGjJ,OAAO,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAG3D,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE3F,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGlE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAUxC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpE,OAAO,EACL,aAAa,EACb,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,cAAc,EACd,aAAa,EACb,SAAS,EACT,cAAc,EACd,wBAAwB,EACxB,YAAY,EACZ,aAAa,EACb,OAAO,EACP,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,aAAa,EACb,SAAS,EACT,gBAAgB,EAChB,YAAY,EACZ,YAAY,EACZ,cAAc,GACf,MAAM,gBAAgB,CAAC;AASxB,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIlG,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGrG,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAY9C,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAUlE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEhF,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAUlG,OAAO,EAAE,aAAa,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAEpE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC","sourcesContent":["/**\n * `@variance-authority/core/format` — what a subject *is*, as a value.\n *\n * The shapes every other group reads and none of them may redefine: a raw\n * capture as a collector hands it over, the normalized snapshot it becomes, the\n * document a renderer paints, the identity that addresses the result, and the\n * hashing that makes any of it comparable.\n *\n * A consumer writing their own collector needs exactly this and nothing else.\n * That is the entrypoint's reason to exist: the format is a contract other people\n * implement, and implementing it should not require reading the rules that\n * interpret it or the verdicts drawn from it.\n */\n\n// compass: variance-authority.normalization\n\nexport type { CanonicalValue } from './canonical.js';\nexport { canonicalize, canonicalNumber } from './canonical.js';\n\nexport type { CapturedValue, ShapeValueOptions, ValueShaping } from './value.js';\nexport { shapeValue, pointerShape, pointerToken, DROPPED, VALUE_RECIPE } from './value.js';\n\nexport type { Digest } from './hash.js';\nexport { digestString, digestBytes, digestValue, digestCombine, digestOfSha256, fileNameFor, digestFileName, digestOfFileName } from './hash.js';\n\nexport type { AccessibilitySnapshot } from './accessibility.js';\nexport { accessibilitySnapshot } from './accessibility.js';\n\nexport type { ObservationProfile, ProfileId } from './profile.js';\nexport { JSDOM_PROFILE, CHROMIUM_PROFILE, profileById, tierOfProfile } from './profile.js';\n\nexport { assetUrl, pageOrigin, withoutOrigin } from './origin.js';\n\nexport type { Tier } from './tier.js';\nexport { tierReaches } from './tier.js';\n\nexport type {\n EnvironmentInputs,\n EnvironmentKey,\n EnvironmentDelta,\n EnvironmentField,\n PageRegion,\n Viewport,\n} from './environment.js';\nexport { environmentKey, diffEnvironments } from './environment.js';\n\nexport {\n INTERVENTIONS,\n SEMANTIC_RECIPE,\n COLLECT_RECIPE,\n DEFAULT_STABILIZE,\n LAYOUT_RECIPE,\n RASTER_RECIPE,\n holdAnimations,\n pinAnimations,\n hideCaret,\n hideScrollbars,\n hidePresentationalImages,\n waitForFonts,\n waitForImages,\n forTier,\n conflicts,\n interventionById,\n recipeOf,\n recipeToDrive,\n recipeCss,\n recipeScreenshot,\n recipeDigest,\n settleRecipe,\n describeRecipe,\n} from './stabilize.js';\nexport type {\n Intervention,\n Recipe,\n Trick,\n ScreenshotOptions,\n SettleTarget,\n} from './stabilize.js';\nexport type { Step, StepClock, StepTotal } from './steps.js';\nexport { stepClock, timeOn, totalsOf } from './steps.js';\n\nexport { DEFAULT_NOW, driveBeforeLoad, driveBeforeRead, featuresOf, forDriver } from './drive.js';\nexport type { DriverState, DriverTarget, MotionPreference } from './intervention.js';\n\nexport type { Provenance, OwnerFrame, SourceLocation, StackFrame } from './provenance.js';\nexport { propsDigest, heldDigest, jsxSourceOf, relativizeSource, JSX_SOURCE } from './provenance.js';\nexport type { Holding, HeldCell, HeldValue } from './holding.js';\nexport type { Wiring } from './wiring.js';\nexport { keyedByPosition } from './wiring.js';\n\nexport type {\n RawCapture,\n RawNode,\n RawAria,\n MatchedRule,\n Declaration,\n Rect,\n SubjectRef,\n Diagnostic,\n} from './capture.js';\nexport { intersectionArea, mergeDiagnostics } from './capture.js';\n\nexport type {\n SemanticSnapshot,\n SemanticNode,\n NodePath,\n StyleProvenanceEntry,\n IgnoreSite,\n ComponentHash,\n} from './snapshot.js';\nexport { isRenderDocument, isSemanticSnapshot, isSubjectRef } from './shape.js';\n\nexport { documentDigest, identityDigest, occupiesPixels, pictured, picture } from './document.js';\nexport type {\n RenderDocument,\n RenderFrame,\n FrameElement,\n RenderResource,\n RenderIdentity,\n Raster,\n Pictured,\n} from './document.js';\nexport { keepsSnapshot, PACKED_SNAPSHOT_HEADER } from './packed.js';\nexport type { PackedSnapshot } from './packed.js';\nexport { atWidth, realizes, widenedFrom } from './width.js';\nexport type { Widenable } from './width.js';\n"]}
@@ -0,0 +1,32 @@
1
+ import type { SemanticSnapshot } from './snapshot.js';
2
+ /**
3
+ * A `SemanticSnapshot` as JSON, gzipped and in base64, after a header that
4
+ * names its format version.
5
+ *
6
+ * Text, so that it survives every hop a raster makes, as `Raster.bytes` does.
7
+ * `packSnapshot` and `unpackSnapshot` in `@variance-authority/observe` are the
8
+ * two ends; between them only {@link keepsSnapshot} reads it, and only its
9
+ * header.
10
+ */
11
+ export type PackedSnapshot = string;
12
+ /**
13
+ * The format version this code reads, typed from `SemanticSnapshot`, so that it
14
+ * stops compiling when the type moves. The header and `isSemanticSnapshot` both
15
+ * read it; `@variance-authority/core/format` does not export it.
16
+ */
17
+ export declare const FORMAT_VERSION: SemanticSnapshot['formatVersion'];
18
+ /** What a packed snapshot of the format this code reads opens with: its version, outside the gzip. */
19
+ export declare const PACKED_SNAPSHOT_HEADER: string;
20
+ /**
21
+ * Whether a raster kept a snapshot this code can read.
22
+ *
23
+ * Read off the header, without unpacking, so that every store gives the same
24
+ * answer, tribunal's inside a Worker with no `node:zlib` too. A snapshot of
25
+ * another format is one this code cannot read, and a baseline that keeps only
26
+ * that keeps none: `settle` renders it, for an accepting run to record one.
27
+ *
28
+ * The header cannot vouch for what follows it. Text damaged after the header
29
+ * is counted as kept here and reads as not recorded where it is unpacked.
30
+ */
31
+ export declare function keepsSnapshot(packed: PackedSnapshot | undefined): boolean;
32
+ //# sourceMappingURL=packed.d.ts.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The format version this code reads, typed from `SemanticSnapshot`, so that it
3
+ * stops compiling when the type moves. The header and `isSemanticSnapshot` both
4
+ * read it; `@variance-authority/core/format` does not export it.
5
+ */
6
+ export const FORMAT_VERSION = 1;
7
+ /** What a packed snapshot of the format this code reads opens with: its version, outside the gzip. */
8
+ export const PACKED_SNAPSHOT_HEADER = `v${FORMAT_VERSION}:`;
9
+ /**
10
+ * Whether a raster kept a snapshot this code can read.
11
+ *
12
+ * Read off the header, without unpacking, so that every store gives the same
13
+ * answer, tribunal's inside a Worker with no `node:zlib` too. A snapshot of
14
+ * another format is one this code cannot read, and a baseline that keeps only
15
+ * that keeps none: `settle` renders it, for an accepting run to record one.
16
+ *
17
+ * The header cannot vouch for what follows it. Text damaged after the header
18
+ * is counted as kept here and reads as not recorded where it is unpacked.
19
+ */
20
+ export function keepsSnapshot(packed) {
21
+ return packed !== undefined && packed.startsWith(PACKED_SNAPSHOT_HEADER);
22
+ }
23
+ //# sourceMappingURL=packed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packed.js","sourceRoot":"","sources":["../../src/format/packed.ts"],"names":[],"mappings":"AAaA;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAsC,CAAC,CAAC;AAEnE,sGAAsG;AACtG,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,cAAc,GAAG,CAAC;AAE5D;;;;;;;;;;GAUG;AACH,MAAM,UAAU,aAAa,CAAC,MAAkC;IAC9D,OAAO,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC;AAC3E,CAAC","sourcesContent":["import type { SemanticSnapshot } from './snapshot.js';\n\n/**\n * A `SemanticSnapshot` as JSON, gzipped and in base64, after a header that\n * names its format version.\n *\n * Text, so that it survives every hop a raster makes, as `Raster.bytes` does.\n * `packSnapshot` and `unpackSnapshot` in `@variance-authority/observe` are the\n * two ends; between them only {@link keepsSnapshot} reads it, and only its\n * header.\n */\nexport type PackedSnapshot = string;\n\n/**\n * The format version this code reads, typed from `SemanticSnapshot`, so that it\n * stops compiling when the type moves. The header and `isSemanticSnapshot` both\n * read it; `@variance-authority/core/format` does not export it.\n */\nexport const FORMAT_VERSION: SemanticSnapshot['formatVersion'] = 1;\n\n/** What a packed snapshot of the format this code reads opens with: its version, outside the gzip. */\nexport const PACKED_SNAPSHOT_HEADER = `v${FORMAT_VERSION}:`;\n\n/**\n * Whether a raster kept a snapshot this code can read.\n *\n * Read off the header, without unpacking, so that every store gives the same\n * answer, tribunal's inside a Worker with no `node:zlib` too. A snapshot of\n * another format is one this code cannot read, and a baseline that keeps only\n * that keeps none: `settle` renders it, for an accepting run to record one.\n *\n * The header cannot vouch for what follows it. Text damaged after the header\n * is counted as kept here and reads as not recorded where it is unpacked.\n */\nexport function keepsSnapshot(packed: PackedSnapshot | undefined): boolean {\n return packed !== undefined && packed.startsWith(PACKED_SNAPSHOT_HEADER);\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import type { SubjectRef } from './capture.js';
2
+ import type { RenderDocument } from './document.js';
3
+ import type { SemanticSnapshot } from './snapshot.js';
4
+ /**
5
+ * Whether a value read back from a file or a store is the format it claims.
6
+ *
7
+ * Read, not believed: a value parsed from JSON is `unknown` until one of these
8
+ * says otherwise, and each checks every field its type declares, so that there
9
+ * is one answer to what a snapshot, a document or a subject is. What a reader
10
+ * can re-derive, a digest against the bytes it names, is the reader's to check.
11
+ */
12
+ /**
13
+ * Whether a value is a `SemanticSnapshot`, every node down to the leaves.
14
+ *
15
+ * The readers walk all of it: the matcher pairs every child, and a node that is
16
+ * not one throws there instead of reading as a snapshot that is not recorded.
17
+ * A capture file and a packed snapshot are the two places one is read back.
18
+ * Whose snapshot it is, against the subject it was filed under, is the
19
+ * reader's question.
20
+ */
21
+ export declare function isSemanticSnapshot(value: unknown): value is SemanticSnapshot;
22
+ /**
23
+ * Whether a value is a `RenderDocument`, its resources' shape included.
24
+ *
25
+ * Shape only: that a resource's bytes are base64 and hash to its digest, and
26
+ * that a document is resource-closed, are what a reader that needs them checks.
27
+ */
28
+ export declare function isRenderDocument(value: unknown): value is RenderDocument;
29
+ /** Whether a value is a `SubjectRef`: an id, one of the four kinds, and an optional title. */
30
+ export declare function isSubjectRef(value: unknown): value is SubjectRef;
31
+ //# sourceMappingURL=shape.d.ts.map