@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
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import type { AccessibilitySnapshot, Digest, Rect, SubjectRef } from '@variance-authority/core';
|
|
2
|
+
export type PresentationFindingRule = 'SEPARATION_COLLISION' | 'SPACING_RELATION_COLLISION' | 'SPACING_HIERARCHY_COLLISION' | 'ALIGNMENT_OUTLIER' | 'BASELINE_DRIFT' | 'PROMINENCE_COLLAPSE' | 'SURFACE_COLLISION' | 'REPETITION_GRAMMAR_COLLAPSE' | 'PRESENTATION_GRAMMAR_DRIFT';
|
|
3
|
+
export type PresentationRelationKind = 'contains' | 'separates' | 'aligns' | 'shares-baseline' | 'semantic-peer';
|
|
4
|
+
export interface ElementReference {
|
|
5
|
+
/** Zero is the subject root; later roots are portal content in capture order. */
|
|
6
|
+
readonly root: number;
|
|
7
|
+
/** Child-node indices from that root. Text nodes occupy an index but are not graph nodes. */
|
|
8
|
+
readonly path: string;
|
|
9
|
+
}
|
|
10
|
+
export interface TypographyEvidence {
|
|
11
|
+
readonly family?: string;
|
|
12
|
+
readonly sizePx?: number;
|
|
13
|
+
readonly weight?: number;
|
|
14
|
+
readonly style?: string;
|
|
15
|
+
readonly lineHeightPx?: number;
|
|
16
|
+
readonly foreground?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface SurfaceEvidence {
|
|
19
|
+
readonly fill?: string;
|
|
20
|
+
readonly containingFill?: string;
|
|
21
|
+
readonly perceptualDifference?: number;
|
|
22
|
+
readonly borderWidthPx: number;
|
|
23
|
+
readonly shadow: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface ProminenceEvidence {
|
|
26
|
+
/** Relative visual magnitude, not a quality or severity score. */
|
|
27
|
+
readonly magnitude: number;
|
|
28
|
+
readonly cluster?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface PresentationNode {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly ref: ElementReference;
|
|
33
|
+
readonly parent?: string;
|
|
34
|
+
readonly children: readonly string[];
|
|
35
|
+
readonly tag: string;
|
|
36
|
+
readonly semanticClass: string;
|
|
37
|
+
readonly role?: string;
|
|
38
|
+
readonly name?: string;
|
|
39
|
+
readonly text?: string;
|
|
40
|
+
readonly state: Readonly<Record<string, string | boolean | number>>;
|
|
41
|
+
readonly stateSignature: string;
|
|
42
|
+
readonly rect: Rect;
|
|
43
|
+
readonly typography: TypographyEvidence;
|
|
44
|
+
readonly surface: SurfaceEvidence;
|
|
45
|
+
readonly prominence: ProminenceEvidence;
|
|
46
|
+
}
|
|
47
|
+
export interface PresentationRelation {
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly kind: PresentationRelationKind;
|
|
50
|
+
readonly from: string;
|
|
51
|
+
readonly to: string;
|
|
52
|
+
readonly axis?: 'horizontal' | 'vertical' | 'overlap';
|
|
53
|
+
readonly distancePx?: number;
|
|
54
|
+
readonly spacingCluster?: string;
|
|
55
|
+
readonly boundaryStrength?: number;
|
|
56
|
+
readonly alignment?: 'left' | 'right' | 'horizontal-center' | 'top' | 'bottom' | 'vertical-center';
|
|
57
|
+
}
|
|
58
|
+
export interface SpacingCluster {
|
|
59
|
+
readonly id: string;
|
|
60
|
+
readonly centerPx: number;
|
|
61
|
+
readonly minPx: number;
|
|
62
|
+
readonly maxPx: number;
|
|
63
|
+
readonly samples: number;
|
|
64
|
+
}
|
|
65
|
+
export interface AlignmentAxis {
|
|
66
|
+
readonly id: string;
|
|
67
|
+
readonly kind: NonNullable<PresentationRelation['alignment']>;
|
|
68
|
+
readonly coordinate: number;
|
|
69
|
+
readonly members: readonly string[];
|
|
70
|
+
}
|
|
71
|
+
export type PresentationAlignmentKind = AlignmentAxis['kind'];
|
|
72
|
+
export interface PresentationAlignmentReading {
|
|
73
|
+
readonly formatVersion: 1;
|
|
74
|
+
readonly report: Digest;
|
|
75
|
+
/** The box or composition in which the caller says these nodes form one visual flow. */
|
|
76
|
+
readonly owner: PresentationNode;
|
|
77
|
+
readonly kind: PresentationAlignmentKind;
|
|
78
|
+
readonly coordinatePx: number;
|
|
79
|
+
readonly spreadPx: number;
|
|
80
|
+
readonly members: readonly {
|
|
81
|
+
readonly node: PresentationNode;
|
|
82
|
+
readonly coordinatePx: number;
|
|
83
|
+
readonly deviationPx: number;
|
|
84
|
+
}[];
|
|
85
|
+
/** Axis and member marks derived from this reading; painting does not acquire the page. */
|
|
86
|
+
readonly paint: readonly PaintInstruction[];
|
|
87
|
+
}
|
|
88
|
+
export type PresentationSpacingAxis = 'horizontal' | 'vertical';
|
|
89
|
+
export interface PresentationSpacingReading {
|
|
90
|
+
readonly formatVersion: 1;
|
|
91
|
+
readonly report: Digest;
|
|
92
|
+
/** The box or composition whose immediate children own these separations. */
|
|
93
|
+
readonly owner: PresentationNode;
|
|
94
|
+
readonly axis: PresentationSpacingAxis;
|
|
95
|
+
/** Consecutive immediate children in structural order. */
|
|
96
|
+
readonly members: readonly PresentationNode[];
|
|
97
|
+
readonly separations: readonly {
|
|
98
|
+
readonly from: PresentationNode;
|
|
99
|
+
readonly to: PresentationNode;
|
|
100
|
+
readonly distancePx: number;
|
|
101
|
+
readonly boundaryStrength: number;
|
|
102
|
+
readonly spacingCluster?: string;
|
|
103
|
+
}[];
|
|
104
|
+
readonly distance: {
|
|
105
|
+
readonly minPx: number;
|
|
106
|
+
readonly medianPx: number;
|
|
107
|
+
readonly maxPx: number;
|
|
108
|
+
};
|
|
109
|
+
readonly boundary: {
|
|
110
|
+
readonly min: number;
|
|
111
|
+
readonly median: number;
|
|
112
|
+
readonly max: number;
|
|
113
|
+
};
|
|
114
|
+
/** Boundary marks derived from this reading; painting does not acquire the page. */
|
|
115
|
+
readonly paint: readonly PaintInstruction[];
|
|
116
|
+
}
|
|
117
|
+
/** Product-owned relationship roles, ordered from the outside of a structure inward. */
|
|
118
|
+
export type PresentationHierarchyRole = 'owner-boundary' | 'leading-to-body' | 'body-peer' | 'content-internal';
|
|
119
|
+
export interface PresentationHierarchyContract {
|
|
120
|
+
readonly id: string;
|
|
121
|
+
/** The smallest graph node that contains every declared relationship. */
|
|
122
|
+
readonly owner: string;
|
|
123
|
+
readonly axis: PresentationSpacingAxis;
|
|
124
|
+
/** Ordered outside-in. A role is intent; it is not inferred from a design token. */
|
|
125
|
+
readonly levels: readonly {
|
|
126
|
+
readonly role: PresentationHierarchyRole;
|
|
127
|
+
readonly relations: readonly {
|
|
128
|
+
readonly from: string;
|
|
129
|
+
readonly to: string;
|
|
130
|
+
}[];
|
|
131
|
+
}[];
|
|
132
|
+
}
|
|
133
|
+
export interface PresentationHierarchyReading {
|
|
134
|
+
readonly formatVersion: 1;
|
|
135
|
+
readonly report: Digest;
|
|
136
|
+
readonly contract: PresentationHierarchyContract;
|
|
137
|
+
readonly owner: PresentationNode;
|
|
138
|
+
readonly levels: readonly {
|
|
139
|
+
readonly role: PresentationHierarchyRole;
|
|
140
|
+
readonly separations: readonly {
|
|
141
|
+
readonly from: PresentationNode;
|
|
142
|
+
readonly to: PresentationNode;
|
|
143
|
+
readonly distancePx: number;
|
|
144
|
+
readonly boundaryStrength: number;
|
|
145
|
+
readonly spacingCluster?: string;
|
|
146
|
+
}[];
|
|
147
|
+
readonly distanceMedianPx: number;
|
|
148
|
+
readonly boundaryMedian: number;
|
|
149
|
+
readonly spacingClusters: readonly string[];
|
|
150
|
+
}[];
|
|
151
|
+
readonly collisions: readonly {
|
|
152
|
+
readonly outer: PresentationHierarchyRole;
|
|
153
|
+
readonly inner: PresentationHierarchyRole;
|
|
154
|
+
readonly outerMedianPx: number;
|
|
155
|
+
readonly innerMedianPx: number;
|
|
156
|
+
readonly ratio: number;
|
|
157
|
+
readonly sharedSpacingClusters: readonly string[];
|
|
158
|
+
}[];
|
|
159
|
+
readonly findings: readonly PresentationFinding[];
|
|
160
|
+
readonly paint: readonly PaintInstruction[];
|
|
161
|
+
}
|
|
162
|
+
export interface BaselineCluster {
|
|
163
|
+
readonly id: string;
|
|
164
|
+
readonly coordinate: number;
|
|
165
|
+
readonly confidence: 'inferred';
|
|
166
|
+
readonly members: readonly {
|
|
167
|
+
readonly node: string;
|
|
168
|
+
readonly deviationPx: number;
|
|
169
|
+
}[];
|
|
170
|
+
}
|
|
171
|
+
export interface ProminenceCluster {
|
|
172
|
+
readonly id: string;
|
|
173
|
+
readonly magnitude: number;
|
|
174
|
+
readonly members: readonly string[];
|
|
175
|
+
readonly semanticClasses: readonly string[];
|
|
176
|
+
}
|
|
177
|
+
export interface SurfaceGroup {
|
|
178
|
+
readonly id: string;
|
|
179
|
+
readonly fill: string;
|
|
180
|
+
readonly members: readonly string[];
|
|
181
|
+
}
|
|
182
|
+
export interface PresentationSignature {
|
|
183
|
+
readonly widthPx: number;
|
|
184
|
+
readonly heightPx: number;
|
|
185
|
+
readonly leftPx: number;
|
|
186
|
+
readonly internalGapPx?: number;
|
|
187
|
+
readonly boundaryStrength?: number;
|
|
188
|
+
readonly prominenceCluster?: string;
|
|
189
|
+
readonly surfaceGroup?: string;
|
|
190
|
+
}
|
|
191
|
+
export interface RepeatedPattern {
|
|
192
|
+
readonly id: string;
|
|
193
|
+
readonly parent: string;
|
|
194
|
+
readonly semanticShape: string;
|
|
195
|
+
readonly instances: readonly string[];
|
|
196
|
+
readonly recurringLabels: readonly string[];
|
|
197
|
+
readonly presentationSimilarity: number;
|
|
198
|
+
readonly dominant: PresentationSignature;
|
|
199
|
+
readonly dominantInstances: readonly string[];
|
|
200
|
+
readonly outliers: readonly {
|
|
201
|
+
readonly node: string;
|
|
202
|
+
readonly deviation: number;
|
|
203
|
+
readonly explainedByState: boolean;
|
|
204
|
+
}[];
|
|
205
|
+
}
|
|
206
|
+
export interface PresentationFinding {
|
|
207
|
+
/** Stable within one deterministic report. */
|
|
208
|
+
readonly id: string;
|
|
209
|
+
readonly rule: PresentationFindingRule;
|
|
210
|
+
/** The graph node whose immediate structural relationship produced this finding. */
|
|
211
|
+
readonly owner: string;
|
|
212
|
+
readonly nodes: readonly string[];
|
|
213
|
+
readonly pattern?: string;
|
|
214
|
+
readonly contract?: string;
|
|
215
|
+
readonly measurements: Readonly<Record<string, number | string>>;
|
|
216
|
+
}
|
|
217
|
+
export interface PresentationTelemetry {
|
|
218
|
+
readonly content: {
|
|
219
|
+
readonly elements: number;
|
|
220
|
+
readonly characters: number;
|
|
221
|
+
readonly estimatedLines: number;
|
|
222
|
+
readonly controls: number;
|
|
223
|
+
readonly repeatedObjects: number;
|
|
224
|
+
};
|
|
225
|
+
readonly dimensions?: {
|
|
226
|
+
readonly regionWidthPx: number;
|
|
227
|
+
readonly regionHeightPx: number;
|
|
228
|
+
readonly viewportWidthPx: number;
|
|
229
|
+
readonly viewportHeightPx: number;
|
|
230
|
+
readonly viewportHeights: number;
|
|
231
|
+
};
|
|
232
|
+
readonly utilization?: {
|
|
233
|
+
readonly horizontal: number;
|
|
234
|
+
readonly occupiedArea: number;
|
|
235
|
+
};
|
|
236
|
+
readonly density?: {
|
|
237
|
+
readonly charactersPer1000Px2: number;
|
|
238
|
+
readonly linesPerViewport: number;
|
|
239
|
+
readonly controlsPerViewport: number;
|
|
240
|
+
readonly repeatedObjectsPerViewport: number;
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
export type PaintLayer = 'semantic' | 'spacing' | 'axes' | 'baselines' | 'surfaces' | 'prominence' | 'repetition' | 'findings';
|
|
244
|
+
export interface PaintInstruction {
|
|
245
|
+
readonly id: string;
|
|
246
|
+
/** The graph node that owns the painted relationship. */
|
|
247
|
+
readonly owner: string;
|
|
248
|
+
/** Graph nodes touched by this measurement. */
|
|
249
|
+
readonly nodes: readonly string[];
|
|
250
|
+
readonly finding?: string;
|
|
251
|
+
readonly pattern?: string;
|
|
252
|
+
readonly contract?: string;
|
|
253
|
+
readonly layer: PaintLayer;
|
|
254
|
+
readonly shape: 'rect' | 'line' | 'label';
|
|
255
|
+
readonly color: string;
|
|
256
|
+
readonly label: string;
|
|
257
|
+
readonly rect?: Rect;
|
|
258
|
+
readonly line?: {
|
|
259
|
+
readonly x1: number;
|
|
260
|
+
readonly y1: number;
|
|
261
|
+
readonly x2: number;
|
|
262
|
+
readonly y2: number;
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
export type PresentationFocusDepth = 'owner' | 'subtree';
|
|
266
|
+
export interface PresentationFocus {
|
|
267
|
+
readonly formatVersion: 1;
|
|
268
|
+
readonly report: Digest;
|
|
269
|
+
readonly depth: PresentationFocusDepth;
|
|
270
|
+
readonly owner: PresentationNode;
|
|
271
|
+
/** Owner, immediate children, and owned evidence nodes; or the complete subtree at `subtree` depth. */
|
|
272
|
+
readonly nodes: readonly PresentationNode[];
|
|
273
|
+
readonly patterns: readonly RepeatedPattern[];
|
|
274
|
+
readonly findings: readonly PresentationFinding[];
|
|
275
|
+
readonly paint: readonly PaintInstruction[];
|
|
276
|
+
/** Evidence below an owner focus, kept separate rather than folded into its findings. */
|
|
277
|
+
readonly nested: {
|
|
278
|
+
readonly owners: number;
|
|
279
|
+
readonly patterns: number;
|
|
280
|
+
readonly findings: number;
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/** One deterministic presentation graph and the evidence derived from its live subject. */
|
|
284
|
+
export interface PresentationReport {
|
|
285
|
+
readonly formatVersion: 1;
|
|
286
|
+
readonly digest: Digest;
|
|
287
|
+
/** Presentation-independent structure, text, DOM-correlated semantics, and state. */
|
|
288
|
+
readonly contentDigest: Digest;
|
|
289
|
+
readonly subject: SubjectRef;
|
|
290
|
+
/**
|
|
291
|
+
* Font identities the caller established for the capture, as the capture
|
|
292
|
+
* spells them. Absence means none were established.
|
|
293
|
+
*/
|
|
294
|
+
readonly fonts?: readonly string[];
|
|
295
|
+
readonly semantic: {
|
|
296
|
+
/** An empty array means semantic anchoring ran and found no anchors. */
|
|
297
|
+
readonly anchors: readonly string[];
|
|
298
|
+
/** Absence means browser ARIA was not observed; empty or partial roots remain present. */
|
|
299
|
+
readonly browserAccessibility?: AccessibilitySnapshot;
|
|
300
|
+
};
|
|
301
|
+
readonly telemetry: PresentationTelemetry;
|
|
302
|
+
readonly graph: {
|
|
303
|
+
readonly nodes: readonly PresentationNode[];
|
|
304
|
+
readonly relations: readonly PresentationRelation[];
|
|
305
|
+
};
|
|
306
|
+
readonly spacing?: readonly SpacingCluster[];
|
|
307
|
+
readonly axes?: readonly AlignmentAxis[];
|
|
308
|
+
readonly baselines?: readonly BaselineCluster[];
|
|
309
|
+
readonly prominence?: readonly ProminenceCluster[];
|
|
310
|
+
readonly surfaces?: readonly SurfaceGroup[];
|
|
311
|
+
readonly patterns?: readonly RepeatedPattern[];
|
|
312
|
+
/** Absence means the capture had no layout evidence; empty means it was measured and clean. */
|
|
313
|
+
readonly findings?: readonly PresentationFinding[];
|
|
314
|
+
readonly paint?: readonly PaintInstruction[];
|
|
315
|
+
}
|
|
316
|
+
export interface PresentationComparison {
|
|
317
|
+
readonly formatVersion: 1;
|
|
318
|
+
readonly before: Digest;
|
|
319
|
+
readonly after: Digest;
|
|
320
|
+
readonly findings?: readonly {
|
|
321
|
+
readonly rule: PresentationFindingRule;
|
|
322
|
+
readonly before: number;
|
|
323
|
+
readonly after: number;
|
|
324
|
+
readonly delta: number;
|
|
325
|
+
}[];
|
|
326
|
+
readonly information: {
|
|
327
|
+
readonly content: {
|
|
328
|
+
readonly before: Digest;
|
|
329
|
+
readonly after: Digest;
|
|
330
|
+
readonly preserved: boolean;
|
|
331
|
+
};
|
|
332
|
+
readonly characters: {
|
|
333
|
+
readonly before: number;
|
|
334
|
+
readonly after: number;
|
|
335
|
+
};
|
|
336
|
+
readonly elements: {
|
|
337
|
+
readonly before: number;
|
|
338
|
+
readonly after: number;
|
|
339
|
+
};
|
|
340
|
+
readonly repeatedObjects: {
|
|
341
|
+
readonly before: number;
|
|
342
|
+
readonly after: number;
|
|
343
|
+
};
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
//# sourceMappingURL=model.d.ts.map
|
package/dist/model.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"","sourcesContent":["import type {\n AccessibilitySnapshot,\n Digest,\n Rect,\n SubjectRef,\n} from '@variance-authority/core';\n\nexport type PresentationFindingRule =\n | 'SEPARATION_COLLISION'\n | 'SPACING_RELATION_COLLISION'\n | 'SPACING_HIERARCHY_COLLISION'\n | 'ALIGNMENT_OUTLIER'\n | 'BASELINE_DRIFT'\n | 'PROMINENCE_COLLAPSE'\n | 'SURFACE_COLLISION'\n | 'REPETITION_GRAMMAR_COLLAPSE'\n | 'PRESENTATION_GRAMMAR_DRIFT';\n\nexport type PresentationRelationKind =\n | 'contains'\n | 'separates'\n | 'aligns'\n | 'shares-baseline'\n | 'semantic-peer';\n\nexport interface ElementReference {\n /** Zero is the subject root; later roots are portal content in capture order. */\n readonly root: number;\n /** Child-node indices from that root. Text nodes occupy an index but are not graph nodes. */\n readonly path: string;\n}\n\nexport interface TypographyEvidence {\n readonly family?: string;\n readonly sizePx?: number;\n readonly weight?: number;\n readonly style?: string;\n readonly lineHeightPx?: number;\n readonly foreground?: string;\n}\n\nexport interface SurfaceEvidence {\n readonly fill?: string;\n readonly containingFill?: string;\n readonly perceptualDifference?: number;\n readonly borderWidthPx: number;\n readonly shadow: boolean;\n}\n\nexport interface ProminenceEvidence {\n /** Relative visual magnitude, not a quality or severity score. */\n readonly magnitude: number;\n readonly cluster?: string;\n}\n\nexport interface PresentationNode {\n readonly id: string;\n readonly ref: ElementReference;\n readonly parent?: string;\n readonly children: readonly string[];\n readonly tag: string;\n readonly semanticClass: string;\n readonly role?: string;\n readonly name?: string;\n readonly text?: string;\n readonly state: Readonly<Record<string, string | boolean | number>>;\n readonly stateSignature: string;\n readonly rect: Rect;\n readonly typography: TypographyEvidence;\n readonly surface: SurfaceEvidence;\n readonly prominence: ProminenceEvidence;\n}\n\nexport interface PresentationRelation {\n readonly id: string;\n readonly kind: PresentationRelationKind;\n readonly from: string;\n readonly to: string;\n readonly axis?: 'horizontal' | 'vertical' | 'overlap';\n readonly distancePx?: number;\n readonly spacingCluster?: string;\n readonly boundaryStrength?: number;\n readonly alignment?: 'left' | 'right' | 'horizontal-center' | 'top' | 'bottom' | 'vertical-center';\n}\n\nexport interface SpacingCluster {\n readonly id: string;\n readonly centerPx: number;\n readonly minPx: number;\n readonly maxPx: number;\n readonly samples: number;\n}\n\nexport interface AlignmentAxis {\n readonly id: string;\n readonly kind: NonNullable<PresentationRelation['alignment']>;\n readonly coordinate: number;\n readonly members: readonly string[];\n}\n\nexport type PresentationAlignmentKind = AlignmentAxis['kind'];\n\nexport interface PresentationAlignmentReading {\n readonly formatVersion: 1;\n readonly report: Digest;\n /** The box or composition in which the caller says these nodes form one visual flow. */\n readonly owner: PresentationNode;\n readonly kind: PresentationAlignmentKind;\n readonly coordinatePx: number;\n readonly spreadPx: number;\n readonly members: readonly {\n readonly node: PresentationNode;\n readonly coordinatePx: number;\n readonly deviationPx: number;\n }[];\n /** Axis and member marks derived from this reading; painting does not acquire the page. */\n readonly paint: readonly PaintInstruction[];\n}\n\nexport type PresentationSpacingAxis = 'horizontal' | 'vertical';\n\nexport interface PresentationSpacingReading {\n readonly formatVersion: 1;\n readonly report: Digest;\n /** The box or composition whose immediate children own these separations. */\n readonly owner: PresentationNode;\n readonly axis: PresentationSpacingAxis;\n /** Consecutive immediate children in structural order. */\n readonly members: readonly PresentationNode[];\n readonly separations: readonly {\n readonly from: PresentationNode;\n readonly to: PresentationNode;\n readonly distancePx: number;\n readonly boundaryStrength: number;\n readonly spacingCluster?: string;\n }[];\n readonly distance: {\n readonly minPx: number;\n readonly medianPx: number;\n readonly maxPx: number;\n };\n readonly boundary: {\n readonly min: number;\n readonly median: number;\n readonly max: number;\n };\n /** Boundary marks derived from this reading; painting does not acquire the page. */\n readonly paint: readonly PaintInstruction[];\n}\n\n/** Product-owned relationship roles, ordered from the outside of a structure inward. */\nexport type PresentationHierarchyRole =\n | 'owner-boundary'\n | 'leading-to-body'\n | 'body-peer'\n | 'content-internal';\n\nexport interface PresentationHierarchyContract {\n readonly id: string;\n /** The smallest graph node that contains every declared relationship. */\n readonly owner: string;\n readonly axis: PresentationSpacingAxis;\n /** Ordered outside-in. A role is intent; it is not inferred from a design token. */\n readonly levels: readonly {\n readonly role: PresentationHierarchyRole;\n readonly relations: readonly {\n readonly from: string;\n readonly to: string;\n }[];\n }[];\n}\n\nexport interface PresentationHierarchyReading {\n readonly formatVersion: 1;\n readonly report: Digest;\n readonly contract: PresentationHierarchyContract;\n readonly owner: PresentationNode;\n readonly levels: readonly {\n readonly role: PresentationHierarchyRole;\n readonly separations: readonly {\n readonly from: PresentationNode;\n readonly to: PresentationNode;\n readonly distancePx: number;\n readonly boundaryStrength: number;\n readonly spacingCluster?: string;\n }[];\n readonly distanceMedianPx: number;\n readonly boundaryMedian: number;\n readonly spacingClusters: readonly string[];\n }[];\n readonly collisions: readonly {\n readonly outer: PresentationHierarchyRole;\n readonly inner: PresentationHierarchyRole;\n readonly outerMedianPx: number;\n readonly innerMedianPx: number;\n readonly ratio: number;\n readonly sharedSpacingClusters: readonly string[];\n }[];\n readonly findings: readonly PresentationFinding[];\n readonly paint: readonly PaintInstruction[];\n}\n\nexport interface BaselineCluster {\n readonly id: string;\n readonly coordinate: number;\n readonly confidence: 'inferred';\n readonly members: readonly { readonly node: string; readonly deviationPx: number }[];\n}\n\nexport interface ProminenceCluster {\n readonly id: string;\n readonly magnitude: number;\n readonly members: readonly string[];\n readonly semanticClasses: readonly string[];\n}\n\nexport interface SurfaceGroup {\n readonly id: string;\n readonly fill: string;\n readonly members: readonly string[];\n}\n\nexport interface PresentationSignature {\n readonly widthPx: number;\n readonly heightPx: number;\n readonly leftPx: number;\n readonly internalGapPx?: number;\n readonly boundaryStrength?: number;\n readonly prominenceCluster?: string;\n readonly surfaceGroup?: string;\n}\n\nexport interface RepeatedPattern {\n readonly id: string;\n readonly parent: string;\n readonly semanticShape: string;\n readonly instances: readonly string[];\n readonly recurringLabels: readonly string[];\n readonly presentationSimilarity: number;\n readonly dominant: PresentationSignature;\n readonly dominantInstances: readonly string[];\n readonly outliers: readonly {\n readonly node: string;\n readonly deviation: number;\n readonly explainedByState: boolean;\n }[];\n}\n\nexport interface PresentationFinding {\n /** Stable within one deterministic report. */\n readonly id: string;\n readonly rule: PresentationFindingRule;\n /** The graph node whose immediate structural relationship produced this finding. */\n readonly owner: string;\n readonly nodes: readonly string[];\n readonly pattern?: string;\n readonly contract?: string;\n readonly measurements: Readonly<Record<string, number | string>>;\n}\n\nexport interface PresentationTelemetry {\n readonly content: {\n readonly elements: number;\n readonly characters: number;\n readonly estimatedLines: number;\n readonly controls: number;\n readonly repeatedObjects: number;\n };\n readonly dimensions?: {\n readonly regionWidthPx: number;\n readonly regionHeightPx: number;\n readonly viewportWidthPx: number;\n readonly viewportHeightPx: number;\n readonly viewportHeights: number;\n };\n readonly utilization?: {\n readonly horizontal: number;\n readonly occupiedArea: number;\n };\n readonly density?: {\n readonly charactersPer1000Px2: number;\n readonly linesPerViewport: number;\n readonly controlsPerViewport: number;\n readonly repeatedObjectsPerViewport: number;\n };\n}\n\nexport type PaintLayer =\n | 'semantic'\n | 'spacing'\n | 'axes'\n | 'baselines'\n | 'surfaces'\n | 'prominence'\n | 'repetition'\n | 'findings';\n\nexport interface PaintInstruction {\n readonly id: string;\n /** The graph node that owns the painted relationship. */\n readonly owner: string;\n /** Graph nodes touched by this measurement. */\n readonly nodes: readonly string[];\n readonly finding?: string;\n readonly pattern?: string;\n readonly contract?: string;\n readonly layer: PaintLayer;\n readonly shape: 'rect' | 'line' | 'label';\n readonly color: string;\n readonly label: string;\n readonly rect?: Rect;\n readonly line?: { readonly x1: number; readonly y1: number; readonly x2: number; readonly y2: number };\n}\n\nexport type PresentationFocusDepth = 'owner' | 'subtree';\n\nexport interface PresentationFocus {\n readonly formatVersion: 1;\n readonly report: Digest;\n readonly depth: PresentationFocusDepth;\n readonly owner: PresentationNode;\n /** Owner, immediate children, and owned evidence nodes; or the complete subtree at `subtree` depth. */\n readonly nodes: readonly PresentationNode[];\n readonly patterns: readonly RepeatedPattern[];\n readonly findings: readonly PresentationFinding[];\n readonly paint: readonly PaintInstruction[];\n /** Evidence below an owner focus, kept separate rather than folded into its findings. */\n readonly nested: {\n readonly owners: number;\n readonly patterns: number;\n readonly findings: number;\n };\n}\n\n/** One deterministic presentation graph and the evidence derived from its live subject. */\nexport interface PresentationReport {\n readonly formatVersion: 1;\n readonly digest: Digest;\n /** Presentation-independent structure, text, DOM-correlated semantics, and state. */\n readonly contentDigest: Digest;\n readonly subject: SubjectRef;\n /**\n * Font identities the caller established for the capture, as the capture\n * spells them. Absence means none were established.\n */\n readonly fonts?: readonly string[];\n readonly semantic: {\n /** An empty array means semantic anchoring ran and found no anchors. */\n readonly anchors: readonly string[];\n /** Absence means browser ARIA was not observed; empty or partial roots remain present. */\n readonly browserAccessibility?: AccessibilitySnapshot;\n };\n readonly telemetry: PresentationTelemetry;\n readonly graph: {\n readonly nodes: readonly PresentationNode[];\n readonly relations: readonly PresentationRelation[];\n };\n readonly spacing?: readonly SpacingCluster[];\n readonly axes?: readonly AlignmentAxis[];\n readonly baselines?: readonly BaselineCluster[];\n readonly prominence?: readonly ProminenceCluster[];\n readonly surfaces?: readonly SurfaceGroup[];\n readonly patterns?: readonly RepeatedPattern[];\n /** Absence means the capture had no layout evidence; empty means it was measured and clean. */\n readonly findings?: readonly PresentationFinding[];\n readonly paint?: readonly PaintInstruction[];\n}\n\nexport interface PresentationComparison {\n readonly formatVersion: 1;\n readonly before: Digest;\n readonly after: Digest;\n readonly findings?: readonly {\n readonly rule: PresentationFindingRule;\n readonly before: number;\n readonly after: number;\n readonly delta: number;\n }[];\n readonly information: {\n readonly content: {\n readonly before: Digest;\n readonly after: Digest;\n readonly preserved: boolean;\n };\n readonly characters: { readonly before: number; readonly after: number };\n readonly elements: { readonly before: number; readonly after: number };\n readonly repeatedObjects: { readonly before: number; readonly after: number };\n };\n}\n"]}
|
package/dist/paint.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AlignmentAxis, BaselineCluster, PaintInstruction, PresentationFinding, PresentationNode, PresentationRelation, ProminenceCluster, RepeatedPattern, SurfaceGroup } from './model.js';
|
|
2
|
+
export declare function paintInstructions(input: {
|
|
3
|
+
readonly nodes: readonly PresentationNode[];
|
|
4
|
+
readonly relations: readonly PresentationRelation[];
|
|
5
|
+
readonly axes: readonly AlignmentAxis[];
|
|
6
|
+
readonly baselines: readonly BaselineCluster[];
|
|
7
|
+
readonly prominence: readonly ProminenceCluster[];
|
|
8
|
+
readonly surfaces: readonly SurfaceGroup[];
|
|
9
|
+
readonly patterns: readonly RepeatedPattern[];
|
|
10
|
+
readonly findings: readonly PresentationFinding[];
|
|
11
|
+
}): PaintInstruction[];
|
|
12
|
+
//# sourceMappingURL=paint.d.ts.map
|
package/dist/paint.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { center } from './graph.js';
|
|
2
|
+
const COLORS = ['#00d4ff', '#ff2bd6', '#ffe600', '#70ff70', '#ff6b35', '#a78bfa'];
|
|
3
|
+
export function paintInstructions(input) {
|
|
4
|
+
const byId = new Map(input.nodes.map((node) => [node.id, node]));
|
|
5
|
+
const instructions = [];
|
|
6
|
+
for (const node of input.nodes) {
|
|
7
|
+
instructions.push({
|
|
8
|
+
id: `semantic:${node.id}`,
|
|
9
|
+
owner: node.parent ?? node.id,
|
|
10
|
+
nodes: [node.id],
|
|
11
|
+
layer: 'semantic',
|
|
12
|
+
shape: 'rect',
|
|
13
|
+
color: COLORS[0],
|
|
14
|
+
label: `${node.semanticClass} ${node.name ?? node.text ?? ''}`.trim(),
|
|
15
|
+
rect: node.rect,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
for (const relation of input.relations.filter((candidate) => candidate.kind === 'separates')) {
|
|
19
|
+
const from = byId.get(relation.from);
|
|
20
|
+
const to = byId.get(relation.to);
|
|
21
|
+
if (from === undefined || to === undefined)
|
|
22
|
+
continue;
|
|
23
|
+
const [x1, y1] = center(from.rect);
|
|
24
|
+
const [x2, y2] = center(to.rect);
|
|
25
|
+
instructions.push({
|
|
26
|
+
id: `spacing:${relation.id}`,
|
|
27
|
+
owner: commonOwner([relation.from, relation.to], byId) ?? relation.from,
|
|
28
|
+
nodes: [relation.from, relation.to],
|
|
29
|
+
layer: 'spacing',
|
|
30
|
+
shape: 'line',
|
|
31
|
+
color: COLORS[2],
|
|
32
|
+
label: `${relation.spacingCluster ?? 'gap'} ${relation.distancePx ?? 0}px`,
|
|
33
|
+
line: { x1, y1, x2, y2 },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
for (const axis of input.axes) {
|
|
37
|
+
const members = axis.members.flatMap((id) => byId.get(id) ?? []);
|
|
38
|
+
if (members.length === 0)
|
|
39
|
+
continue;
|
|
40
|
+
const vertical = ['left', 'right', 'horizontal-center'].includes(axis.kind);
|
|
41
|
+
instructions.push({
|
|
42
|
+
id: `axis:${axis.id}`,
|
|
43
|
+
owner: commonOwner(axis.members, byId) ?? firstMember(members),
|
|
44
|
+
nodes: axis.members,
|
|
45
|
+
layer: 'axes',
|
|
46
|
+
shape: 'line',
|
|
47
|
+
color: COLORS[3],
|
|
48
|
+
label: `${axis.id} ${axis.coordinate}px`,
|
|
49
|
+
line: vertical
|
|
50
|
+
? {
|
|
51
|
+
x1: axis.coordinate,
|
|
52
|
+
y1: Math.min(...members.map((node) => node.rect.y)),
|
|
53
|
+
x2: axis.coordinate,
|
|
54
|
+
y2: Math.max(...members.map((node) => node.rect.y + node.rect.height)),
|
|
55
|
+
}
|
|
56
|
+
: {
|
|
57
|
+
x1: Math.min(...members.map((node) => node.rect.x)),
|
|
58
|
+
y1: axis.coordinate,
|
|
59
|
+
x2: Math.max(...members.map((node) => node.rect.x + node.rect.width)),
|
|
60
|
+
y2: axis.coordinate,
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
for (const baseline of input.baselines) {
|
|
65
|
+
const members = baseline.members.flatMap((member) => byId.get(member.node) ?? []);
|
|
66
|
+
if (members.length === 0)
|
|
67
|
+
continue;
|
|
68
|
+
instructions.push({
|
|
69
|
+
id: `baseline:${baseline.id}`,
|
|
70
|
+
owner: commonOwner(baseline.members.map((member) => member.node), byId) ?? firstMember(members),
|
|
71
|
+
nodes: baseline.members.map((member) => member.node),
|
|
72
|
+
layer: 'baselines',
|
|
73
|
+
shape: 'line',
|
|
74
|
+
color: COLORS[4],
|
|
75
|
+
label: `${baseline.id} inferred`,
|
|
76
|
+
line: {
|
|
77
|
+
x1: Math.min(...members.map((node) => node.rect.x)),
|
|
78
|
+
y1: baseline.coordinate,
|
|
79
|
+
x2: Math.max(...members.map((node) => node.rect.x + node.rect.width)),
|
|
80
|
+
y2: baseline.coordinate,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
groupedRects(input.surfaces, 'surfaces', input.nodes, instructions);
|
|
85
|
+
groupedRects(input.prominence, 'prominence', input.nodes, instructions);
|
|
86
|
+
input.patterns.forEach((pattern, index) => {
|
|
87
|
+
for (const id of pattern.instances) {
|
|
88
|
+
const node = byId.get(id);
|
|
89
|
+
if (node === undefined)
|
|
90
|
+
continue;
|
|
91
|
+
instructions.push({
|
|
92
|
+
id: `pattern:${pattern.id}:${id}`,
|
|
93
|
+
owner: pattern.parent,
|
|
94
|
+
nodes: [id],
|
|
95
|
+
pattern: pattern.id,
|
|
96
|
+
layer: 'repetition',
|
|
97
|
+
shape: 'rect',
|
|
98
|
+
color: COLORS[index % COLORS.length],
|
|
99
|
+
label: `${pattern.id} #${pattern.instances.indexOf(id) + 1}`,
|
|
100
|
+
rect: node.rect,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
for (const finding of input.findings) {
|
|
105
|
+
if (finding.rule === 'SPACING_HIERARCHY_COLLISION') {
|
|
106
|
+
for (const instanceId of finding.nodes) {
|
|
107
|
+
const instance = byId.get(instanceId);
|
|
108
|
+
const members = instance?.children.slice(0, 3).flatMap((id) => byId.get(id) ?? []) ?? [];
|
|
109
|
+
if (members.length !== 3)
|
|
110
|
+
continue;
|
|
111
|
+
for (let index = 0; index < 2; index += 1) {
|
|
112
|
+
const from = members[index];
|
|
113
|
+
const to = members[index + 1];
|
|
114
|
+
const relation = input.relations.find((candidate) => candidate.kind === 'separates' && candidate.from === from.id && candidate.to === to.id);
|
|
115
|
+
const [x1, y1] = center(from.rect);
|
|
116
|
+
const [x2, y2] = center(to.rect);
|
|
117
|
+
instructions.push({
|
|
118
|
+
id: `finding:${finding.id}:${from.id}:${to.id}`,
|
|
119
|
+
owner: finding.owner,
|
|
120
|
+
nodes: [from.id, to.id],
|
|
121
|
+
finding: finding.id,
|
|
122
|
+
...(finding.pattern === undefined ? {} : { pattern: finding.pattern }),
|
|
123
|
+
layer: 'findings',
|
|
124
|
+
shape: 'line',
|
|
125
|
+
color: '#ff1744',
|
|
126
|
+
label: `${index === 0 ? 'leading→body' : 'body→body'} ${relation?.distancePx ?? 0}px`,
|
|
127
|
+
line: { x1, y1, x2, y2 },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
for (const id of finding.nodes) {
|
|
134
|
+
const node = byId.get(id);
|
|
135
|
+
if (node === undefined)
|
|
136
|
+
continue;
|
|
137
|
+
instructions.push({
|
|
138
|
+
id: `finding:${finding.id}:${id}`,
|
|
139
|
+
owner: finding.owner,
|
|
140
|
+
nodes: [id],
|
|
141
|
+
finding: finding.id,
|
|
142
|
+
...(finding.pattern === undefined ? {} : { pattern: finding.pattern }),
|
|
143
|
+
layer: 'findings',
|
|
144
|
+
shape: 'rect',
|
|
145
|
+
color: '#ff1744',
|
|
146
|
+
label: finding.rule,
|
|
147
|
+
rect: node.rect,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return instructions;
|
|
152
|
+
}
|
|
153
|
+
function groupedRects(groups, layer, nodes, sink) {
|
|
154
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
155
|
+
groups.forEach((group, index) => {
|
|
156
|
+
for (const member of group.members) {
|
|
157
|
+
const node = byId.get(member);
|
|
158
|
+
if (node === undefined)
|
|
159
|
+
continue;
|
|
160
|
+
sink.push({
|
|
161
|
+
id: `${layer}:${group.id}:${member}`,
|
|
162
|
+
owner: node.parent ?? node.id,
|
|
163
|
+
nodes: [member],
|
|
164
|
+
layer,
|
|
165
|
+
shape: 'rect',
|
|
166
|
+
color: COLORS[index % COLORS.length],
|
|
167
|
+
label: group.id,
|
|
168
|
+
rect: node.rect,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
function firstMember(nodes) {
|
|
174
|
+
return nodes[0]?.parent ?? nodes[0]?.id ?? 'presentation';
|
|
175
|
+
}
|
|
176
|
+
function commonOwner(ids, byId) {
|
|
177
|
+
const chains = ids.map((id) => ancestorChain(id, byId));
|
|
178
|
+
const first = chains[0];
|
|
179
|
+
if (first === undefined)
|
|
180
|
+
return undefined;
|
|
181
|
+
return first.find((candidate) => chains.every((chain) => chain.includes(candidate)));
|
|
182
|
+
}
|
|
183
|
+
function ancestorChain(id, byId) {
|
|
184
|
+
const chain = [];
|
|
185
|
+
let current = byId.get(id)?.parent;
|
|
186
|
+
while (current !== undefined) {
|
|
187
|
+
chain.push(current);
|
|
188
|
+
current = byId.get(current)?.parent;
|
|
189
|
+
}
|
|
190
|
+
return chain;
|
|
191
|
+
}
|
|
192
|
+
//# sourceMappingURL=paint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paint.js","sourceRoot":"","sources":["../src/paint.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n AlignmentAxis,\n BaselineCluster,\n PaintInstruction,\n PresentationFinding,\n PresentationNode,\n PresentationRelation,\n ProminenceCluster,\n RepeatedPattern,\n SurfaceGroup,\n} from './model.js';\nimport { center } from './graph.js';\n\nconst COLORS = ['#00d4ff', '#ff2bd6', '#ffe600', '#70ff70', '#ff6b35', '#a78bfa'];\n\nexport function paintInstructions(input: {\n readonly nodes: readonly PresentationNode[];\n readonly relations: readonly PresentationRelation[];\n readonly axes: readonly AlignmentAxis[];\n readonly baselines: readonly BaselineCluster[];\n readonly prominence: readonly ProminenceCluster[];\n readonly surfaces: readonly SurfaceGroup[];\n readonly patterns: readonly RepeatedPattern[];\n readonly findings: readonly PresentationFinding[];\n}): PaintInstruction[] {\n const byId = new Map(input.nodes.map((node) => [node.id, node]));\n const instructions: PaintInstruction[] = [];\n for (const node of input.nodes) {\n instructions.push({\n id: `semantic:${node.id}`,\n owner: node.parent ?? node.id,\n nodes: [node.id],\n layer: 'semantic',\n shape: 'rect',\n color: COLORS[0]!,\n label: `${node.semanticClass} ${node.name ?? node.text ?? ''}`.trim(),\n rect: node.rect,\n });\n }\n for (const relation of input.relations.filter((candidate) => candidate.kind === 'separates')) {\n const from = byId.get(relation.from);\n const to = byId.get(relation.to);\n if (from === undefined || to === undefined) continue;\n const [x1, y1] = center(from.rect);\n const [x2, y2] = center(to.rect);\n instructions.push({\n id: `spacing:${relation.id}`,\n owner: commonOwner([relation.from, relation.to], byId) ?? relation.from,\n nodes: [relation.from, relation.to],\n layer: 'spacing',\n shape: 'line',\n color: COLORS[2]!,\n label: `${relation.spacingCluster ?? 'gap'} ${relation.distancePx ?? 0}px`,\n line: { x1, y1, x2, y2 },\n });\n }\n for (const axis of input.axes) {\n const members = axis.members.flatMap((id) => byId.get(id) ?? []);\n if (members.length === 0) continue;\n const vertical = ['left', 'right', 'horizontal-center'].includes(axis.kind);\n instructions.push({\n id: `axis:${axis.id}`,\n owner: commonOwner(axis.members, byId) ?? firstMember(members),\n nodes: axis.members,\n layer: 'axes',\n shape: 'line',\n color: COLORS[3]!,\n label: `${axis.id} ${axis.coordinate}px`,\n line: vertical\n ? {\n x1: axis.coordinate,\n y1: Math.min(...members.map((node) => node.rect.y)),\n x2: axis.coordinate,\n y2: Math.max(...members.map((node) => node.rect.y + node.rect.height)),\n }\n : {\n x1: Math.min(...members.map((node) => node.rect.x)),\n y1: axis.coordinate,\n x2: Math.max(...members.map((node) => node.rect.x + node.rect.width)),\n y2: axis.coordinate,\n },\n });\n }\n for (const baseline of input.baselines) {\n const members = baseline.members.flatMap((member) => byId.get(member.node) ?? []);\n if (members.length === 0) continue;\n instructions.push({\n id: `baseline:${baseline.id}`,\n owner: commonOwner(baseline.members.map((member) => member.node), byId) ?? firstMember(members),\n nodes: baseline.members.map((member) => member.node),\n layer: 'baselines',\n shape: 'line',\n color: COLORS[4]!,\n label: `${baseline.id} inferred`,\n line: {\n x1: Math.min(...members.map((node) => node.rect.x)),\n y1: baseline.coordinate,\n x2: Math.max(...members.map((node) => node.rect.x + node.rect.width)),\n y2: baseline.coordinate,\n },\n });\n }\n groupedRects(input.surfaces, 'surfaces', input.nodes, instructions);\n groupedRects(input.prominence, 'prominence', input.nodes, instructions);\n input.patterns.forEach((pattern, index) => {\n for (const id of pattern.instances) {\n const node = byId.get(id);\n if (node === undefined) continue;\n instructions.push({\n id: `pattern:${pattern.id}:${id}`,\n owner: pattern.parent,\n nodes: [id],\n pattern: pattern.id,\n layer: 'repetition',\n shape: 'rect',\n color: COLORS[index % COLORS.length]!,\n label: `${pattern.id} #${pattern.instances.indexOf(id) + 1}`,\n rect: node.rect,\n });\n }\n });\n for (const finding of input.findings) {\n if (finding.rule === 'SPACING_HIERARCHY_COLLISION') {\n for (const instanceId of finding.nodes) {\n const instance = byId.get(instanceId);\n const members = instance?.children.slice(0, 3).flatMap((id) => byId.get(id) ?? []) ?? [];\n if (members.length !== 3) continue;\n for (let index = 0; index < 2; index += 1) {\n const from = members[index]!;\n const to = members[index + 1]!;\n const relation = input.relations.find((candidate) =>\n candidate.kind === 'separates' && candidate.from === from.id && candidate.to === to.id);\n const [x1, y1] = center(from.rect);\n const [x2, y2] = center(to.rect);\n instructions.push({\n id: `finding:${finding.id}:${from.id}:${to.id}`,\n owner: finding.owner,\n nodes: [from.id, to.id],\n finding: finding.id,\n ...(finding.pattern === undefined ? {} : { pattern: finding.pattern }),\n layer: 'findings',\n shape: 'line',\n color: '#ff1744',\n label: `${index === 0 ? 'leading→body' : 'body→body'} ${relation?.distancePx ?? 0}px`,\n line: { x1, y1, x2, y2 },\n });\n }\n }\n continue;\n }\n for (const id of finding.nodes) {\n const node = byId.get(id);\n if (node === undefined) continue;\n instructions.push({\n id: `finding:${finding.id}:${id}`,\n owner: finding.owner,\n nodes: [id],\n finding: finding.id,\n ...(finding.pattern === undefined ? {} : { pattern: finding.pattern }),\n layer: 'findings',\n shape: 'rect',\n color: '#ff1744',\n label: finding.rule,\n rect: node.rect,\n });\n }\n }\n return instructions;\n}\n\nfunction groupedRects(\n groups: readonly { readonly id: string; readonly members: readonly string[] }[],\n layer: 'surfaces' | 'prominence',\n nodes: readonly PresentationNode[],\n sink: PaintInstruction[],\n): void {\n const byId = new Map(nodes.map((node) => [node.id, node]));\n groups.forEach((group, index) => {\n for (const member of group.members) {\n const node = byId.get(member);\n if (node === undefined) continue;\n sink.push({\n id: `${layer}:${group.id}:${member}`,\n owner: node.parent ?? node.id,\n nodes: [member],\n layer,\n shape: 'rect',\n color: COLORS[index % COLORS.length]!,\n label: group.id,\n rect: node.rect,\n });\n }\n });\n}\n\nfunction firstMember(nodes: readonly PresentationNode[]): string {\n return nodes[0]?.parent ?? nodes[0]?.id ?? 'presentation';\n}\n\nfunction commonOwner(\n ids: readonly string[],\n byId: ReadonlyMap<string, PresentationNode>,\n): string | undefined {\n const chains = ids.map((id) => ancestorChain(id, byId));\n const first = chains[0];\n if (first === undefined) return undefined;\n return first.find((candidate) => chains.every((chain) => chain.includes(candidate)));\n}\n\nfunction ancestorChain(id: string, byId: ReadonlyMap<string, PresentationNode>): string[] {\n const chain: string[] = [];\n let current = byId.get(id)?.parent;\n while (current !== undefined) {\n chain.push(current);\n current = byId.get(current)?.parent;\n }\n return chain;\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BuiltGraph } from './graph.js';
|
|
2
|
+
import type { Measurements } from './measure.js';
|
|
3
|
+
import type { PresentationNode, PresentationRelation, RepeatedPattern } from './model.js';
|
|
4
|
+
export interface PatternResult {
|
|
5
|
+
readonly patterns: RepeatedPattern[];
|
|
6
|
+
readonly peerRelations: PresentationRelation[];
|
|
7
|
+
}
|
|
8
|
+
export declare function inferPatterns(graph: BuiltGraph, measured: Measurements): PatternResult;
|
|
9
|
+
export declare function descendants(node: PresentationNode, graph: BuiltGraph): PresentationNode[];
|
|
10
|
+
//# sourceMappingURL=patterns.d.ts.map
|