UI library examples

These task workspaces use real component libraries to exercise geometry, portals, scrolling, overlays, hit testing, and dynamic layout. The hosted pages run in a browser; their repository tests run the same workflows in happy-dom with DOM Layout Shim attached.

Current compatibility findings

Agreement measures how often the shim matches Chromium across three equally weighted comparisons: geometry fields within 1 px, visibility state, and the element receiving a center-point hit test. A checkpoint percentage averages those three comparisons; overall agreement averages all checkpoints. Observation coverage is the share of declared elements captured in both environments. Differences are recorded for inspection and are not failures.

Material UI

@mui/material
87.1%Average of checkpoint agreement
100.0%Elements captured in both environments
7Interaction checkpoints
25Unique difference signatures

Measured with Chromium 151.0.7922.34 at 1280 × 720.

Use DOM Layout Shim with Material UITest setup and first assertion

The example uses ordinary Material UI components. DOM Layout Shim is attached only in the happy-dom test environment; no adapter or production application change is required.

1. Use happy-dom in Vitest

vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    // Give React a DOM-like window that the layout shim can patch.
    environment: 'happy-dom',
    include: ['test/**/*.test.tsx'],
    setupFiles: ['./test/setup.ts'],
  },
});
test/setup.ts

import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import {
  attachLayoutEngine,
  createUnsupportedCssReporter,
} from 'dom-layout-shim';

// Tell React that state updates are intentionally coordinated with `act()`.
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });

// Vitest transforms CSS imports without reliably installing their rules in
// happy-dom, so expose the same authored stylesheet that the browser app loads.
const exampleStyles = await readFile(
  resolve(process.cwd(), 'src/styles.css'),
  'utf8',
);
const style = window.document.createElement('style');
style.textContent = exampleStyles;
window.document.head.append(style);

// Attach once with shared defaults; tests can override only what they exercise.
export const unsupportedCssReporter = createUnsupportedCssReporter();
export const layoutEngine = await attachLayoutEngine({
  window,
  unsupportedCss: {
    default: 'warn',
    onWarning: unsupportedCssReporter.onWarning,
  },
});

2. Render React normally

test/task-workspace.test.tsx
// Mount the real Material UI tree exactly as the consumer test uses it.
document.body.innerHTML = '<div id="app"></div>';
reactRoot = createRoot(requiredElement('#app'));
await act(async () => reactRoot.render(<TaskWorkspace />));

3. Click where the element is

test/click-where-element-is.ts
export function clickWhereElementIs(element: HTMLElement): void {
  const rect = element.getBoundingClientRect();
  const x = rect.left + rect.width / 2;
  const y = rect.top + rect.height / 2;
  const hitTarget = element.ownerDocument.elementFromPoint(x, y);

  if (!hitTarget || (hitTarget !== element && !element.contains(hitTarget))) {
    throw new Error(
      `Expected ${element.tagName.toLowerCase()} to receive the click at (${x}, ${y})`,
    );
  }

  hitTarget.dispatchEvent(
    new MouseEvent('click', {
      bubbles: true,
      cancelable: true,
      clientX: x,
      clientY: y,
    }),
  );
}

The utility measures the element's center, asks the document which element owns that point, and refuses to click through an overlay. It dispatches the event to the actual hit target so descendants behave like a coordinate-based browser click. Call it inside React's act() to synchronize resulting state updates.

The example ignores unsupported declarations because Material UI emits many visual-only rules. Use the default warning policy, or narrow overrides, when unsupported declarations should remain visible in your own suite.

4. Assert the reachable element

test/task-workspace.test.tsx
const underlyingControl = requiredElement<HTMLElement>(
  '[data-layout-key="underlying-control"]',
);
const rect = underlyingControl.getBoundingClientRect();
// Query the visual center instead of bypassing layout with `.click()`.
const point = {
  x: rect.left + rect.width / 2,
  y: rect.top + rect.height / 2,
};

expect(rect.width).toBeGreaterThan(0);
expect(document.elementFromPoint(point.x, point.y)).toBe(underlyingControl);

The same approach works for Material UI portals: open the real menu or dialog, then query geometry and hit targets through normal DOM APIs.

Interaction checkpoints

Initial task list86.9%
Coverage Tracked elements captured in both environments
100.0%
Geometry x, y, width, and height fields within 1 px
60.7%
Visibility Displayed or hidden state matches Chromium
100.0%
Hit testing Center point resolves to the same element
100.0%

7 observations · 11 differences

  • geometry · height[data-layout-key="sidebar"]expected 444.03 · observed 476.05
  • geometry · y[data-layout-key="task-list"]expected 156.03 · observed 188.05
  • geometry · x[data-layout-key="underlying-control"]expected 992.08 · observed 995
  • geometry · y[data-layout-key="underlying-control"]expected 95.77 · observed 111.78
  • geometry · width[data-layout-key="underlying-control"]expected 103.92 · observed 101
  • geometry · y[data-layout-key="task-1"]expected 164.03 · observed 196.05
  • geometry · height[data-layout-key="task-1"]expected 73.02 · observed 68.22
  • geometry · y[data-layout-key="task-2"]expected 237.05 · observed 264.27
  • geometry · height[data-layout-key="task-2"]expected 73.02 · observed 68.22
  • geometry · y[data-layout-key="task-3"]expected 310.06 · observed 332.49
  • geometry · height[data-layout-key="task-3"]expected 73.02 · observed 68.22
Active filter87.5%
Coverage Tracked elements captured in both environments
100.0%
Geometry x, y, width, and height fields within 1 px
62.5%
Visibility Displayed or hidden state matches Chromium
100.0%
Hit testing Center point resolves to the same element
100.0%

6 observations · 9 differences

  • geometry · height[data-layout-key="sidebar"]expected 444.03 · observed 476.05
  • geometry · y[data-layout-key="task-list"]expected 156.03 · observed 188.05
  • geometry · x[data-layout-key="underlying-control"]expected 992.08 · observed 995
  • geometry · y[data-layout-key="underlying-control"]expected 95.77 · observed 111.78
  • geometry · width[data-layout-key="underlying-control"]expected 103.92 · observed 101
  • geometry · y[data-layout-key="task-1"]expected 164.03 · observed 196.05
  • geometry · height[data-layout-key="task-1"]expected 73.02 · observed 68.22
  • geometry · y[data-layout-key="task-3"]expected 237.05 · observed 264.27
  • geometry · height[data-layout-key="task-3"]expected 73.02 · observed 68.22
Add dialog open83.3%
Coverage Tracked elements captured in both environments
100.0%
Geometry x, y, width, and height fields within 1 px
66.7%
Visibility Displayed or hidden state matches Chromium
100.0%
Hit testing Center point resolves to the same element
83.3%

6 observations · 9 differences

  • geometry · height[data-layout-key="sidebar"]expected 444.03 · observed 476.05
  • geometry · y[data-layout-key="task-list"]expected 156.03 · observed 188.05
  • geometry · x[data-layout-key="underlying-control"]expected 992.08 · observed 995
  • geometry · y[data-layout-key="underlying-control"]expected 95.77 · observed 111.78
  • geometry · width[data-layout-key="underlying-control"]expected 103.92 · observed 101
  • geometry · y[data-layout-key="add-dialog"]expected 212.25 · observed 235.25
  • geometry · height[data-layout-key="add-dialog"]expected 295.5 · observed 249.5
  • geometry · y[data-layout-key="task-title-input"]expected 284.25 · observed 307.25
  • hit testing[data-layout-key="task-title-input"]expected [data-layout-key="task-title-input"] · observed [data-layout-key="add-dialog"]
Task added88.3%
Coverage Tracked elements captured in both environments
100.0%
Geometry x, y, width, and height fields within 1 px
65.0%
Visibility Displayed or hidden state matches Chromium
100.0%
Hit testing Center point resolves to the same element
100.0%

5 observations · 7 differences

  • geometry · height[data-layout-key="sidebar"]expected 444.03 · observed 476.05
  • geometry · y[data-layout-key="task-list"]expected 156.03 · observed 188.05
  • geometry · x[data-layout-key="underlying-control"]expected 992.08 · observed 995
  • geometry · y[data-layout-key="underlying-control"]expected 95.77 · observed 111.78
  • geometry · width[data-layout-key="underlying-control"]expected 103.92 · observed 101
  • geometry · y[data-layout-key="task-4"]expected 383.08 · observed 400.71
  • geometry · height[data-layout-key="task-4"]expected 73.02 · observed 68.22
Action menu open88.9%
Coverage Tracked elements captured in both environments
100.0%
Geometry x, y, width, and height fields within 1 px
66.7%
Visibility Displayed or hidden state matches Chromium
100.0%
Hit testing Center point resolves to the same element
100.0%

6 observations · 8 differences

  • geometry · height[data-layout-key="sidebar"]expected 444.03 · observed 476.05
  • geometry · y[data-layout-key="task-list"]expected 156.03 · observed 188.05
  • geometry · x[data-layout-key="underlying-control"]expected 992.08 · observed 995
  • geometry · y[data-layout-key="underlying-control"]expected 95.77 · observed 111.78
  • geometry · width[data-layout-key="underlying-control"]expected 103.92 · observed 101
  • geometry · x[data-layout-key="delete-action"]expected 1031 · observed 1020
  • geometry · y[data-layout-key="delete-action"]expected 230 · observed 260
  • geometry · width[data-layout-key="delete-action"]expected 113.7 · observed 123.25
Delete dialog open87.5%
Coverage Tracked elements captured in both environments
100.0%
Geometry x, y, width, and height fields within 1 px
62.5%
Visibility Displayed or hidden state matches Chromium
100.0%
Hit testing Center point resolves to the same element
100.0%

6 observations · 9 differences

  • geometry · height[data-layout-key="sidebar"]expected 444.03 · observed 476.05
  • geometry · y[data-layout-key="task-list"]expected 156.03 · observed 188.05
  • geometry · x[data-layout-key="underlying-control"]expected 992.08 · observed 995
  • geometry · y[data-layout-key="underlying-control"]expected 95.77 · observed 111.78
  • geometry · width[data-layout-key="underlying-control"]expected 103.92 · observed 101
  • geometry · x[data-layout-key="delete-dialog"]expected 346.91 · observed 340
  • geometry · y[data-layout-key="delete-dialog"]expected 279.75 · observed 267.75
  • geometry · width[data-layout-key="delete-dialog"]expected 586.19 · observed 600
  • geometry · height[data-layout-key="delete-dialog"]expected 160.5 · observed 184.5
Dialog dismissed87.5%
Coverage Tracked elements captured in both environments
100.0%
Geometry x, y, width, and height fields within 1 px
62.5%
Visibility Displayed or hidden state matches Chromium
100.0%
Hit testing Center point resolves to the same element
100.0%

6 observations · 9 differences

  • geometry · height[data-layout-key="sidebar"]expected 444.03 · observed 476.05
  • geometry · y[data-layout-key="task-list"]expected 156.03 · observed 188.05
  • geometry · x[data-layout-key="underlying-control"]expected 992.08 · observed 995
  • geometry · y[data-layout-key="underlying-control"]expected 95.77 · observed 111.78
  • geometry · width[data-layout-key="underlying-control"]expected 103.92 · observed 101
  • geometry · y[data-layout-key="task-1"]expected 164.03 · observed 196.05
  • geometry · height[data-layout-key="task-1"]expected 73.02 · observed 68.22
  • geometry · y[data-layout-key="task-4"]expected 383.08 · observed 400.71
  • geometry · height[data-layout-key="task-4"]expected 73.02 · observed 68.22
Most repeated layout differences
  • geometry · height [data-layout-key="sidebar"] 7 checkpoints
  • geometry · y [data-layout-key="task-list"] local-layout 7 checkpoints
  • geometry · x [data-layout-key="underlying-control"] local-layout 7 checkpoints
  • geometry · y [data-layout-key="underlying-control"] local-layout 7 checkpoints
  • geometry · width [data-layout-key="underlying-control"] 7 checkpoints
  • geometry · y [data-layout-key="task-1"] ancestor-offset 3 checkpoints
  • geometry · height [data-layout-key="task-1"] 3 checkpoints
  • geometry · height [data-layout-key="task-3"] 2 checkpoints
Unsupported CSS observed (20 properties)

Ordered by likely layout impact and how often declarations were encountered. Computed values help identify fallback declarations that did not win the cascade.

  • observed layout displayAuthored: boxComputed: flex, inline-flex 25 occurrences
  • observed unclassified box-alignAuthored: center 17 occurrences
  • observed unclassified flex-alignAuthored: center 17 occurrences
  • observed unclassified box-packAuthored: center, end, justify… 16 occurrences
  • observed unclassified flex-packAuthored: center, end, start 15 occurrences
  • observed unclassified -webkit-tap-highlight-colorAuthored: transparentComputed: transparent 9 occurrences
  • observed unclassified -webkit-overflow-scrollingAuthored: touchComputed: touch 3 occurrences
  • observed unclassified flex-negativeAuthored: 0 3 occurrences
  • observed unclassified text-alignAuthored: center, leftComputed: center, left 3 occurrences
  • observed unclassified text-overflowAuthored: ellipsisComputed: ellipsis 3 occurrences
  • observed unclassified animation-durationComputed: 10ms1 complex parsed value available in the JSON report 2 occurrences
  • observed unclassified animation-nameComputed: mui-auto-fill-cancel1 complex parsed value available in the JSON report 2 occurrences

Ant Design

antd
90.1%Average of checkpoint agreement
100.0%Elements captured in both environments
7Interaction checkpoints
20Unique difference signatures

Measured with Chromium 151.0.7922.34 at 1280 × 720.

Use DOM Layout Shim with Ant DesignTest setup and first assertion

The example mounts real Ant Design components and portals. DOM Layout Shim is a test-only attachment; the application does not import it or use an Ant-specific adapter.

1. Configure happy-dom and React tests

vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    // Give React a DOM-like window that the layout shim can patch.
    environment: 'happy-dom',
    include: ['test/**/*.test.tsx'],
    setupFiles: ['./test/setup.ts'],
    // Real Ant Design observer and animation phases can exceed Vitest's 5s
    // default on slower CI runners while still settling normally in auto mode.
    testTimeout: 10_000,
  },
});
test/setup.ts

import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import {
  attachLayoutEngine,
  createUnsupportedCssReporter,
} from 'dom-layout-shim';

// Tell React that state updates are intentionally coordinated with `act()`.
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });

// Vitest transforms CSS imports without adding them to happy-dom, so install the
// example's authored stylesheet before the layout engine reads computed styles.
const exampleStyles = await readFile(
  resolve(process.cwd(), 'src/styles.css'),
  'utf8',
);
const style = window.document.createElement('style');
style.textContent = exampleStyles;
window.document.head.append(style);

// Attach once with shared defaults; tests can override only what they exercise.
export const unsupportedCssReporter = createUnsupportedCssReporter();
export const layoutEngine = await attachLayoutEngine({
  window,
  // Exercise the default automatic observer delivery used by consumers. Parsed
  // stylesheet rules are reused across Ant Design's observer-driven DOM phases.
  unsupportedCss: {
    default: 'warn',
    onWarning: unsupportedCssReporter.onWarning,
  },
});

2. Mount the application normally

test/task-workspace.test.tsx
// Mount the real Ant Design tree, including its normal portal behavior.
document.body.innerHTML = '<main id="app"></main>';
const container = document.querySelector('#app');
if (!container) throw new Error('Missing example application root');
await act(async () => {
  root = mountTaskWorkspace(container);
  await nextAnimationFrame();
});

3. Click where the element is

test/click-where-element-is.ts
export function clickWhereElementIs(element: HTMLElement): void {
  const rect = element.getBoundingClientRect();
  const x = rect.left + rect.width / 2;
  const y = rect.top + rect.height / 2;
  const hitTarget = element.ownerDocument.elementFromPoint(x, y);

  if (!hitTarget || (hitTarget !== element && !element.contains(hitTarget))) {
    throw new Error(
      `Expected ${element.tagName.toLowerCase()} to receive the click at (${x}, ${y})`,
    );
  }

  hitTarget.dispatchEvent(
    new MouseEvent('click', {
      bubbles: true,
      cancelable: true,
      clientX: x,
      clientY: y,
    }),
  );
}

The utility measures the element's center, asks the document which element owns that point, and refuses to click through an overlay. It dispatches the event to the actual hit target so descendants behave like a coordinate-based browser click. Call it inside React's act() to synchronize resulting state updates.

The example ignores unsupported declarations because Ant Design emits many visual-only rules. Use the default warning policy, or narrow overrides, when unsupported declarations should remain visible in your own suite.

4. Use geometry-aware assertions

Before opening the modal:

test/task-workspace.test.tsx
// This helper clicks the geometry-derived center rather than bypassing layout.
expectReceivesPointer(addTask);

After opening the real Ant Design modal:

test/task-workspace.test.tsx
// Ant Design places its full-screen interaction wrapper above the visual mask.
// Verify that this real modal layer wins the same point while the modal is open.
expectBlockedBy(addTask, modalWrap);

The helpers derive interaction points from element geometry, so the test checks whether overlays actually cover or expose a control instead of bypassing layout with a direct .click().

Interaction checkpoints

Initial task list97.6%
Coverage Tracked elements captured in both environments
100.0%
Geometry x, y, width, and height fields within 1 px
92.9%
Visibility Displayed or hidden state matches Chromium
100.0%
Hit testing Center point resolves to the same element
100.0%

7 observations · 2 differences

  • geometry · x[data-layout-key="add-task"]expected 983.52 · observed 982
  • geometry · width[data-layout-key="add-task"]expected 108.48 · observed 110
Active filter97.2%
Coverage Tracked elements captured in both environments
100.0%
Geometry x, y, width, and height fields within 1 px
91.7%
Visibility Displayed or hidden state matches Chromium
100.0%
Hit testing Center point resolves to the same element
100.0%

6 observations · 2 differences

  • geometry · x[data-layout-key="add-task"]expected 983.52 · observed 982
  • geometry · width[data-layout-key="add-task"]expected 108.48 · observed 110
Add dialog open76.4%
Coverage Tracked elements captured in both environments
100.0%
Geometry x, y, width, and height fields within 1 px
79.2%
Visibility Displayed or hidden state matches Chromium
100.0%
Hit testing Center point resolves to the same element
50.0%

6 observations · 8 differences

  • hit testing[data-layout-key="task-workspace"]expected [data-layout-key="task-title-input"] · observed [data-layout-key="task-description-input"]
  • hit testing[data-layout-key="task-list"]expected [data-layout-key="add-dialog"] · observed [data-layout-key="task-description-input"]
  • geometry · x[data-layout-key="add-task"]expected 983.52 · observed 982
  • geometry · width[data-layout-key="add-task"]expected 108.48 · observed 110
  • geometry · y[data-layout-key="add-dialog"]expected 624 · observed 752
  • hit testing[data-layout-key="add-dialog"]expected [data-layout-key="add-dialog"] · observed null
  • geometry · y[data-layout-key="task-title-input"]expected 302 · observed 272
  • geometry · height[data-layout-key="task-title-input"]expected 32 · observed 23
Task added96.7%
Coverage Tracked elements captured in both environments
100.0%
Geometry x, y, width, and height fields within 1 px
90.0%
Visibility Displayed or hidden state matches Chromium
100.0%
Hit testing Center point resolves to the same element
100.0%

5 observations · 2 differences

  • geometry · x[data-layout-key="add-task"]expected 983.52 · observed 982
  • geometry · width[data-layout-key="add-task"]expected 108.48 · observed 110
Action menu open75.0%
Coverage Tracked elements captured in both environments
100.0%
Geometry x, y, width, and height fields within 1 px
58.3%
Visibility Displayed or hidden state matches Chromium
100.0%
Hit testing Center point resolves to the same element
66.7%

6 observations · 12 differences

  • geometry · x[data-layout-key="add-task"]expected 983.52 · observed 982
  • geometry · width[data-layout-key="add-task"]expected 108.48 · observed 110
  • geometry · x.ant-dropdownexpected 938.95 · observed -12800
  • geometry · y.ant-dropdownexpected 220 · observed -6448
  • geometry · width.ant-dropdownexpected 124.05 · observed 340
  • geometry · height.ant-dropdownexpected 40 · observed 168
  • hit testing.ant-dropdownexpected span · observed li
  • geometry · x.ant-dropdown-menu-item-dangerexpected 942.95 · observed -12796
  • geometry · y.ant-dropdown-menu-item-dangerexpected 224 · observed -6444
  • geometry · width.ant-dropdown-menu-item-dangerexpected 116.05 · observed 332
  • geometry · height.ant-dropdown-menu-item-dangerexpected 32 · observed 160
  • hit testing.ant-dropdown-menu-item-dangerexpected span · observed li
Delete dialog open90.3%
Coverage Tracked elements captured in both environments
100.0%
Geometry x, y, width, and height fields within 1 px
87.5%
Visibility Displayed or hidden state matches Chromium
100.0%
Hit testing Center point resolves to the same element
83.3%

6 observations · 4 differences

  • geometry · x[data-layout-key="add-task"]expected 983.52 · observed 982
  • geometry · width[data-layout-key="add-task"]expected 108.48 · observed 110
  • geometry · y[data-layout-key="delete-dialog"]expected 624 · observed 771.2
  • hit testing[data-layout-key="delete-dialog"]expected [data-layout-key="delete-dialog"] · observed null
Dialog dismissed97.2%
Coverage Tracked elements captured in both environments
100.0%
Geometry x, y, width, and height fields within 1 px
91.7%
Visibility Displayed or hidden state matches Chromium
100.0%
Hit testing Center point resolves to the same element
100.0%

6 observations · 2 differences

  • geometry · x[data-layout-key="add-task"]expected 983.52 · observed 982
  • geometry · width[data-layout-key="add-task"]expected 108.48 · observed 110
Most repeated layout differences
  • geometry · x [data-layout-key="add-task"] local-layout 7 checkpoints
  • geometry · width [data-layout-key="add-task"] 7 checkpoints
  • geometry · x .ant-dropdown local-layout 1 checkpoint
  • geometry · y .ant-dropdown local-layout 1 checkpoint
  • geometry · width .ant-dropdown 1 checkpoint
  • geometry · height .ant-dropdown 1 checkpoint
  • hit-testing .ant-dropdown 1 checkpoint
  • geometry · x .ant-dropdown-menu-item-danger ancestor-offset 1 checkpoint
Unsupported CSS observed (26 properties)

Ordered by likely layout impact and how often declarations were encountered. Computed values help identify fallback declarations that did not win the cascade.

  • observed layout widthAuthored: var(--ant-modal-lg-width), var(--ant-modal-md-width), var(--ant-modal-sm-width)…Computed: 520px 5 occurrences
  • observed layout borderAuthored: var(--ant-btn-border-width) var(--ant-btn-border-style) var(--ant-btn-border-color), var(--ant-line-width) var(--ant-line-type) var(--ant-color-border)Computed: 1px solid #d9d9d9, 1px solid transparent 2 occurrences
  • observed layout font-sizeAuthored: var(--ant-button-only-icon-size)Computed: inherit 1 occurrence
  • observed layout vertical-alignComputed: middle1 complex parsed value available in the JSON report 1 occurrence
  • observed unclassified text-alignAuthored: center, end, left…Computed: center, end, left… 6 occurrences
  • observed unclassified animation-durationAuthored: var(--ant-motion-duration-mid), var(--ant-motion-duration-slow)Computed: 0s 3 occurrences
  • observed unclassified animation-fill-modeAuthored: ["both"]Computed: both 2 occurrences
  • observed unclassified animation-play-stateAuthored: ["paused"]Computed: paused 2 occurrences
  • observed unclassified animation-timing-functionAuthored: var(--ant-motion-ease-out-circ), var(--ant-motion-ease-out-quint)Computed: cubic-bezier(0.08, 0.82, 0.17, 1), cubic-bezier(0.23, 1, 0.32, 1) 2 occurrences
  • observed unclassified border-block-endAuthored: var(--ant-line-width) var(--ant-line-type) var(--ant-color-split)Computed: 1px solid rgba(5,5,5,0.06), none1 complex parsed value available in the JSON report 2 occurrences
  • observed unclassified font-styleComputed: normal1 complex parsed value available in the JSON report 2 occurrences
  • observed unclassified text-renderingAuthored: auto, optimizelegibilityComputed: auto, optimizeLegibility 2 occurrences

These summaries are generated by running the same scripted interaction in Chromium and happy-dom with DOM Layout Shim. Agreement is observational rather than a pass threshold: expand any checkpoint to inspect its recorded geometry, visibility, or hit-testing differences.