@variance-authority/presentation 0.1.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.
- package/CHANGELOG.md +32 -0
- package/LICENSE +21 -0
- package/README.md +319 -0
- package/dist/alignment.d.ts +9 -0
- package/dist/alignment.js +100 -0
- package/dist/alignment.js.map +1 -0
- package/dist/analyze.d.ts +20 -0
- package/dist/analyze.js +132 -0
- package/dist/analyze.js.map +1 -0
- package/dist/browser-agent-entry.d.ts +2 -0
- package/dist/browser-agent-entry.js +8 -0
- package/dist/browser-agent-entry.js.map +1 -0
- package/dist/browser-agent.bundle.js +3587 -0
- package/dist/browser-agent.d.ts +37 -0
- package/dist/browser-agent.js +130 -0
- package/dist/browser-agent.js.map +1 -0
- package/dist/bundle.d.ts +3 -0
- package/dist/bundle.js +20 -0
- package/dist/bundle.js.map +1 -0
- package/dist/compare.d.ts +4 -0
- package/dist/compare.js +49 -0
- package/dist/compare.js.map +1 -0
- package/dist/findings.d.ts +5 -0
- package/dist/findings.js +285 -0
- package/dist/findings.js.map +1 -0
- package/dist/focus.d.ts +17 -0
- package/dist/focus.js +84 -0
- package/dist/focus.js.map +1 -0
- package/dist/graph.d.ts +11 -0
- package/dist/graph.js +145 -0
- package/dist/graph.js.map +1 -0
- package/dist/hierarchy.d.ts +4 -0
- package/dist/hierarchy.js +171 -0
- package/dist/hierarchy.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/math.d.ts +24 -0
- package/dist/math.js +124 -0
- package/dist/math.js.map +1 -0
- package/dist/measure.d.ts +15 -0
- package/dist/measure.js +216 -0
- package/dist/measure.js.map +1 -0
- package/dist/model.d.ts +346 -0
- package/dist/model.js +2 -0
- package/dist/model.js.map +1 -0
- package/dist/paint.d.ts +12 -0
- package/dist/paint.js +192 -0
- package/dist/paint.js.map +1 -0
- package/dist/patterns.d.ts +10 -0
- package/dist/patterns.js +148 -0
- package/dist/patterns.js.map +1 -0
- package/dist/playwright.d.ts +27 -0
- package/dist/playwright.js +108 -0
- package/dist/playwright.js.map +1 -0
- package/dist/report.d.ts +17 -0
- package/dist/report.js +126 -0
- package/dist/report.js.map +1 -0
- package/dist/spacing.d.ts +4 -0
- package/dist/spacing.js +112 -0
- package/dist/spacing.js.map +1 -0
- package/dist/telemetry.d.ts +4 -0
- package/dist/telemetry.js +73 -0
- package/dist/telemetry.js.map +1 -0
- package/mark.svg +30 -0
- package/package.json +62 -0
- package/skills/variance-presentation/SKILL.md +463 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findings.js","sourceRoot":"","sources":["../src/findings.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BuiltGraph } from './graph.js';\nimport type { Measurements } from './measure.js';\nimport type {\n PresentationFinding,\n PresentationNode,\n RepeatedPattern,\n} from './model.js';\nimport { codeUnitCompare, median, round } from './math.js';\n\ntype DetectedFinding = Omit<PresentationFinding, 'id'>;\n\n/** Thresholds are pinned by the acceptance fixtures, never user-facing targets. */\nconst CALIBRATION = {\n relationshipRatio: 1.15,\n weakBoundary: 0.28,\n surfaceDelta: 8,\n axisDeviationPx: 2,\n baselineDeviationPx: 2,\n} as const;\n\nexport function detectFindings(\n graph: BuiltGraph,\n measured: Measurements,\n patterns: readonly RepeatedPattern[],\n): PresentationFinding[] {\n const findings: DetectedFinding[] = [];\n for (const pattern of patterns) {\n findings.push(...relationshipFindings(pattern, measured));\n findings.push(...hierarchySpacingFindings(pattern, measured));\n findings.push(...slotFindings(pattern, graph));\n const unexplained = pattern.outliers.filter((outlier) => !outlier.explainedByState);\n if (\n pattern.instances.length >= 3 &&\n pattern.dominantInstances.length * 2 >= pattern.instances.length &&\n unexplained.length > 0\n ) {\n findings.push({\n rule: 'PRESENTATION_GRAMMAR_DRIFT',\n owner: pattern.parent,\n nodes: unexplained.map((outlier) => outlier.node),\n pattern: pattern.id,\n measurements: {\n dominantInstances: pattern.dominantInstances.length,\n peerInstances: pattern.instances.length,\n maxDeviation: round(Math.max(...unexplained.map((outlier) => outlier.deviation))),\n },\n });\n }\n }\n findings.push(...surfaceFindings(measured.nodes));\n findings.push(...prominenceFindings(measured));\n return findings.sort((left, right) =>\n codeUnitCompare(left.rule, right.rule) ||\n codeUnitCompare(left.owner, right.owner) ||\n codeUnitCompare(left.nodes.join('\\0'), right.nodes.join('\\0')) ||\n codeUnitCompare(left.pattern ?? '', right.pattern ?? ''),\n ).map((finding, index) => ({ id: `F${index + 1}`, ...finding }));\n}\n\n/** A leading structural label and the body peers it introduces need distinct spacing relations. */\nfunction hierarchySpacingFindings(\n pattern: RepeatedPattern,\n measured: Measurements,\n): DetectedFinding[] {\n const byId = new Map(measured.nodes.map((node) => [node.id, node]));\n const separations = new Map(measured.relations\n .filter((relation) => relation.kind === 'separates')\n .map((relation) => [`${relation.from}\\0${relation.to}`, relation]));\n const collisions = pattern.instances.flatMap((instanceId) => {\n const instance = byId.get(instanceId);\n if (instance === undefined || instance.children.length < 3) return [];\n const leader = byId.get(instance.children[0]!);\n const body = instance.children.slice(1).map((id) => byId.get(id));\n if (leader === undefined || body.some((node) => node === undefined)) return [];\n const bodyNodes = body as PresentationNode[];\n if (\n bodyNodes.length < 2 ||\n bodyNodes.some((node) => node.semanticClass !== bodyNodes[0]!.semanticClass) ||\n leader.semanticClass === bodyNodes[0]!.semanticClass\n ) return [];\n const leading = separations.get(`${leader.id}\\0${bodyNodes[0]!.id}`);\n const peer = bodyNodes.slice(0, -1).flatMap((node, index) =>\n separations.get(`${node.id}\\0${bodyNodes[index + 1]!.id}`) ?? []);\n const leadingGap = leading?.distancePx;\n const peerGap = median(peer.flatMap((relation) => relation.distancePx ?? []));\n if (leadingGap === undefined || peerGap === undefined) return [];\n const leadingClusters = new Set(leading?.spacingCluster === undefined ? [] : [leading.spacingCluster]);\n const peerClusters = new Set(peer.flatMap((relation) => relation.spacingCluster ?? []));\n const sharedCluster = [...leadingClusters].some((cluster) => peerClusters.has(cluster));\n const smaller = Math.min(leadingGap, peerGap);\n const larger = Math.max(leadingGap, peerGap);\n const ratio = smaller === 0 ? (larger === 0 ? 1 : Number.POSITIVE_INFINITY) : larger / smaller;\n if (!sharedCluster && ratio > CALIBRATION.relationshipRatio) return [];\n return [{\n instance: instance.id,\n leader: leader.id,\n body: bodyNodes.map((node) => node.id),\n leadingGap,\n peerGap,\n ratio,\n sharedCluster,\n }];\n });\n if (collisions.length < 2) return [];\n return [{\n rule: 'SPACING_HIERARCHY_COLLISION',\n owner: pattern.parent,\n nodes: collisions.map((collision) => collision.instance),\n pattern: pattern.id,\n measurements: {\n instances: collisions.length,\n leadingToBodyGapMedianPx: round(median(collisions.map((collision) => collision.leadingGap))!, 2),\n bodyToBodyGapMedianPx: round(median(collisions.map((collision) => collision.peerGap))!, 2),\n ratio: round(median(collisions.map((collision) => collision.ratio))!),\n sharedSpacingClusterInstances: collisions.filter((collision) => collision.sharedCluster).length,\n },\n }];\n}\n\nfunction relationshipFindings(\n pattern: RepeatedPattern,\n measured: Measurements,\n): DetectedFinding[] {\n const members = new Set(pattern.instances);\n const between = measured.relations.filter(\n (relation) => relation.kind === 'separates' && members.has(relation.from) && members.has(relation.to),\n );\n const within = pattern.instances.flatMap((instance) => {\n const node = measured.nodes.find((candidate) => candidate.id === instance);\n if (node === undefined) return [];\n const children = new Set(node.children);\n return measured.relations.filter(\n (relation) => relation.kind === 'separates' && children.has(relation.from) && children.has(relation.to),\n );\n });\n const betweenBoundary = median(between.flatMap((relation) => relation.boundaryStrength ?? []));\n const withinBoundary = median(within.flatMap((relation) => relation.boundaryStrength ?? []));\n const betweenGap = median(between.flatMap((relation) => relation.distancePx ?? []));\n const withinGap = median(within.flatMap((relation) => relation.distancePx ?? []));\n const results: DetectedFinding[] = [];\n if (betweenBoundary !== undefined && withinBoundary !== undefined) {\n const ratio = withinBoundary === 0 ? (betweenBoundary === 0 ? 1 : Number.POSITIVE_INFINITY) : betweenBoundary / withinBoundary;\n if (ratio <= CALIBRATION.relationshipRatio) {\n results.push({\n rule: 'SEPARATION_COLLISION',\n owner: pattern.parent,\n nodes: pattern.instances,\n pattern: pattern.id,\n measurements: {\n withinBoundaryMedian: round(withinBoundary),\n betweenBoundaryMedian: round(betweenBoundary),\n ratio: round(ratio),\n },\n });\n }\n if (pattern.instances.length >= 3 && betweenBoundary <= CALIBRATION.weakBoundary) {\n results.push({\n rule: 'REPETITION_GRAMMAR_COLLAPSE',\n owner: pattern.parent,\n nodes: pattern.instances,\n pattern: pattern.id,\n measurements: {\n instances: pattern.instances.length,\n betweenBoundaryMedian: round(betweenBoundary),\n presentationSimilarity: pattern.presentationSimilarity,\n },\n });\n }\n }\n if (betweenGap !== undefined && withinGap !== undefined) {\n const ratio = withinGap === 0 ? (betweenGap === 0 ? 1 : Number.POSITIVE_INFINITY) : betweenGap / withinGap;\n const betweenClusters = new Set(between.flatMap((relation) => relation.spacingCluster ?? []));\n const withinClusters = new Set(within.flatMap((relation) => relation.spacingCluster ?? []));\n const overlap = [...betweenClusters].some((cluster) => withinClusters.has(cluster));\n if (overlap || ratio <= CALIBRATION.relationshipRatio) {\n results.push({\n rule: 'SPACING_RELATION_COLLISION',\n owner: pattern.parent,\n nodes: pattern.instances,\n pattern: pattern.id,\n measurements: {\n withinGapMedianPx: round(withinGap, 2),\n betweenGapMedianPx: round(betweenGap, 2),\n ratio: round(ratio),\n },\n });\n }\n }\n return results;\n}\n\nfunction slotFindings(pattern: RepeatedPattern, graph: BuiltGraph): DetectedFinding[] {\n if (pattern.instances.length < 4) return [];\n const slots = new Map<string, Array<{ readonly node: PresentationNode; readonly instance: PresentationNode }>>();\n for (const instanceId of pattern.instances) {\n const instance = graph.byId.get(instanceId);\n if (instance === undefined) continue;\n const occurrence = new Map<string, number>();\n for (const childId of instance.children) {\n const child = graph.byId.get(childId);\n if (child === undefined) continue;\n const count = occurrence.get(child.semanticClass) ?? 0;\n occurrence.set(child.semanticClass, count + 1);\n const key = `${child.semanticClass}:${count}`;\n const members = slots.get(key) ?? [];\n members.push({ node: child, instance });\n slots.set(key, members);\n }\n }\n const findings: DetectedFinding[] = [];\n for (const [slot, members] of slots) {\n if (members.length !== pattern.instances.length) continue;\n const left = median(members.map((member) => member.node.rect.x))!;\n const aligned = members.filter((member) => Math.abs(member.node.rect.x - left) <= 1);\n const alignmentOutliers = members.filter(\n (member) => Math.abs(member.node.rect.x - left) > CALIBRATION.axisDeviationPx,\n );\n if (aligned.length * 3 >= members.length * 2 && alignmentOutliers.length > 0) {\n findings.push({\n rule: 'ALIGNMENT_OUTLIER',\n owner: pattern.parent,\n nodes: alignmentOutliers.map((member) => member.node.id),\n pattern: pattern.id,\n measurements: {\n slot,\n dominantLeftPx: round(left, 2),\n maxDeviationPx: round(Math.max(...alignmentOutliers.map((member) => Math.abs(member.node.rect.x - left))), 2),\n },\n });\n }\n\n const baselines = members.flatMap(({ node, instance }) => {\n const size = node.typography.sizePx;\n const lineHeight = node.typography.lineHeightPx;\n if (size === undefined || lineHeight === undefined || (node.text === undefined && node.name === undefined)) return [];\n return [{\n node,\n coordinate:\n node.rect.y - instance.rect.y + Math.max(0, (node.rect.height - lineHeight) / 2) + size * 0.8,\n }];\n });\n if (baselines.length !== members.length) continue;\n const baseline = median(baselines.map((candidate) => candidate.coordinate))!;\n const baselineAligned = baselines.filter((candidate) => Math.abs(candidate.coordinate - baseline) <= 1);\n const baselineOutliers = baselines.filter(\n (candidate) => Math.abs(candidate.coordinate - baseline) > CALIBRATION.baselineDeviationPx,\n );\n if (baselineAligned.length * 3 >= baselines.length * 2 && baselineOutliers.length > 0) {\n findings.push({\n rule: 'BASELINE_DRIFT',\n owner: pattern.parent,\n nodes: baselineOutliers.map((candidate) => candidate.node.id),\n pattern: pattern.id,\n measurements: {\n slot,\n dominantBaselinePx: round(baseline, 2),\n maxDeviationPx: round(Math.max(...baselineOutliers.map((candidate) => Math.abs(candidate.coordinate - baseline))), 2),\n confidence: 'inferred',\n },\n });\n }\n }\n return findings;\n}\n\nfunction surfaceFindings(nodes: readonly PresentationNode[]): DetectedFinding[] {\n const surfaceTags = new Set(['button', 'input', 'select', 'textarea', 'article', 'section', 'aside', 'dialog']);\n return nodes.flatMap((node) => {\n const difference = node.surface.perceptualDifference;\n const meaningful = node.role !== undefined || surfaceTags.has(node.tag);\n if (\n node.parent === undefined ||\n !meaningful ||\n node.surface.fill === undefined ||\n difference === undefined ||\n difference >= CALIBRATION.surfaceDelta ||\n node.surface.borderWidthPx > 0 ||\n node.surface.shadow\n ) return [];\n return [{\n rule: 'SURFACE_COLLISION' as const,\n owner: node.parent,\n nodes: [node.id],\n measurements: {\n perceptualDifference: difference,\n borderWidthPx: node.surface.borderWidthPx,\n shadow: node.surface.shadow ? 1 : 0,\n },\n }];\n });\n}\n\nfunction prominenceFindings(measured: Measurements): DetectedFinding[] {\n const byId = new Map(measured.nodes.map((node) => [node.id, node]));\n return measured.nodes.flatMap((owner) => measured.prominence.flatMap((cluster) => {\n const members = owner.children.flatMap((id) => {\n const node = byId.get(id);\n return node !== undefined && node.prominence.cluster === cluster.id ? [node] : [];\n });\n const headings = members.filter((node) => node.semanticClass.startsWith('heading'));\n const ordinary = members.filter((node) =>\n ['label', 'paragraph', 'text', 'generic', 'span'].includes(node.semanticClass),\n );\n if (headings.length === 0 || ordinary.length === 0) return [];\n return [{\n rule: 'PROMINENCE_COLLAPSE' as const,\n owner: owner.id,\n nodes: [...headings, ...ordinary].map((node) => node.id),\n measurements: {\n prominenceCluster: cluster.id,\n semanticClasses: [...new Set([...headings, ...ordinary].map((node) => node.semanticClass))].join(','),\n magnitude: cluster.magnitude,\n },\n }];\n }));\n}\n"]}
|
package/dist/focus.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PaintLayer, PresentationFocus, PresentationFocusDepth, PresentationReport } from './model.js';
|
|
2
|
+
export interface FocusPresentationOptions {
|
|
3
|
+
/** `owner` keeps nested boxes separate; `subtree` reads the composition holistically. */
|
|
4
|
+
readonly depth?: PresentationFocusDepth;
|
|
5
|
+
/** Omit to retain every paint layer owned by the selected structural depth. */
|
|
6
|
+
readonly paint?: readonly PaintLayer[];
|
|
7
|
+
/** Omit to read every owned finding; provide ids to isolate one or more questions. */
|
|
8
|
+
readonly findings?: readonly string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Read one structural level from an existing report without acquiring the page again.
|
|
12
|
+
*
|
|
13
|
+
* An owner focus contains only relationships owned by that node. Nested evidence is
|
|
14
|
+
* counted but not folded into the result. A subtree focus deliberately includes it.
|
|
15
|
+
*/
|
|
16
|
+
export declare function focusPresentation(report: PresentationReport, ownerId: string, options?: FocusPresentationOptions): PresentationFocus;
|
|
17
|
+
//# sourceMappingURL=focus.d.ts.map
|
package/dist/focus.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read one structural level from an existing report without acquiring the page again.
|
|
3
|
+
*
|
|
4
|
+
* An owner focus contains only relationships owned by that node. Nested evidence is
|
|
5
|
+
* counted but not folded into the result. A subtree focus deliberately includes it.
|
|
6
|
+
*/
|
|
7
|
+
export function focusPresentation(report, ownerId, options = {}) {
|
|
8
|
+
const byId = new Map(report.graph.nodes.map((node) => [node.id, node]));
|
|
9
|
+
const owner = byId.get(ownerId);
|
|
10
|
+
if (owner === undefined)
|
|
11
|
+
throw new Error(`presentation owner ${ownerId} is not in report ${report.digest}`);
|
|
12
|
+
const depth = options.depth ?? 'owner';
|
|
13
|
+
const descendants = descendantIds(owner, byId);
|
|
14
|
+
const selectedOwners = depth === 'subtree' ? new Set([ownerId, ...descendants]) : new Set([ownerId]);
|
|
15
|
+
const ownedFindings = (report.findings ?? []).filter((finding) => selectedOwners.has(finding.owner));
|
|
16
|
+
const requestedFindings = options.findings === undefined ? undefined : new Set(options.findings);
|
|
17
|
+
if (requestedFindings !== undefined) {
|
|
18
|
+
for (const id of requestedFindings) {
|
|
19
|
+
if (!ownedFindings.some((finding) => finding.id === id)) {
|
|
20
|
+
throw new Error(`presentation finding ${id} is not owned by ${ownerId} at ${depth} depth`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const findings = requestedFindings === undefined
|
|
25
|
+
? ownedFindings
|
|
26
|
+
: ownedFindings.filter((finding) => requestedFindings.has(finding.id));
|
|
27
|
+
const selectedPatterns = requestedFindings === undefined
|
|
28
|
+
? undefined
|
|
29
|
+
: new Set(findings.flatMap((finding) => finding.pattern ?? []));
|
|
30
|
+
const patterns = (report.patterns ?? []).filter((pattern) => selectedOwners.has(pattern.parent) && (selectedPatterns?.has(pattern.id) ?? true));
|
|
31
|
+
const selectedNodeIds = depth === 'subtree'
|
|
32
|
+
? [ownerId, ...descendants]
|
|
33
|
+
: [
|
|
34
|
+
ownerId,
|
|
35
|
+
...owner.children,
|
|
36
|
+
...findings.flatMap((finding) => finding.nodes),
|
|
37
|
+
...patterns.flatMap((pattern) => pattern.instances),
|
|
38
|
+
];
|
|
39
|
+
const selectedNodes = [...new Set(selectedNodeIds)].flatMap((id) => byId.get(id) ?? []);
|
|
40
|
+
const layers = options.paint === undefined ? undefined : new Set(options.paint);
|
|
41
|
+
const relevantNodes = new Set([
|
|
42
|
+
...findings.flatMap((finding) => finding.nodes),
|
|
43
|
+
...patterns.flatMap((pattern) => pattern.instances),
|
|
44
|
+
]);
|
|
45
|
+
const paint = (report.paint ?? []).filter((instruction) => selectedOwners.has(instruction.owner) &&
|
|
46
|
+
(layers?.has(instruction.layer) ?? true) &&
|
|
47
|
+
(requestedFindings === undefined || selectedPaint(instruction, requestedFindings, selectedPatterns, relevantNodes)));
|
|
48
|
+
const nestedOwners = new Set(descendants);
|
|
49
|
+
const nestedPatterns = (report.patterns ?? []).filter((pattern) => nestedOwners.has(pattern.parent));
|
|
50
|
+
const nestedFindings = (report.findings ?? []).filter((finding) => nestedOwners.has(finding.owner));
|
|
51
|
+
return {
|
|
52
|
+
formatVersion: 1,
|
|
53
|
+
report: report.digest,
|
|
54
|
+
depth,
|
|
55
|
+
owner,
|
|
56
|
+
nodes: selectedNodes,
|
|
57
|
+
patterns,
|
|
58
|
+
findings,
|
|
59
|
+
paint,
|
|
60
|
+
nested: {
|
|
61
|
+
owners: new Set([...nestedPatterns.map((pattern) => pattern.parent), ...nestedFindings.map((finding) => finding.owner)]).size,
|
|
62
|
+
patterns: nestedPatterns.length,
|
|
63
|
+
findings: nestedFindings.length,
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function selectedPaint(instruction, findings, patterns, nodes) {
|
|
68
|
+
if (instruction.finding !== undefined)
|
|
69
|
+
return findings.has(instruction.finding);
|
|
70
|
+
if (instruction.pattern !== undefined)
|
|
71
|
+
return patterns.has(instruction.pattern);
|
|
72
|
+
return instruction.nodes.some((node) => nodes.has(node));
|
|
73
|
+
}
|
|
74
|
+
function descendantIds(owner, byId) {
|
|
75
|
+
const result = [];
|
|
76
|
+
for (const id of owner.children) {
|
|
77
|
+
const child = byId.get(id);
|
|
78
|
+
if (child === undefined)
|
|
79
|
+
continue;
|
|
80
|
+
result.push(id, ...descendantIds(child, byId));
|
|
81
|
+
}
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=focus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus.js","sourceRoot":"","sources":["../src/focus.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n PaintInstruction,\n PaintLayer,\n PresentationFocus,\n PresentationFocusDepth,\n PresentationNode,\n PresentationReport,\n} from './model.js';\n\nexport interface FocusPresentationOptions {\n /** `owner` keeps nested boxes separate; `subtree` reads the composition holistically. */\n readonly depth?: PresentationFocusDepth;\n /** Omit to retain every paint layer owned by the selected structural depth. */\n readonly paint?: readonly PaintLayer[];\n /** Omit to read every owned finding; provide ids to isolate one or more questions. */\n readonly findings?: readonly string[];\n}\n\n/**\n * Read one structural level from an existing report without acquiring the page again.\n *\n * An owner focus contains only relationships owned by that node. Nested evidence is\n * counted but not folded into the result. A subtree focus deliberately includes it.\n */\nexport function focusPresentation(\n report: PresentationReport,\n ownerId: string,\n options: FocusPresentationOptions = {},\n): PresentationFocus {\n const byId = new Map(report.graph.nodes.map((node) => [node.id, node]));\n const owner = byId.get(ownerId);\n if (owner === undefined) throw new Error(`presentation owner ${ownerId} is not in report ${report.digest}`);\n const depth = options.depth ?? 'owner';\n const descendants = descendantIds(owner, byId);\n const selectedOwners = depth === 'subtree' ? new Set([ownerId, ...descendants]) : new Set([ownerId]);\n const ownedFindings = (report.findings ?? []).filter((finding) => selectedOwners.has(finding.owner));\n const requestedFindings = options.findings === undefined ? undefined : new Set(options.findings);\n if (requestedFindings !== undefined) {\n for (const id of requestedFindings) {\n if (!ownedFindings.some((finding) => finding.id === id)) {\n throw new Error(`presentation finding ${id} is not owned by ${ownerId} at ${depth} depth`);\n }\n }\n }\n const findings = requestedFindings === undefined\n ? ownedFindings\n : ownedFindings.filter((finding) => requestedFindings.has(finding.id));\n const selectedPatterns = requestedFindings === undefined\n ? undefined\n : new Set(findings.flatMap((finding) => finding.pattern ?? []));\n const patterns = (report.patterns ?? []).filter(\n (pattern) => selectedOwners.has(pattern.parent) && (selectedPatterns?.has(pattern.id) ?? true),\n );\n const selectedNodeIds = depth === 'subtree'\n ? [ownerId, ...descendants]\n : [\n ownerId,\n ...owner.children,\n ...findings.flatMap((finding) => finding.nodes),\n ...patterns.flatMap((pattern) => pattern.instances),\n ];\n const selectedNodes = [...new Set(selectedNodeIds)].flatMap((id) => byId.get(id) ?? []);\n const layers = options.paint === undefined ? undefined : new Set(options.paint);\n const relevantNodes = new Set([\n ...findings.flatMap((finding) => finding.nodes),\n ...patterns.flatMap((pattern) => pattern.instances),\n ]);\n const paint = (report.paint ?? []).filter(\n (instruction) =>\n selectedOwners.has(instruction.owner) &&\n (layers?.has(instruction.layer) ?? true) &&\n (requestedFindings === undefined || selectedPaint(instruction, requestedFindings, selectedPatterns!, relevantNodes)),\n );\n const nestedOwners = new Set(descendants);\n const nestedPatterns = (report.patterns ?? []).filter((pattern) => nestedOwners.has(pattern.parent));\n const nestedFindings = (report.findings ?? []).filter((finding) => nestedOwners.has(finding.owner));\n return {\n formatVersion: 1,\n report: report.digest,\n depth,\n owner,\n nodes: selectedNodes,\n patterns,\n findings,\n paint,\n nested: {\n owners: new Set([...nestedPatterns.map((pattern) => pattern.parent), ...nestedFindings.map((finding) => finding.owner)]).size,\n patterns: nestedPatterns.length,\n findings: nestedFindings.length,\n },\n };\n}\n\nfunction selectedPaint(\n instruction: PaintInstruction,\n findings: ReadonlySet<string>,\n patterns: ReadonlySet<string>,\n nodes: ReadonlySet<string>,\n): boolean {\n if (instruction.finding !== undefined) return findings.has(instruction.finding);\n if (instruction.pattern !== undefined) return patterns.has(instruction.pattern);\n return instruction.nodes.some((node) => nodes.has(node));\n}\n\nfunction descendantIds(owner: PresentationNode, byId: ReadonlyMap<string, PresentationNode>): string[] {\n const result: string[] = [];\n for (const id of owner.children) {\n const child = byId.get(id);\n if (child === undefined) continue;\n result.push(id, ...descendantIds(child, byId));\n }\n return result;\n}\n"]}
|
package/dist/graph.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RawCapture, RawNode, Rect } from '@variance-authority/core';
|
|
2
|
+
import type { PresentationNode, PresentationRelation } from './model.js';
|
|
3
|
+
export interface BuiltGraph {
|
|
4
|
+
readonly nodes: PresentationNode[];
|
|
5
|
+
readonly relations: PresentationRelation[];
|
|
6
|
+
readonly byId: Map<string, PresentationNode>;
|
|
7
|
+
readonly rawById: Map<string, RawNode>;
|
|
8
|
+
}
|
|
9
|
+
export declare function buildGraph(capture: RawCapture): BuiltGraph;
|
|
10
|
+
export declare function center(rect: Rect): readonly [number, number];
|
|
11
|
+
//# sourceMappingURL=graph.d.ts.map
|
package/dist/graph.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { colorOf, luminance, numeric, opaque, perceptualDifference, round, stableEntries, } from './math.js';
|
|
2
|
+
export function buildGraph(capture) {
|
|
3
|
+
const nodes = [];
|
|
4
|
+
const relations = [];
|
|
5
|
+
const byId = new Map();
|
|
6
|
+
const rawById = new Map();
|
|
7
|
+
const roots = [capture.root, ...(capture.portals ?? [])];
|
|
8
|
+
roots.forEach((root, rootIndex) => walk(root, rootIndex, '0', undefined, '#ffffff'));
|
|
9
|
+
return { nodes, relations, byId, rawById };
|
|
10
|
+
function walk(raw, root, path, parent, containingFill) {
|
|
11
|
+
if (raw.tag === '#text')
|
|
12
|
+
return undefined;
|
|
13
|
+
const rect = raw.rect;
|
|
14
|
+
const style = raw.computedStyle ?? raw.inlineStyle ?? {};
|
|
15
|
+
const fill = opaque(style['background-color']);
|
|
16
|
+
const effectiveFill = fill ?? containingFill;
|
|
17
|
+
const id = `r${root}:${path}`;
|
|
18
|
+
const childIds = [];
|
|
19
|
+
raw.children.forEach((child, index) => {
|
|
20
|
+
const childId = walk(child, root, `${path}/${index}`, id, effectiveFill);
|
|
21
|
+
if (childId !== undefined)
|
|
22
|
+
childIds.push(childId);
|
|
23
|
+
});
|
|
24
|
+
raw.shadowChildren?.forEach((child, index) => {
|
|
25
|
+
const childId = walk(child, root, `${path}/shadow:${index}`, id, effectiveFill);
|
|
26
|
+
if (childId !== undefined)
|
|
27
|
+
childIds.push(childId);
|
|
28
|
+
});
|
|
29
|
+
if (rect === undefined || rect.width <= 0 || rect.height <= 0)
|
|
30
|
+
return undefined;
|
|
31
|
+
const typography = typographyOf(style);
|
|
32
|
+
const surface = surfaceOf(style, fill, containingFill);
|
|
33
|
+
const text = textOf(raw);
|
|
34
|
+
const semanticClass = semanticClassOf(raw);
|
|
35
|
+
const state = raw.aria?.state ?? {};
|
|
36
|
+
const area = Math.max(1, rect.width * rect.height);
|
|
37
|
+
const magnitude = prominenceMagnitude(typography, surface, area);
|
|
38
|
+
const node = {
|
|
39
|
+
id,
|
|
40
|
+
ref: { root, path },
|
|
41
|
+
...(parent === undefined ? {} : { parent }),
|
|
42
|
+
children: childIds,
|
|
43
|
+
tag: raw.tag,
|
|
44
|
+
semanticClass,
|
|
45
|
+
...(raw.aria?.role ? { role: raw.aria.role } : {}),
|
|
46
|
+
...(raw.aria?.name ? { name: raw.aria.name } : {}),
|
|
47
|
+
...(text.length > 0 ? { text } : {}),
|
|
48
|
+
state,
|
|
49
|
+
stateSignature: stableEntries(state),
|
|
50
|
+
rect,
|
|
51
|
+
typography,
|
|
52
|
+
surface,
|
|
53
|
+
prominence: { magnitude },
|
|
54
|
+
};
|
|
55
|
+
nodes.push(node);
|
|
56
|
+
byId.set(id, node);
|
|
57
|
+
rawById.set(id, raw);
|
|
58
|
+
if (parent !== undefined) {
|
|
59
|
+
relations.push({ id: `contains:${parent}:${id}`, kind: 'contains', from: parent, to: id });
|
|
60
|
+
}
|
|
61
|
+
return id;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function typographyOf(style) {
|
|
65
|
+
const sizePx = numeric(style['font-size']);
|
|
66
|
+
const lineHeight = numeric(style['line-height']);
|
|
67
|
+
const weightText = style['font-weight'];
|
|
68
|
+
const namedWeight = weightText === 'bold' ? 700 : weightText === 'normal' ? 400 : undefined;
|
|
69
|
+
const weight = namedWeight ?? numeric(weightText);
|
|
70
|
+
return {
|
|
71
|
+
...(style['font-family'] === undefined ? {} : { family: style['font-family'] }),
|
|
72
|
+
...(sizePx === undefined ? {} : { sizePx }),
|
|
73
|
+
...(weight === undefined ? {} : { weight }),
|
|
74
|
+
...(style['font-style'] === undefined ? {} : { style: style['font-style'] }),
|
|
75
|
+
...(sizePx === undefined ? {} : { lineHeightPx: lineHeight ?? round(sizePx * 1.2, 2) }),
|
|
76
|
+
...(style.color === undefined ? {} : { foreground: style.color }),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function surfaceOf(style, fill, containingFill) {
|
|
80
|
+
const borderWidthPx = Math.max(...['top', 'right', 'bottom', 'left'].map((side) => {
|
|
81
|
+
const width = numeric(style[`border-${side}-width`]) ?? 0;
|
|
82
|
+
const kind = style[`border-${side}-style`];
|
|
83
|
+
const color = colorOf(style[`border-${side}-color`]);
|
|
84
|
+
return kind !== undefined && kind !== 'none' && color !== undefined && color.alpha > 0.01
|
|
85
|
+
? width
|
|
86
|
+
: 0;
|
|
87
|
+
}));
|
|
88
|
+
const shadow = style['box-shadow'] !== undefined && style['box-shadow'] !== 'none';
|
|
89
|
+
const difference = fill === undefined ? undefined : perceptualDifference(fill, containingFill);
|
|
90
|
+
return {
|
|
91
|
+
...(fill === undefined ? {} : { fill }),
|
|
92
|
+
containingFill,
|
|
93
|
+
...(difference === undefined ? {} : { perceptualDifference: difference }),
|
|
94
|
+
borderWidthPx,
|
|
95
|
+
shadow,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function prominenceMagnitude(typography, surface, area) {
|
|
99
|
+
const size = typography.sizePx ?? 16;
|
|
100
|
+
const weight = typography.weight ?? 400;
|
|
101
|
+
const darkness = 1 - (luminance(typography.foreground) ?? 0.5);
|
|
102
|
+
const surfaceLift = surface.fill === undefined ? 0 : 0.25;
|
|
103
|
+
const borderLift = surface.borderWidthPx > 0 || surface.shadow ? 0.15 : 0;
|
|
104
|
+
return round(Math.log2(Math.max(1, size)) + weight / 1000 + darkness * 0.25 + Math.log10(area) * 0.05 + surfaceLift + borderLift);
|
|
105
|
+
}
|
|
106
|
+
function textOf(node) {
|
|
107
|
+
const parts = [];
|
|
108
|
+
collect(node);
|
|
109
|
+
return parts.join(' ').replace(/\s+/g, ' ').trim();
|
|
110
|
+
function collect(current) {
|
|
111
|
+
if (current.tag === '#text') {
|
|
112
|
+
if (current.text !== undefined)
|
|
113
|
+
parts.push(current.text);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
for (const child of current.children)
|
|
117
|
+
collect(child);
|
|
118
|
+
for (const child of current.shadowChildren ?? [])
|
|
119
|
+
collect(child);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function semanticClassOf(node) {
|
|
123
|
+
const role = node.aria?.role;
|
|
124
|
+
if (role === 'heading') {
|
|
125
|
+
const level = node.attributes['aria-level'] ?? /^h([1-6])$/.exec(node.tag)?.[1];
|
|
126
|
+
return level === undefined ? 'heading' : `heading:${level}`;
|
|
127
|
+
}
|
|
128
|
+
if (role !== null && role !== undefined)
|
|
129
|
+
return role;
|
|
130
|
+
if (node.tag === 'label')
|
|
131
|
+
return 'label';
|
|
132
|
+
if (/^h[1-6]$/.test(node.tag))
|
|
133
|
+
return `heading:${node.tag.slice(1)}`;
|
|
134
|
+
if (node.tag === 'p')
|
|
135
|
+
return 'paragraph';
|
|
136
|
+
if (node.tag === 'li')
|
|
137
|
+
return 'listitem';
|
|
138
|
+
if (node.tag === 'section' || node.tag === 'article')
|
|
139
|
+
return 'region';
|
|
140
|
+
return node.tag;
|
|
141
|
+
}
|
|
142
|
+
export function center(rect) {
|
|
143
|
+
return [rect.x + rect.width / 2, rect.y + rect.height / 2];
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../src/graph.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RawCapture, RawNode, Rect } from '@variance-authority/core';\nimport type {\n PresentationNode,\n PresentationRelation,\n SurfaceEvidence,\n TypographyEvidence,\n} from './model.js';\nimport {\n colorOf,\n luminance,\n numeric,\n opaque,\n perceptualDifference,\n round,\n stableEntries,\n} from './math.js';\n\nexport interface BuiltGraph {\n readonly nodes: PresentationNode[];\n readonly relations: PresentationRelation[];\n readonly byId: Map<string, PresentationNode>;\n readonly rawById: Map<string, RawNode>;\n}\n\nexport function buildGraph(capture: RawCapture): BuiltGraph {\n const nodes: PresentationNode[] = [];\n const relations: PresentationRelation[] = [];\n const byId = new Map<string, PresentationNode>();\n const rawById = new Map<string, RawNode>();\n const roots = [capture.root, ...(capture.portals ?? [])];\n\n roots.forEach((root, rootIndex) => walk(root, rootIndex, '0', undefined, '#ffffff'));\n return { nodes, relations, byId, rawById };\n\n function walk(\n raw: RawNode,\n root: number,\n path: string,\n parent: string | undefined,\n containingFill: string,\n ): string | undefined {\n if (raw.tag === '#text') return undefined;\n const rect = raw.rect;\n const style = raw.computedStyle ?? raw.inlineStyle ?? {};\n const fill = opaque(style['background-color']);\n const effectiveFill = fill ?? containingFill;\n const id = `r${root}:${path}`;\n const childIds: string[] = [];\n raw.children.forEach((child, index) => {\n const childId = walk(child, root, `${path}/${index}`, id, effectiveFill);\n if (childId !== undefined) childIds.push(childId);\n });\n raw.shadowChildren?.forEach((child, index) => {\n const childId = walk(child, root, `${path}/shadow:${index}`, id, effectiveFill);\n if (childId !== undefined) childIds.push(childId);\n });\n if (rect === undefined || rect.width <= 0 || rect.height <= 0) return undefined;\n\n const typography = typographyOf(style);\n const surface = surfaceOf(style, fill, containingFill);\n const text = textOf(raw);\n const semanticClass = semanticClassOf(raw);\n const state = raw.aria?.state ?? {};\n const area = Math.max(1, rect.width * rect.height);\n const magnitude = prominenceMagnitude(typography, surface, area);\n const node: PresentationNode = {\n id,\n ref: { root, path },\n ...(parent === undefined ? {} : { parent }),\n children: childIds,\n tag: raw.tag,\n semanticClass,\n ...(raw.aria?.role ? { role: raw.aria.role } : {}),\n ...(raw.aria?.name ? { name: raw.aria.name } : {}),\n ...(text.length > 0 ? { text } : {}),\n state,\n stateSignature: stableEntries(state),\n rect,\n typography,\n surface,\n prominence: { magnitude },\n };\n nodes.push(node);\n byId.set(id, node);\n rawById.set(id, raw);\n if (parent !== undefined) {\n relations.push({ id: `contains:${parent}:${id}`, kind: 'contains', from: parent, to: id });\n }\n return id;\n }\n}\n\nfunction typographyOf(style: Readonly<Record<string, string>>): TypographyEvidence {\n const sizePx = numeric(style['font-size']);\n const lineHeight = numeric(style['line-height']);\n const weightText = style['font-weight'];\n const namedWeight = weightText === 'bold' ? 700 : weightText === 'normal' ? 400 : undefined;\n const weight = namedWeight ?? numeric(weightText);\n return {\n ...(style['font-family'] === undefined ? {} : { family: style['font-family'] }),\n ...(sizePx === undefined ? {} : { sizePx }),\n ...(weight === undefined ? {} : { weight }),\n ...(style['font-style'] === undefined ? {} : { style: style['font-style'] }),\n ...(sizePx === undefined ? {} : { lineHeightPx: lineHeight ?? round(sizePx * 1.2, 2) }),\n ...(style.color === undefined ? {} : { foreground: style.color }),\n };\n}\n\nfunction surfaceOf(\n style: Readonly<Record<string, string>>,\n fill: string | undefined,\n containingFill: string,\n): SurfaceEvidence {\n const borderWidthPx = Math.max(\n ...['top', 'right', 'bottom', 'left'].map((side) => {\n const width = numeric(style[`border-${side}-width`]) ?? 0;\n const kind = style[`border-${side}-style`];\n const color = colorOf(style[`border-${side}-color`]);\n return kind !== undefined && kind !== 'none' && color !== undefined && color.alpha > 0.01\n ? width\n : 0;\n }),\n );\n const shadow = style['box-shadow'] !== undefined && style['box-shadow'] !== 'none';\n const difference = fill === undefined ? undefined : perceptualDifference(fill, containingFill);\n return {\n ...(fill === undefined ? {} : { fill }),\n containingFill,\n ...(difference === undefined ? {} : { perceptualDifference: difference }),\n borderWidthPx,\n shadow,\n };\n}\n\nfunction prominenceMagnitude(\n typography: TypographyEvidence,\n surface: SurfaceEvidence,\n area: number,\n): number {\n const size = typography.sizePx ?? 16;\n const weight = typography.weight ?? 400;\n const darkness = 1 - (luminance(typography.foreground) ?? 0.5);\n const surfaceLift = surface.fill === undefined ? 0 : 0.25;\n const borderLift = surface.borderWidthPx > 0 || surface.shadow ? 0.15 : 0;\n return round(Math.log2(Math.max(1, size)) + weight / 1000 + darkness * 0.25 + Math.log10(area) * 0.05 + surfaceLift + borderLift);\n}\n\nfunction textOf(node: RawNode): string {\n const parts: string[] = [];\n collect(node);\n return parts.join(' ').replace(/\\s+/g, ' ').trim();\n\n function collect(current: RawNode): void {\n if (current.tag === '#text') {\n if (current.text !== undefined) parts.push(current.text);\n return;\n }\n for (const child of current.children) collect(child);\n for (const child of current.shadowChildren ?? []) collect(child);\n }\n}\n\nfunction semanticClassOf(node: RawNode): string {\n const role = node.aria?.role;\n if (role === 'heading') {\n const level = node.attributes['aria-level'] ?? /^h([1-6])$/.exec(node.tag)?.[1];\n return level === undefined ? 'heading' : `heading:${level}`;\n }\n if (role !== null && role !== undefined) return role;\n if (node.tag === 'label') return 'label';\n if (/^h[1-6]$/.test(node.tag)) return `heading:${node.tag.slice(1)}`;\n if (node.tag === 'p') return 'paragraph';\n if (node.tag === 'li') return 'listitem';\n if (node.tag === 'section' || node.tag === 'article') return 'region';\n return node.tag;\n}\n\nexport function center(rect: Rect): readonly [number, number] {\n return [rect.x + rect.width / 2, rect.y + rect.height / 2];\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PresentationHierarchyContract, PresentationHierarchyReading, PresentationReport } from './model.js';
|
|
2
|
+
/** Evaluate product-declared hierarchy roles without treating token use as authorization. */
|
|
3
|
+
export declare function inspectPresentationHierarchy(report: PresentationReport, contract: PresentationHierarchyContract): PresentationHierarchyReading;
|
|
4
|
+
//# sourceMappingURL=hierarchy.d.ts.map
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { codeUnitCompare, median, round } from './math.js';
|
|
2
|
+
const ROLE_ORDER = {
|
|
3
|
+
'owner-boundary': 0,
|
|
4
|
+
'leading-to-body': 1,
|
|
5
|
+
'body-peer': 2,
|
|
6
|
+
'content-internal': 3,
|
|
7
|
+
};
|
|
8
|
+
const RELATIONSHIP_RATIO = 1.15;
|
|
9
|
+
const COLORS = ['#00d4ff', '#ff2bd6', '#ffe600', '#70ff70'];
|
|
10
|
+
/** Evaluate product-declared hierarchy roles without treating token use as authorization. */
|
|
11
|
+
export function inspectPresentationHierarchy(report, contract) {
|
|
12
|
+
if (contract.id.trim().length === 0)
|
|
13
|
+
throw new Error('presentation hierarchy contract needs an id');
|
|
14
|
+
if (contract.levels.length < 2)
|
|
15
|
+
throw new Error('presentation hierarchy needs at least two relationship levels');
|
|
16
|
+
const byId = new Map(report.graph.nodes.map((node) => [node.id, node]));
|
|
17
|
+
const owner = byId.get(contract.owner);
|
|
18
|
+
if (owner === undefined) {
|
|
19
|
+
throw new Error(`presentation hierarchy owner ${contract.owner} is not in report ${report.digest}`);
|
|
20
|
+
}
|
|
21
|
+
const roles = contract.levels.map((level) => level.role);
|
|
22
|
+
if (new Set(roles).size !== roles.length)
|
|
23
|
+
throw new Error('presentation hierarchy roles must be distinct');
|
|
24
|
+
if (!roles.every((role, index) => index === 0 || ROLE_ORDER[role] > ROLE_ORDER[roles[index - 1]])) {
|
|
25
|
+
throw new Error('presentation hierarchy roles must be ordered outside-in');
|
|
26
|
+
}
|
|
27
|
+
const descendants = new Set([owner.id, ...descendantIds(owner, byId)]);
|
|
28
|
+
const relationByPair = new Map(report.graph.relations
|
|
29
|
+
.filter((relation) => relation.kind === 'separates')
|
|
30
|
+
.map((relation) => [`${relation.from}\0${relation.to}`, relation]));
|
|
31
|
+
const seenPairs = new Set();
|
|
32
|
+
const levels = contract.levels.map((level) => {
|
|
33
|
+
if (level.relations.length === 0) {
|
|
34
|
+
throw new Error(`presentation hierarchy role ${level.role} needs at least one relationship`);
|
|
35
|
+
}
|
|
36
|
+
const separations = level.relations.map(({ from, to }) => {
|
|
37
|
+
const key = `${from}\0${to}`;
|
|
38
|
+
if (seenPairs.has(key))
|
|
39
|
+
throw new Error(`presentation hierarchy relationship ${from} → ${to} has two roles`);
|
|
40
|
+
seenPairs.add(key);
|
|
41
|
+
const fromNode = byId.get(from);
|
|
42
|
+
const toNode = byId.get(to);
|
|
43
|
+
if (fromNode === undefined || toNode === undefined) {
|
|
44
|
+
throw new Error(`presentation hierarchy relationship ${from} → ${to} is not in report ${report.digest}`);
|
|
45
|
+
}
|
|
46
|
+
if (!descendants.has(from) || !descendants.has(to)) {
|
|
47
|
+
throw new Error(`presentation hierarchy relationship ${from} → ${to} is outside owner ${owner.id}`);
|
|
48
|
+
}
|
|
49
|
+
const relation = relationByPair.get(key);
|
|
50
|
+
if (relation === undefined)
|
|
51
|
+
throw new Error(`presentation hierarchy has no separation ${from} → ${to}`);
|
|
52
|
+
assertAxis(contract, relation, fromNode, toNode);
|
|
53
|
+
if (relation.distancePx === undefined || relation.boundaryStrength === undefined) {
|
|
54
|
+
throw new Error(`presentation hierarchy separation ${from} → ${to} is incomplete`);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
from: fromNode,
|
|
58
|
+
to: toNode,
|
|
59
|
+
distancePx: relation.distancePx,
|
|
60
|
+
boundaryStrength: relation.boundaryStrength,
|
|
61
|
+
...(relation.spacingCluster === undefined ? {} : { spacingCluster: relation.spacingCluster }),
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
const clusters = [...new Set(separations.flatMap((separation) => separation.spacingCluster ?? []))]
|
|
65
|
+
.sort(codeUnitCompare);
|
|
66
|
+
return {
|
|
67
|
+
role: level.role,
|
|
68
|
+
separations,
|
|
69
|
+
distanceMedianPx: round(median(separations.map((separation) => separation.distancePx)), 2),
|
|
70
|
+
boundaryMedian: round(median(separations.map((separation) => separation.boundaryStrength))),
|
|
71
|
+
spacingClusters: clusters,
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
const collisions = levels.slice(0, -1).flatMap((outer, index) => {
|
|
75
|
+
const inner = levels[index + 1];
|
|
76
|
+
const sharedSpacingClusters = outer.spacingClusters.filter((cluster) => inner.spacingClusters.includes(cluster));
|
|
77
|
+
const smaller = Math.min(outer.distanceMedianPx, inner.distanceMedianPx);
|
|
78
|
+
const larger = Math.max(outer.distanceMedianPx, inner.distanceMedianPx);
|
|
79
|
+
const ratio = smaller === 0 ? (larger === 0 ? 1 : Number.POSITIVE_INFINITY) : larger / smaller;
|
|
80
|
+
if (sharedSpacingClusters.length === 0 && ratio > RELATIONSHIP_RATIO)
|
|
81
|
+
return [];
|
|
82
|
+
return [{
|
|
83
|
+
outer: outer.role,
|
|
84
|
+
inner: inner.role,
|
|
85
|
+
outerMedianPx: outer.distanceMedianPx,
|
|
86
|
+
innerMedianPx: inner.distanceMedianPx,
|
|
87
|
+
ratio: round(ratio),
|
|
88
|
+
sharedSpacingClusters,
|
|
89
|
+
}];
|
|
90
|
+
});
|
|
91
|
+
const findings = collisions.map((collision, index) => ({
|
|
92
|
+
id: `H${index + 1}`,
|
|
93
|
+
rule: 'SPACING_HIERARCHY_COLLISION',
|
|
94
|
+
owner: owner.id,
|
|
95
|
+
nodes: nodesForCollision(levels, collision.outer, collision.inner),
|
|
96
|
+
contract: contract.id,
|
|
97
|
+
measurements: {
|
|
98
|
+
outerRole: collision.outer,
|
|
99
|
+
innerRole: collision.inner,
|
|
100
|
+
outerMedianPx: collision.outerMedianPx,
|
|
101
|
+
innerMedianPx: collision.innerMedianPx,
|
|
102
|
+
ratio: collision.ratio,
|
|
103
|
+
sharedSpacingClusters: collision.sharedSpacingClusters.join(','),
|
|
104
|
+
},
|
|
105
|
+
}));
|
|
106
|
+
return {
|
|
107
|
+
formatVersion: 1,
|
|
108
|
+
report: report.digest,
|
|
109
|
+
contract,
|
|
110
|
+
owner,
|
|
111
|
+
levels,
|
|
112
|
+
collisions,
|
|
113
|
+
findings,
|
|
114
|
+
paint: paint(contract, levels, collisions),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function assertAxis(contract, relation, from, to) {
|
|
118
|
+
const orderedOverlap = relation.axis === 'overlap' && (contract.axis === 'vertical' ? to.rect.y >= from.rect.y : to.rect.x >= from.rect.x);
|
|
119
|
+
if (relation.axis !== contract.axis && !orderedOverlap) {
|
|
120
|
+
throw new Error(`presentation hierarchy separation ${from.id} → ${to.id} is ${relation.axis}, not ${contract.axis}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function nodesForCollision(levels, outer, inner) {
|
|
124
|
+
return [...new Set(levels
|
|
125
|
+
.filter((level) => level.role === outer || level.role === inner)
|
|
126
|
+
.flatMap((level) => level.separations.flatMap((separation) => [separation.from.id, separation.to.id])))]
|
|
127
|
+
.sort(codeUnitCompare);
|
|
128
|
+
}
|
|
129
|
+
function paint(contract, levels, collisions) {
|
|
130
|
+
const collided = new Set(collisions.flatMap((collision) => [collision.outer, collision.inner]));
|
|
131
|
+
return levels.flatMap((level, levelIndex) => level.separations.map((separation, relationIndex) => {
|
|
132
|
+
const horizontal = contract.axis === 'vertical';
|
|
133
|
+
const coordinate = horizontal
|
|
134
|
+
? round((separation.from.rect.y + separation.from.rect.height + separation.to.rect.y) / 2, 2)
|
|
135
|
+
: round((separation.from.rect.x + separation.from.rect.width + separation.to.rect.x) / 2, 2);
|
|
136
|
+
return {
|
|
137
|
+
id: `hierarchy:${contract.id}:${level.role}:${relationIndex + 1}`,
|
|
138
|
+
owner: contract.owner,
|
|
139
|
+
nodes: [separation.from.id, separation.to.id],
|
|
140
|
+
contract: contract.id,
|
|
141
|
+
layer: 'findings',
|
|
142
|
+
shape: 'line',
|
|
143
|
+
color: collided.has(level.role) ? '#ff1744' : COLORS[levelIndex],
|
|
144
|
+
label: `${level.role} ${separation.distancePx}px`,
|
|
145
|
+
line: horizontal
|
|
146
|
+
? {
|
|
147
|
+
x1: Math.min(separation.from.rect.x, separation.to.rect.x),
|
|
148
|
+
y1: coordinate,
|
|
149
|
+
x2: Math.max(separation.from.rect.x + separation.from.rect.width, separation.to.rect.x + separation.to.rect.width),
|
|
150
|
+
y2: coordinate,
|
|
151
|
+
}
|
|
152
|
+
: {
|
|
153
|
+
x1: coordinate,
|
|
154
|
+
y1: Math.min(separation.from.rect.y, separation.to.rect.y),
|
|
155
|
+
x2: coordinate,
|
|
156
|
+
y2: Math.max(separation.from.rect.y + separation.from.rect.height, separation.to.rect.y + separation.to.rect.height),
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
function descendantIds(owner, byId) {
|
|
162
|
+
const result = [];
|
|
163
|
+
for (const id of owner.children) {
|
|
164
|
+
const child = byId.get(id);
|
|
165
|
+
if (child === undefined)
|
|
166
|
+
continue;
|
|
167
|
+
result.push(id, ...descendantIds(child, byId));
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=hierarchy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hierarchy.js","sourceRoot":"","sources":["../src/hierarchy.ts"],"names":[],"mappings":"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","sourcesContent":["import { codeUnitCompare, median, round } from './math.js';\nimport type {\n PaintInstruction,\n PresentationFinding,\n PresentationHierarchyContract,\n PresentationHierarchyReading,\n PresentationHierarchyRole,\n PresentationNode,\n PresentationRelation,\n PresentationReport,\n} from './model.js';\n\nconst ROLE_ORDER: Readonly<Record<PresentationHierarchyRole, number>> = {\n 'owner-boundary': 0,\n 'leading-to-body': 1,\n 'body-peer': 2,\n 'content-internal': 3,\n};\nconst RELATIONSHIP_RATIO = 1.15;\nconst COLORS = ['#00d4ff', '#ff2bd6', '#ffe600', '#70ff70'] as const;\n\n/** Evaluate product-declared hierarchy roles without treating token use as authorization. */\nexport function inspectPresentationHierarchy(\n report: PresentationReport,\n contract: PresentationHierarchyContract,\n): PresentationHierarchyReading {\n if (contract.id.trim().length === 0) throw new Error('presentation hierarchy contract needs an id');\n if (contract.levels.length < 2) throw new Error('presentation hierarchy needs at least two relationship levels');\n const byId = new Map(report.graph.nodes.map((node) => [node.id, node]));\n const owner = byId.get(contract.owner);\n if (owner === undefined) {\n throw new Error(`presentation hierarchy owner ${contract.owner} is not in report ${report.digest}`);\n }\n const roles = contract.levels.map((level) => level.role);\n if (new Set(roles).size !== roles.length) throw new Error('presentation hierarchy roles must be distinct');\n if (!roles.every((role, index) => index === 0 || ROLE_ORDER[role] > ROLE_ORDER[roles[index - 1]!]!)) {\n throw new Error('presentation hierarchy roles must be ordered outside-in');\n }\n const descendants = new Set([owner.id, ...descendantIds(owner, byId)]);\n const relationByPair = new Map(report.graph.relations\n .filter((relation) => relation.kind === 'separates')\n .map((relation) => [`${relation.from}\\0${relation.to}`, relation]));\n const seenPairs = new Set<string>();\n const levels = contract.levels.map((level) => {\n if (level.relations.length === 0) {\n throw new Error(`presentation hierarchy role ${level.role} needs at least one relationship`);\n }\n const separations = level.relations.map(({ from, to }) => {\n const key = `${from}\\0${to}`;\n if (seenPairs.has(key)) throw new Error(`presentation hierarchy relationship ${from} → ${to} has two roles`);\n seenPairs.add(key);\n const fromNode = byId.get(from);\n const toNode = byId.get(to);\n if (fromNode === undefined || toNode === undefined) {\n throw new Error(`presentation hierarchy relationship ${from} → ${to} is not in report ${report.digest}`);\n }\n if (!descendants.has(from) || !descendants.has(to)) {\n throw new Error(`presentation hierarchy relationship ${from} → ${to} is outside owner ${owner.id}`);\n }\n const relation = relationByPair.get(key);\n if (relation === undefined) throw new Error(`presentation hierarchy has no separation ${from} → ${to}`);\n assertAxis(contract, relation, fromNode, toNode);\n if (relation.distancePx === undefined || relation.boundaryStrength === undefined) {\n throw new Error(`presentation hierarchy separation ${from} → ${to} is incomplete`);\n }\n return {\n from: fromNode,\n to: toNode,\n distancePx: relation.distancePx,\n boundaryStrength: relation.boundaryStrength,\n ...(relation.spacingCluster === undefined ? {} : { spacingCluster: relation.spacingCluster }),\n };\n });\n const clusters = [...new Set(separations.flatMap((separation) => separation.spacingCluster ?? []))]\n .sort(codeUnitCompare);\n return {\n role: level.role,\n separations,\n distanceMedianPx: round(median(separations.map((separation) => separation.distancePx))!, 2),\n boundaryMedian: round(median(separations.map((separation) => separation.boundaryStrength))!),\n spacingClusters: clusters,\n };\n });\n const collisions = levels.slice(0, -1).flatMap((outer, index) => {\n const inner = levels[index + 1]!;\n const sharedSpacingClusters = outer.spacingClusters.filter((cluster) => inner.spacingClusters.includes(cluster));\n const smaller = Math.min(outer.distanceMedianPx, inner.distanceMedianPx);\n const larger = Math.max(outer.distanceMedianPx, inner.distanceMedianPx);\n const ratio = smaller === 0 ? (larger === 0 ? 1 : Number.POSITIVE_INFINITY) : larger / smaller;\n if (sharedSpacingClusters.length === 0 && ratio > RELATIONSHIP_RATIO) return [];\n return [{\n outer: outer.role,\n inner: inner.role,\n outerMedianPx: outer.distanceMedianPx,\n innerMedianPx: inner.distanceMedianPx,\n ratio: round(ratio),\n sharedSpacingClusters,\n }];\n });\n const findings: PresentationFinding[] = collisions.map((collision, index) => ({\n id: `H${index + 1}`,\n rule: 'SPACING_HIERARCHY_COLLISION',\n owner: owner.id,\n nodes: nodesForCollision(levels, collision.outer, collision.inner),\n contract: contract.id,\n measurements: {\n outerRole: collision.outer,\n innerRole: collision.inner,\n outerMedianPx: collision.outerMedianPx,\n innerMedianPx: collision.innerMedianPx,\n ratio: collision.ratio,\n sharedSpacingClusters: collision.sharedSpacingClusters.join(','),\n },\n }));\n return {\n formatVersion: 1,\n report: report.digest,\n contract,\n owner,\n levels,\n collisions,\n findings,\n paint: paint(contract, levels, collisions),\n };\n}\n\nfunction assertAxis(\n contract: PresentationHierarchyContract,\n relation: PresentationRelation,\n from: PresentationNode,\n to: PresentationNode,\n): void {\n const orderedOverlap = relation.axis === 'overlap' && (\n contract.axis === 'vertical' ? to.rect.y >= from.rect.y : to.rect.x >= from.rect.x\n );\n if (relation.axis !== contract.axis && !orderedOverlap) {\n throw new Error(\n `presentation hierarchy separation ${from.id} → ${to.id} is ${relation.axis}, not ${contract.axis}`,\n );\n }\n}\n\nfunction nodesForCollision(\n levels: PresentationHierarchyReading['levels'],\n outer: PresentationHierarchyRole,\n inner: PresentationHierarchyRole,\n): string[] {\n return [...new Set(levels\n .filter((level) => level.role === outer || level.role === inner)\n .flatMap((level) => level.separations.flatMap((separation) => [separation.from.id, separation.to.id])))]\n .sort(codeUnitCompare);\n}\n\nfunction paint(\n contract: PresentationHierarchyContract,\n levels: PresentationHierarchyReading['levels'],\n collisions: PresentationHierarchyReading['collisions'],\n): PaintInstruction[] {\n const collided = new Set(collisions.flatMap((collision) => [collision.outer, collision.inner]));\n return levels.flatMap((level, levelIndex) => level.separations.map((separation, relationIndex) => {\n const horizontal = contract.axis === 'vertical';\n const coordinate = horizontal\n ? round((separation.from.rect.y + separation.from.rect.height + separation.to.rect.y) / 2, 2)\n : round((separation.from.rect.x + separation.from.rect.width + separation.to.rect.x) / 2, 2);\n return {\n id: `hierarchy:${contract.id}:${level.role}:${relationIndex + 1}`,\n owner: contract.owner,\n nodes: [separation.from.id, separation.to.id],\n contract: contract.id,\n layer: 'findings' as const,\n shape: 'line' as const,\n color: collided.has(level.role) ? '#ff1744' : COLORS[levelIndex]!,\n label: `${level.role} ${separation.distancePx}px`,\n line: horizontal\n ? {\n x1: Math.min(separation.from.rect.x, separation.to.rect.x),\n y1: coordinate,\n x2: Math.max(\n separation.from.rect.x + separation.from.rect.width,\n separation.to.rect.x + separation.to.rect.width,\n ),\n y2: coordinate,\n }\n : {\n x1: coordinate,\n y1: Math.min(separation.from.rect.y, separation.to.rect.y),\n x2: coordinate,\n y2: Math.max(\n separation.from.rect.y + separation.from.rect.height,\n separation.to.rect.y + separation.to.rect.height,\n ),\n },\n };\n }));\n}\n\nfunction descendantIds(owner: PresentationNode, byId: ReadonlyMap<string, PresentationNode>): string[] {\n const result: string[] = [];\n for (const id of owner.children) {\n const child = byId.get(id);\n if (child === undefined) continue;\n result.push(id, ...descendantIds(child, byId));\n }\n return result;\n}\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { analyzePresentation } from './analyze.js';
|
|
2
|
+
export type { AnalyzePresentationOptions } from './analyze.js';
|
|
3
|
+
export { inspectPresentationAlignment } from './alignment.js';
|
|
4
|
+
export { inspectPresentationSpacing } from './spacing.js';
|
|
5
|
+
export { inspectPresentationHierarchy } from './hierarchy.js';
|
|
6
|
+
export { presentationSignal } from './report.js';
|
|
7
|
+
export type { PresentationSignalOptions } from './report.js';
|
|
8
|
+
export { comparePresentation } from './compare.js';
|
|
9
|
+
export { focusPresentation } from './focus.js';
|
|
10
|
+
export type { FocusPresentationOptions } from './focus.js';
|
|
11
|
+
export type { AlignmentAxis, BaselineCluster, ElementReference, PaintInstruction, PaintLayer, PresentationComparison, PresentationAlignmentKind, PresentationAlignmentReading, PresentationFinding, PresentationFindingRule, PresentationHierarchyContract, PresentationHierarchyReading, PresentationHierarchyRole, PresentationFocus, PresentationFocusDepth, PresentationNode, PresentationRelation, PresentationReport, PresentationSpacingAxis, PresentationSpacingReading, PresentationSignature, PresentationTelemetry, ProminenceCluster, RepeatedPattern, SpacingCluster, SurfaceGroup, } from './model.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { analyzePresentation } from './analyze.js';
|
|
2
|
+
export { inspectPresentationAlignment } from './alignment.js';
|
|
3
|
+
export { inspectPresentationSpacing } from './spacing.js';
|
|
4
|
+
export { inspectPresentationHierarchy } from './hierarchy.js';
|
|
5
|
+
export { presentationSignal } from './report.js';
|
|
6
|
+
export { comparePresentation } from './compare.js';
|
|
7
|
+
export { focusPresentation } from './focus.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAC1D,OAAO,EAAE,4BAA4B,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export { analyzePresentation } from './analyze.js';\nexport type { AnalyzePresentationOptions } from './analyze.js';\nexport { inspectPresentationAlignment } from './alignment.js';\nexport { inspectPresentationSpacing } from './spacing.js';\nexport { inspectPresentationHierarchy } from './hierarchy.js';\nexport { presentationSignal } from './report.js';\nexport type { PresentationSignalOptions } from './report.js';\nexport { comparePresentation } from './compare.js';\nexport { focusPresentation } from './focus.js';\nexport type { FocusPresentationOptions } from './focus.js';\nexport type {\n AlignmentAxis,\n BaselineCluster,\n ElementReference,\n PaintInstruction,\n PaintLayer,\n PresentationComparison,\n PresentationAlignmentKind,\n PresentationAlignmentReading,\n PresentationFinding,\n PresentationFindingRule,\n PresentationHierarchyContract,\n PresentationHierarchyReading,\n PresentationHierarchyRole,\n PresentationFocus,\n PresentationFocusDepth,\n PresentationNode,\n PresentationRelation,\n PresentationReport,\n PresentationSpacingAxis,\n PresentationSpacingReading,\n PresentationSignature,\n PresentationTelemetry,\n ProminenceCluster,\n RepeatedPattern,\n SpacingCluster,\n SurfaceGroup,\n} from './model.js';\n"]}
|