@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.
Files changed (67) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/LICENSE +21 -0
  3. package/README.md +319 -0
  4. package/dist/alignment.d.ts +9 -0
  5. package/dist/alignment.js +100 -0
  6. package/dist/alignment.js.map +1 -0
  7. package/dist/analyze.d.ts +20 -0
  8. package/dist/analyze.js +132 -0
  9. package/dist/analyze.js.map +1 -0
  10. package/dist/browser-agent-entry.d.ts +2 -0
  11. package/dist/browser-agent-entry.js +8 -0
  12. package/dist/browser-agent-entry.js.map +1 -0
  13. package/dist/browser-agent.bundle.js +3587 -0
  14. package/dist/browser-agent.d.ts +37 -0
  15. package/dist/browser-agent.js +130 -0
  16. package/dist/browser-agent.js.map +1 -0
  17. package/dist/bundle.d.ts +3 -0
  18. package/dist/bundle.js +20 -0
  19. package/dist/bundle.js.map +1 -0
  20. package/dist/compare.d.ts +4 -0
  21. package/dist/compare.js +49 -0
  22. package/dist/compare.js.map +1 -0
  23. package/dist/findings.d.ts +5 -0
  24. package/dist/findings.js +285 -0
  25. package/dist/findings.js.map +1 -0
  26. package/dist/focus.d.ts +17 -0
  27. package/dist/focus.js +84 -0
  28. package/dist/focus.js.map +1 -0
  29. package/dist/graph.d.ts +11 -0
  30. package/dist/graph.js +145 -0
  31. package/dist/graph.js.map +1 -0
  32. package/dist/hierarchy.d.ts +4 -0
  33. package/dist/hierarchy.js +171 -0
  34. package/dist/hierarchy.js.map +1 -0
  35. package/dist/index.d.ts +12 -0
  36. package/dist/index.js +8 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/math.d.ts +24 -0
  39. package/dist/math.js +124 -0
  40. package/dist/math.js.map +1 -0
  41. package/dist/measure.d.ts +15 -0
  42. package/dist/measure.js +216 -0
  43. package/dist/measure.js.map +1 -0
  44. package/dist/model.d.ts +346 -0
  45. package/dist/model.js +2 -0
  46. package/dist/model.js.map +1 -0
  47. package/dist/paint.d.ts +12 -0
  48. package/dist/paint.js +192 -0
  49. package/dist/paint.js.map +1 -0
  50. package/dist/patterns.d.ts +10 -0
  51. package/dist/patterns.js +148 -0
  52. package/dist/patterns.js.map +1 -0
  53. package/dist/playwright.d.ts +27 -0
  54. package/dist/playwright.js +108 -0
  55. package/dist/playwright.js.map +1 -0
  56. package/dist/report.d.ts +17 -0
  57. package/dist/report.js +126 -0
  58. package/dist/report.js.map +1 -0
  59. package/dist/spacing.d.ts +4 -0
  60. package/dist/spacing.js +112 -0
  61. package/dist/spacing.js.map +1 -0
  62. package/dist/telemetry.d.ts +4 -0
  63. package/dist/telemetry.js +73 -0
  64. package/dist/telemetry.js.map +1 -0
  65. package/mark.svg +30 -0
  66. package/package.json +62 -0
  67. 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.