@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,463 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: variance-presentation
|
|
3
|
+
description: Sense, inspect, paint, and re-sense presentation relationships in a live Playwright page or Variance Authority RawCapture. Use for coding-agent UI presentation work; not for screenshot regression, baseline approval, generic accessibility auditing, or enforcing density and layout preferences.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Variance Presentation
|
|
7
|
+
|
|
8
|
+
Use `@variance-authority/presentation` to make the presentation relationships in
|
|
9
|
+
one rendered subject observable while a coding agent investigates or changes the
|
|
10
|
+
interface. Keep presentation evidence, product information, and browser ARIA as
|
|
11
|
+
separate signals.
|
|
12
|
+
|
|
13
|
+
The offering senses and supports. It does not own a baseline, approve a visual
|
|
14
|
+
change, return a build verdict, score UI quality, or prescribe a redesign.
|
|
15
|
+
|
|
16
|
+
## Authority boundary
|
|
17
|
+
|
|
18
|
+
Preserve product meaning and expose rendered relationships.
|
|
19
|
+
|
|
20
|
+
Use this authority order: product meaning → structural ownership → rendered
|
|
21
|
+
relationship → component contract → design-system token. A token is evidence of
|
|
22
|
+
implementation, never authorization for the relationship in which it appears.
|
|
23
|
+
|
|
24
|
+
- Treat content volume, density, dimensions, margins, whitespace, utilization,
|
|
25
|
+
and page height as telemetry. None is independently a defect.
|
|
26
|
+
- Do not recommend deleting, hiding, collapsing, or truncating required
|
|
27
|
+
information to reduce a finding.
|
|
28
|
+
- Do not infer a preferred table, card, accordion, column count, spacing scale,
|
|
29
|
+
margin, density, or page length.
|
|
30
|
+
- Do not homogenize a visual difference when observed semantic state explains
|
|
31
|
+
it.
|
|
32
|
+
- Do not infer implementation history from presentation variance.
|
|
33
|
+
- Findings name measured relationship failures. They are not severities,
|
|
34
|
+
regressions, or design instructions.
|
|
35
|
+
|
|
36
|
+
If the request is only to inspect or explain, return evidence without editing
|
|
37
|
+
the application. If the request includes implementation, let the product task
|
|
38
|
+
and the user decide the response to that evidence.
|
|
39
|
+
|
|
40
|
+
## Choose the entry point
|
|
41
|
+
|
|
42
|
+
| Starting state | Entry point | Result |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| A live Playwright `Page` and subject `Locator` | `sensePresentation` from `@variance-authority/presentation/playwright` | Acquires browser layout and Playwright ARIA, analyzes them, and optionally paints the page |
|
|
45
|
+
| An existing `RawCapture` | `analyzePresentation` from `@variance-authority/presentation` | Pure report derivation; browser accessibility is optional independent evidence |
|
|
46
|
+
| One structural level in an existing report | `focusPresentation` from `@variance-authority/presentation` | Pure owner reading that keeps nested evidence separate by default |
|
|
47
|
+
| Product-known visual peers across wrappers | `inspectPresentationAlignment` plus `paintPresentationAlignment` | Explicit coordinates, spread, member deviations, and matching paint without a verdict or another acquisition |
|
|
48
|
+
| Consecutive regions arranged by one box or composition | `inspectPresentationSpacing` plus `paintPresentationSpacing` | Every adjacent gap and boundary at that owner, even when the regions have different semantic shapes |
|
|
49
|
+
| Product-known hierarchy roles across nested owners | `inspectPresentationHierarchy` plus `paintPresentationHierarchy` | Typed outside-in relationship levels, adjacent collisions, findings, and role-labelled paint |
|
|
50
|
+
| Two presentation reports | `comparePresentation`, or `presentationSignal` when the evidence must travel with a general run report | Optional edit feedback, or introduced/resolved/persisted consequences with information identity retained separately from the verdict |
|
|
51
|
+
|
|
52
|
+
Prefer a page the caller already owns. Launch a browser only when the user asks
|
|
53
|
+
for a new live or headed session.
|
|
54
|
+
|
|
55
|
+
## The first move: build the presentation hierarchy
|
|
56
|
+
|
|
57
|
+
The locator says what to acquire. It does not say that every descendant should
|
|
58
|
+
be compared. Before interpreting a finding or painting the page, map the sensed
|
|
59
|
+
subject into the smallest useful hierarchy:
|
|
60
|
+
|
|
61
|
+
| Structural reading | What belongs together | What does not follow |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| Composition | Major content, illustration, navigation, or action regions whose arrangement answers a whole-subject question | Descendants of different regions are not automatically peers |
|
|
64
|
+
| Box | Immediate contents whose boundary owns spacing, repetition, surface, or prominence evidence | A finding owned by a nested box is not a defect of every ancestor |
|
|
65
|
+
| Flow | Product-known visual peers, even when implementation wrappers separate them | Shared ancestry alone does not make a flow |
|
|
66
|
+
| Content or illustration | Material carried by a composition and the internal boxes that organize it | Content and illustration do not need alignment merely because they are adjacent |
|
|
67
|
+
|
|
68
|
+
Use the graph's containment, roles, names, text, geometry and repeated patterns
|
|
69
|
+
to make this map. Do not start by reading the findings list as prose about the
|
|
70
|
+
whole locator.
|
|
71
|
+
|
|
72
|
+
Then choose one path:
|
|
73
|
+
|
|
74
|
+
1. For a box-owned relationship, call `focusPresentation(report, ownerId)`.
|
|
75
|
+
Read its immediate nodes and owned findings. If `nested` is non-zero, descend
|
|
76
|
+
only when that nested box is the next product question.
|
|
77
|
+
2. For a deliberately holistic question, use `depth: 'subtree'` and state why
|
|
78
|
+
descendants belong in the same reading.
|
|
79
|
+
3. For a visual flow that crosses wrappers, explicitly select its concrete
|
|
80
|
+
nodes with `inspectPresentationAlignment`. Select one structural level rather
|
|
81
|
+
than both a semantic wrapper and its nested control. The API measures the
|
|
82
|
+
relationship; the product task authorizes the peer set.
|
|
83
|
+
4. For a box or composition that arranges different kinds of adjacent region,
|
|
84
|
+
inspect its consecutive immediate children with `inspectPresentationSpacing`.
|
|
85
|
+
Read every separation, not only repeated patterns or existing findings.
|
|
86
|
+
Compare the sequence of gaps and boundary strengths as one rhythm; then
|
|
87
|
+
descend into a child only to answer a separate internal-spacing question.
|
|
88
|
+
Do not stop after comparing the owner's outside gap with its internal median:
|
|
89
|
+
when a leading heading or structural label introduces repeated body blocks,
|
|
90
|
+
compare `leading → first body` with `body → next body`. Equal relationship
|
|
91
|
+
classes erase that level of the visual hierarchy even when the owner's outer
|
|
92
|
+
boundary remains stronger.
|
|
93
|
+
5. When product meaning identifies the roles, declare them with
|
|
94
|
+
`inspectPresentationHierarchy`: `owner-boundary`, `leading-to-body`,
|
|
95
|
+
`body-peer`, then `content-internal`. Do not derive those roles from token
|
|
96
|
+
names. The contract rejects reversed levels, duplicated roles, reused
|
|
97
|
+
relationships and nodes outside its owner.
|
|
98
|
+
6. Challenge a clean report once at the mapped hierarchy: identify a plausible
|
|
99
|
+
relationship class automatic inference did not compare. Stop when the map
|
|
100
|
+
contains no product-authorized peer set or relationship role left unmeasured;
|
|
101
|
+
do not manufacture intent to force a finding.
|
|
102
|
+
7. Isolate one finding id before paint. Add a pattern or measurement layer only
|
|
103
|
+
when it answers the same question.
|
|
104
|
+
8. When the work is part of a regression run, call `presentationSignal` with the
|
|
105
|
+
before and after reports plus their product-owned hierarchy readings. Return
|
|
106
|
+
it through the collector's `presentation` field. Do not translate it into a
|
|
107
|
+
render impact or change the observation verdict.
|
|
108
|
+
|
|
109
|
+
After sensing, the report becomes a structural map, then one owned relationship,
|
|
110
|
+
then evidence for a product decision. A finding can legitimately produce no edit
|
|
111
|
+
when the hierarchy explains it.
|
|
112
|
+
|
|
113
|
+
## Sense a live subject
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import type { Page } from '@playwright/test';
|
|
117
|
+
import { sensePresentation } from '@variance-authority/presentation/playwright';
|
|
118
|
+
|
|
119
|
+
declare const page: Page;
|
|
120
|
+
|
|
121
|
+
const report = await sensePresentation(page, page.getByRole('main'), {
|
|
122
|
+
subjectId: 'underwriting:demands',
|
|
123
|
+
title: 'Underwriting demands',
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The locator is the semantic and geometric boundary. Root `0` is that locator;
|
|
128
|
+
later roots are React portal content in component-tree order. Prefer a stable,
|
|
129
|
+
meaningful boundary such as `main`, a named region, or the product surface named
|
|
130
|
+
by the task. Do not widen to the whole document merely to collect more nodes.
|
|
131
|
+
|
|
132
|
+
Options:
|
|
133
|
+
|
|
134
|
+
- `subjectId`: stable report identity for the boundary. It defaults to
|
|
135
|
+
`presentation`.
|
|
136
|
+
- `title`: optional human-facing subject title.
|
|
137
|
+
- `fonts`: font identities the caller has independently established.
|
|
138
|
+
- `suspense.timeoutMs`: settlement bound for React Suspense before sensing.
|
|
139
|
+
- `stabilize`: optional intervention ids replacing the default recipe. Use `[]`
|
|
140
|
+
only when the caller owns a static document, such as an MHTML archive whose
|
|
141
|
+
page clock cannot advance.
|
|
142
|
+
- `paint`: omit for no overlay, use `true` for all layers, or provide selected
|
|
143
|
+
layer names.
|
|
144
|
+
|
|
145
|
+
The browser agent waits for Suspense, settles images inside the subject and its
|
|
146
|
+
portals rather than unrelated document images, applies the other collection
|
|
147
|
+
interventions, collects computed style and layout, reads Playwright's ARIA
|
|
148
|
+
snapshot for the subject and portals, analyzes the plain capture, and then
|
|
149
|
+
optionally paints from the returned report. It does not capture a PNG. Prefer an
|
|
150
|
+
unpainted first acquisition so the hierarchy can decide which evidence to show.
|
|
151
|
+
Do not use an empty stabilization recipe to make a live page faster; it is a
|
|
152
|
+
declaration that the document cannot move.
|
|
153
|
+
|
|
154
|
+
If the page agent bundle is missing, build the installed package or repository
|
|
155
|
+
before retrying. Do not replace a missing bundle with an empty or improvised
|
|
156
|
+
page script.
|
|
157
|
+
|
|
158
|
+
## Work headed with a user
|
|
159
|
+
|
|
160
|
+
The same live entry point works with a headed Playwright browser:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { chromium } from '@playwright/test';
|
|
164
|
+
import { focusPresentation } from '@variance-authority/presentation';
|
|
165
|
+
import {
|
|
166
|
+
clearPresentationPaint,
|
|
167
|
+
paintPresentationFocus,
|
|
168
|
+
sensePresentation,
|
|
169
|
+
} from '@variance-authority/presentation/playwright';
|
|
170
|
+
|
|
171
|
+
const browser = await chromium.launch({ headless: false, slowMo: 50 });
|
|
172
|
+
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
|
|
173
|
+
await page.goto('http://127.0.0.1:3000');
|
|
174
|
+
|
|
175
|
+
const subject = page.getByRole('main');
|
|
176
|
+
const report = await sensePresentation(page, subject);
|
|
177
|
+
const finding = report.findings?.[0];
|
|
178
|
+
if (finding !== undefined) {
|
|
179
|
+
const focus = focusPresentation(report, finding.owner, {
|
|
180
|
+
findings: [finding.id],
|
|
181
|
+
paint: ['findings'],
|
|
182
|
+
});
|
|
183
|
+
await paintPresentationFocus(page, focus);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await page.pause();
|
|
187
|
+
await clearPresentationPaint(page);
|
|
188
|
+
await browser.close();
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Use headed operation when the user asks to see the evidence, challenge a
|
|
192
|
+
grouping, or work through an edit together. Explain which layers are visible and
|
|
193
|
+
connect any painted mark to its report measurement id.
|
|
194
|
+
|
|
195
|
+
Current collaboration boundaries:
|
|
196
|
+
|
|
197
|
+
- The package accepts an existing headed page; it does not own an interactive
|
|
198
|
+
session, browser lifetime, layer picker, legend, or user annotations.
|
|
199
|
+
- The overlay has `pointer-events: none`, so it does not intercept interaction
|
|
200
|
+
with the application.
|
|
201
|
+
- Paint is a reading at one moment, not a reactive inspector. After content or
|
|
202
|
+
layout changes, call `sensePresentation` again. A new sense clears old paint
|
|
203
|
+
before acquisition.
|
|
204
|
+
- `clearPresentationPaint(page)` removes only the diagnostic SVG.
|
|
205
|
+
- Collection stabilization is deliberately left in the document and can keep
|
|
206
|
+
animations frozen. Clearing paint does not release it. Close or reload the
|
|
207
|
+
page when the user needs the untouched animated state; do not claim the
|
|
208
|
+
current API restores it.
|
|
209
|
+
- Keep a browser open only when the user asked for a headed session. A test or
|
|
210
|
+
script ending still closes the page under its caller's lifecycle.
|
|
211
|
+
|
|
212
|
+
## Analyze an existing capture
|
|
213
|
+
|
|
214
|
+
```ts
|
|
215
|
+
import type { AccessibilitySnapshot, RawCapture } from '@variance-authority/core';
|
|
216
|
+
import { analyzePresentation } from '@variance-authority/presentation';
|
|
217
|
+
|
|
218
|
+
declare const capture: RawCapture;
|
|
219
|
+
declare const accessibility: AccessibilitySnapshot | undefined;
|
|
220
|
+
|
|
221
|
+
const report = analyzePresentation(capture, {
|
|
222
|
+
...(accessibility === undefined ? {} : { accessibility }),
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The pure analyzer touches no live DOM. When `capture.profile.layout` is false,
|
|
227
|
+
it returns content identity, content telemetry, and semantic anchors while
|
|
228
|
+
layout-derived fields remain absent. If a profile claims layout but an element
|
|
229
|
+
rectangle is missing, analysis refuses the capture; an absent measurement is not
|
|
230
|
+
an empty box.
|
|
231
|
+
|
|
232
|
+
## Read the report
|
|
233
|
+
|
|
234
|
+
The report is deterministic JSON with these independent dimensions:
|
|
235
|
+
|
|
236
|
+
| Field | Meaning |
|
|
237
|
+
|---|---|
|
|
238
|
+
| `digest` | Identity of the entire presentation report, including retained browser ARIA when present |
|
|
239
|
+
| `contentDigest` | Layout-independent structure, text, DOM-correlated semantics, and state |
|
|
240
|
+
| `semantic.anchors` | DOM-correlated roles and accessible names attached to boundary-relative element references |
|
|
241
|
+
| `semantic.browserAccessibility` | Independent Playwright ARIA reading; absence alone means unobserved |
|
|
242
|
+
| `telemetry` | Neutral content, dimensions, utilization, occupied area, and density context |
|
|
243
|
+
| `graph.nodes` | Rendered elements with refs, semantics, state, rects, typography, surfaces, and relative prominence |
|
|
244
|
+
| `graph.relations` | Containment, separation, alignment, inferred-baseline, and semantic-peer relations |
|
|
245
|
+
| `spacing`, `axes`, `baselines`, `prominence`, `surfaces` | Derived measurable structures |
|
|
246
|
+
| `patterns` | Local repeated semantic shapes, recurring labels, dominant presentation signatures, and outliers |
|
|
247
|
+
| `findings` | Deterministic measured relationship failures |
|
|
248
|
+
| `paint` | Instructions used by the page overlay; absent when layout was unobserved |
|
|
249
|
+
|
|
250
|
+
For layout-derived fields, absence means layout was not observed. An empty array
|
|
251
|
+
means the dimension was measured and no entries were found. Never turn absence
|
|
252
|
+
into `[]` or zero.
|
|
253
|
+
|
|
254
|
+
Element references are stable within the boundary: `r0:0` is the subject,
|
|
255
|
+
`r0:0/2` is a descendant path, and `r1:0` begins the first portal root. Text
|
|
256
|
+
nodes occupy path positions but are not invented as rendered graph nodes.
|
|
257
|
+
|
|
258
|
+
Baselines are explicitly `inferred`. Treat them as browser geometry plus a
|
|
259
|
+
typographic approximation, not optical alignment.
|
|
260
|
+
|
|
261
|
+
Every finding carries a stable report-local `id` and an `owner`. Every paint
|
|
262
|
+
instruction carries an owner and touched nodes; finding and pattern paint also
|
|
263
|
+
carry their correlation ids. These fields are the route from a broad report to
|
|
264
|
+
one inspectable relationship.
|
|
265
|
+
|
|
266
|
+
## Focus one owner or visual flow
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
import {
|
|
270
|
+
focusPresentation,
|
|
271
|
+
inspectPresentationAlignment,
|
|
272
|
+
inspectPresentationHierarchy,
|
|
273
|
+
inspectPresentationSpacing,
|
|
274
|
+
} from '@variance-authority/presentation';
|
|
275
|
+
import {
|
|
276
|
+
paintPresentationAlignment,
|
|
277
|
+
paintPresentationFocus,
|
|
278
|
+
paintPresentationHierarchy,
|
|
279
|
+
paintPresentationSpacing,
|
|
280
|
+
} from '@variance-authority/presentation/playwright';
|
|
281
|
+
|
|
282
|
+
const owner = report.patterns?.find((pattern) => pattern.instances.length >= 3)?.parent;
|
|
283
|
+
if (owner === undefined) throw new Error('no repeated owner was observed');
|
|
284
|
+
|
|
285
|
+
const owned = focusPresentation(report, owner, {
|
|
286
|
+
paint: ['repetition', 'findings'],
|
|
287
|
+
});
|
|
288
|
+
const finding = owned.findings[0];
|
|
289
|
+
if (finding !== undefined) {
|
|
290
|
+
const isolated = focusPresentation(report, owner, {
|
|
291
|
+
findings: [finding.id],
|
|
292
|
+
paint: ['findings'],
|
|
293
|
+
});
|
|
294
|
+
await paintPresentationFocus(page, isolated);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const navFlow = inspectPresentationAlignment(
|
|
298
|
+
report,
|
|
299
|
+
'r0:0',
|
|
300
|
+
['r0:0/0/0', 'r0:0/1/0', 'r0:0/1/1'],
|
|
301
|
+
'vertical-center',
|
|
302
|
+
);
|
|
303
|
+
console.log(navFlow.spreadPx, navFlow.members);
|
|
304
|
+
await paintPresentationAlignment(page, navFlow);
|
|
305
|
+
|
|
306
|
+
const composition = report.graph.nodes.find((node) => node.parent === undefined)!;
|
|
307
|
+
const rhythm = inspectPresentationSpacing(
|
|
308
|
+
report,
|
|
309
|
+
composition.id,
|
|
310
|
+
composition.children,
|
|
311
|
+
'vertical',
|
|
312
|
+
);
|
|
313
|
+
console.log(rhythm.distance, rhythm.boundary, rhythm.separations);
|
|
314
|
+
await paintPresentationSpacing(page, rhythm);
|
|
315
|
+
|
|
316
|
+
const hierarchy = inspectPresentationHierarchy(report, {
|
|
317
|
+
id: 'record-hierarchy',
|
|
318
|
+
owner: composition.id,
|
|
319
|
+
axis: 'vertical',
|
|
320
|
+
levels: [
|
|
321
|
+
{ role: 'leading-to-body', relations: [{ from: 'r0:0/1/0', to: 'r0:0/1/1' }] },
|
|
322
|
+
{ role: 'body-peer', relations: [{ from: 'r0:0/1/1', to: 'r0:0/1/2' }] },
|
|
323
|
+
],
|
|
324
|
+
});
|
|
325
|
+
console.log(hierarchy.collisions, hierarchy.findings);
|
|
326
|
+
await paintPresentationHierarchy(page, hierarchy);
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`focusPresentation` refuses an unknown owner and refuses a requested finding
|
|
330
|
+
that is not owned at the selected depth. `inspectPresentationAlignment` refuses
|
|
331
|
+
fewer than two distinct members, unknown members and members outside the owner.
|
|
332
|
+
Its spread is neutral measurement, not a threshold or finding. Alignment paint
|
|
333
|
+
shows only the selected flow: one median axis and one deviation-labelled box per
|
|
334
|
+
member. Role or name matching can discover candidates, but it does not authorize
|
|
335
|
+
flattening a matching container and its matching descendant into the same flow.
|
|
336
|
+
|
|
337
|
+
`inspectPresentationSpacing` refuses descendants, skipped siblings and an axis
|
|
338
|
+
that does not describe the owned separations. Its range summaries do not replace
|
|
339
|
+
the sequence: several touching regions followed by one spacious boundary is
|
|
340
|
+
different evidence from a uniformly spaced composition with the same maximum.
|
|
341
|
+
Heterogeneous regions are not required to align or repeat; their shared owner is
|
|
342
|
+
what makes their adjacent spacing one inspectable relationship.
|
|
343
|
+
|
|
344
|
+
`inspectPresentationHierarchy` is the product-owned route when automatic
|
|
345
|
+
inference or a design system cannot establish meaning. Read every level and its
|
|
346
|
+
pair before the collisions. Equal token values across adjacent roles are
|
|
347
|
+
evidence of a collision, but token validity, popularity and naming do not alter
|
|
348
|
+
the result. Treat the contract as a falsifiable hypothesis: the owner, nodes,
|
|
349
|
+
axis and outside-in order must survive inspection of the rendered graph.
|
|
350
|
+
|
|
351
|
+
## Preserve ARIA as a separately sensitive signal
|
|
352
|
+
|
|
353
|
+
The DOM-correlated semantic anchors and Playwright's ARIA snapshot answer
|
|
354
|
+
different questions. Keep both.
|
|
355
|
+
|
|
356
|
+
- Omitting `accessibility` from pure analysis means browser ARIA was not
|
|
357
|
+
observed.
|
|
358
|
+
- An observed root with no exposed ARIA is retained as an empty root, commonly
|
|
359
|
+
`roots: ['']`.
|
|
360
|
+
- A boundary-relative root may expose no parent or no children. That is a
|
|
361
|
+
partial reading, not malformed evidence.
|
|
362
|
+
- Empty and partial readings participate in the report digest without repair.
|
|
363
|
+
- Do not synthesize missing parents, children, roles, or names.
|
|
364
|
+
- Do not turn this presentation workflow into a generic accessibility audit.
|
|
365
|
+
|
|
366
|
+
ARIA state such as `invalid`, `selected`, or `disabled` may explain a peer's
|
|
367
|
+
visual variance. The report retains the variance and marks the explanation; it
|
|
368
|
+
does not call the difference unexplained grammar drift.
|
|
369
|
+
|
|
370
|
+
## Interpret findings
|
|
371
|
+
|
|
372
|
+
| Rule | Evidence required |
|
|
373
|
+
|---|---|
|
|
374
|
+
| `SEPARATION_COLLISION` | Between-object boundary strength is indistinguishable from within-object boundaries |
|
|
375
|
+
| `SPACING_RELATION_COLLISION` | Different relationship classes occupy the same inferred spacing cluster or distribution |
|
|
376
|
+
| `SPACING_HIERARCHY_COLLISION` | A leading structural label is separated from its first body block exactly like ordinary body peers are separated from one another |
|
|
377
|
+
| `ALIGNMENT_OUTLIER` | One corresponding peer departs from a dominant alignment axis |
|
|
378
|
+
| `BASELINE_DRIFT` | One corresponding text-bearing peer departs from an inferred baseline |
|
|
379
|
+
| `PROMINENCE_COLLAPSE` | A heading class and an ordinary text class occupy the same prominence treatment |
|
|
380
|
+
| `SURFACE_COLLISION` | A meaningful painted surface has low perceptual difference from its container and no border or shadow contribution |
|
|
381
|
+
| `REPETITION_GRAMMAR_COLLAPSE` | Repeated objects have weak between-instance boundary evidence |
|
|
382
|
+
| `PRESENTATION_GRAMMAR_DRIFT` | A peer departs from a dominant signature without observed semantic state explaining it |
|
|
383
|
+
|
|
384
|
+
Read measurements before prose. Name the involved nodes, pattern, coordinates,
|
|
385
|
+
ratios, distances, clusters, or perceptual difference. Then connect that evidence
|
|
386
|
+
to the product task. Do not translate a finding into “looks wrong.”
|
|
387
|
+
|
|
388
|
+
Thresholds are analyzer calibration. They are not user-facing design targets.
|
|
389
|
+
A hierarchy-spacing finding is relational, not a preferred margin. Report the
|
|
390
|
+
`leadingToBodyGapMedianPx`, `bodyToBodyGapMedianPx`, ratio and shared-cluster
|
|
391
|
+
count. Its finding paint draws both relations for each affected instance, so the
|
|
392
|
+
equal steps remain visible instead of replacing them with one enclosing box.
|
|
393
|
+
|
|
394
|
+
A report with no findings is not a claim that the interface is good; it means no
|
|
395
|
+
implemented relationship rule fired on the observed evidence. In particular,
|
|
396
|
+
automatic relationship findings are local to inferred repeated patterns. Use an
|
|
397
|
+
explicit spacing reading when the hierarchy says heterogeneous siblings form a
|
|
398
|
+
composition whose rhythm must be inspected holistically.
|
|
399
|
+
|
|
400
|
+
## Paint evidence
|
|
401
|
+
|
|
402
|
+
Available layers are:
|
|
403
|
+
|
|
404
|
+
- `semantic`: graph-node rectangles with semantic labels.
|
|
405
|
+
- `spacing`: separation lines with distance and spacing-cluster labels.
|
|
406
|
+
- `axes`: discovered alignment axes.
|
|
407
|
+
- `baselines`: inferred text baselines.
|
|
408
|
+
- `surfaces`: painted-surface groups.
|
|
409
|
+
- `prominence`: relative-prominence clusters.
|
|
410
|
+
- `repetition`: every instance of each repeated pattern.
|
|
411
|
+
- `findings`: affected nodes emphasized by rule.
|
|
412
|
+
|
|
413
|
+
Start with one owner and one finding. For a repeated
|
|
414
|
+
wall of text, use `repetition`, `spacing`, `prominence`, and `findings`. For one
|
|
415
|
+
misplaced action, use `axes`, `baselines`, and `findings`. Use `paint: true` only
|
|
416
|
+
when the full diagnostic field is useful rather than visually overwhelming.
|
|
417
|
+
|
|
418
|
+
Paint colors identify diagnostic groups. They do not judge or reinterpret the
|
|
419
|
+
application's product colors.
|
|
420
|
+
|
|
421
|
+
## Re-sense after an edit
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
import { comparePresentation } from '@variance-authority/presentation';
|
|
425
|
+
|
|
426
|
+
const feedback = comparePresentation(before, after);
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Use comparison as optional edit feedback, not a stored baseline:
|
|
430
|
+
|
|
431
|
+
1. Sense the original subject and retain its report.
|
|
432
|
+
2. Make only the product-authorized edit.
|
|
433
|
+
3. Re-sense the same boundary under the same relevant environment.
|
|
434
|
+
4. Read finding deltas beside `information.content.preserved`, character count,
|
|
435
|
+
element count, and repeated-object count.
|
|
436
|
+
5. Paint the measurements responsible for any claimed relationship change.
|
|
437
|
+
|
|
438
|
+
`information.content.preserved` covers presentation-independent structure,
|
|
439
|
+
text, DOM-correlated semantics, state, and browser ARIA. It is derived before
|
|
440
|
+
layout analysis, so it remains sensitive when layout is unobserved. A false value
|
|
441
|
+
does not automatically reject the edit: the product task may require content or
|
|
442
|
+
state to change. It prevents that movement from being hidden behind fewer
|
|
443
|
+
presentation findings.
|
|
444
|
+
|
|
445
|
+
Do not call fewer findings an improvement when required information disappeared.
|
|
446
|
+
Do not call unchanged density a failure. Do not call changed density a success.
|
|
447
|
+
|
|
448
|
+
## Report to the user
|
|
449
|
+
|
|
450
|
+
Lead with what was sensed and what relationship evidence is available. Keep
|
|
451
|
+
independent signals visibly separate:
|
|
452
|
+
|
|
453
|
+
- presentation findings and their measurements;
|
|
454
|
+
- neutral density, size, utilization, and content telemetry;
|
|
455
|
+
- DOM-correlated semantic anchors;
|
|
456
|
+
- browser ARIA, including empty or partial readings;
|
|
457
|
+
- information identity across re-sensing;
|
|
458
|
+
- anything unobserved or externally unavailable.
|
|
459
|
+
|
|
460
|
+
When headed, say which paint layers the user can see and whether the page is
|
|
461
|
+
still stabilized. When an external fixture, URL, browser binary, or product task
|
|
462
|
+
is unavailable, mark that boundary unvalidated rather than substituting a
|
|
463
|
+
constructed page and claiming completion.
|