@polyxd/verifier 0.2.2 → 0.4.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/NOTICE CHANGED
@@ -135,6 +135,51 @@ Upstream: https://github.com/adobe/spectrum-tokens
135
135
 
136
136
  Vendored under `packages/ds-spectrum/scripts/sources/`, with the upstream licence file beside it.
137
137
 
138
+ ## Logos
139
+
140
+ Twelve packs ship their design system's official logo, unaltered, as `logo.svg`
141
+ (Bootstrap and Radix also ship the owner's white version as `logo-white.svg`),
142
+ downloaded from the owner's own site, repository or brand page. Where a design
143
+ system has no mark of its own (Polaris, Spectrum, Primer), the pack ships its
144
+ company's logo. Each is used only to identify the design system a pack is
145
+ modelled on, beside its name. The logos are trademarks of their owners, not
146
+ Polyxd's, and are not covered by Polyxd's licence:
147
+
148
+ - Material 3 (`@polyxd/ds-material3`) — Google LLC
149
+ - Carbon (`@polyxd/ds-carbon`) — IBM Corp.
150
+ - Fluent 2 (`@polyxd/ds-fluent`) — Microsoft Corporation
151
+ - Polaris (`@polyxd/ds-polaris`) — the Shopify logo, Shopify Inc.
152
+ - Spectrum 2 (`@polyxd/ds-spectrum`) — the Adobe logo, Adobe Inc.
153
+ - Primer (`@polyxd/ds-primer`) — the GitHub logo, GitHub, Inc.
154
+ - shadcn/ui (`@polyxd/ds-shadcn`) — shadcn
155
+ - Ant Design (`@polyxd/ds-antd`) — Ant Group
156
+ - Bootstrap (`@polyxd/ds-bootstrap`) — the Bootstrap team
157
+ - Chakra UI (`@polyxd/ds-chakra`) — Chakra Systems Inc.
158
+ - Mantine (`@polyxd/ds-mantine`) — Vitaly Rtishchev
159
+ - Radix UI (`@polyxd/ds-radix`) — WorkOS
160
+
161
+ Google, IBM, Microsoft, Shopify, Adobe and GitHub restrict the use of their
162
+ logos without permission; each pack's manifest (`logo`) and README record the
163
+ source and the owner's guidelines. GOV.UK's crown and logotype are protected and
164
+ may not be used by a service that is not on GOV.UK, so `@polyxd/ds-govuk` ships
165
+ no logo and is named in words only.
166
+
167
+ The twelve template packs' `logo.svg` files are Polyxd's own marks, drawn from
168
+ each template's tokens.
169
+
170
+ ## Template packs
171
+
172
+ Twelve packs are original Polyxd templates, not readings of any design system:
173
+ `@polyxd/ds-sketch`, `@polyxd/ds-wireframe`, `@polyxd/ds-editorial`,
174
+ `@polyxd/ds-brutalist`, `@polyxd/ds-glass`, `@polyxd/ds-terminal`,
175
+ `@polyxd/ds-pastel`, `@polyxd/ds-civic`, `@polyxd/ds-finance`,
176
+ `@polyxd/ds-health`, `@polyxd/ds-neon` and `@polyxd/ds-mono`. Every value in
177
+ them was chosen for the template, they vendor nothing, and they are Apache-2.0
178
+ like the rest of Polyxd. The typefaces some of them name (Caveat, Patrick Hand,
179
+ Fraunces, Nunito, JetBrains Mono, Space Grotesk) are under the SIL Open Font
180
+ License and are loaded from Google Fonts by the page that uses them, never
181
+ distributed here.
182
+
138
183
  ## Specification
139
184
 
140
185
  ### A2UI
package/README.md CHANGED
@@ -3,11 +3,25 @@
3
3
  Scores a Polyxd UI document the way it will actually be used: rendered, in every design system, by people and by agents.
4
4
 
5
5
  ```bash
6
- npm install -D @polyxd/verifier
6
+ npm install -D @polyxd/verifier playwright
7
7
  npx playwright install chromium # once
8
8
  npx polyxd-verify my-ui.json --themes carbon --modes light --widths 390 --json report.json
9
9
  ```
10
10
 
11
+ Playwright is an optional peer dependency: the rendered checks need it, so install it beside the verifier as above. Nothing loads it until a browser is launched, and without it `launch()` says what to install.
12
+
13
+ ## Document checks only
14
+
15
+ `@polyxd/verifier/static` is the document checks on their own: `staticAudit(doc, { registry, rules, emphasisBudget, missingData })`. It imports no Playwright and reads no files (the spec's pattern checks are built into it), so it runs in Node, browsers and Workers, and installing the verifier for it alone doesn't pull in Playwright.
16
+
17
+ ```ts
18
+ import { staticAudit } from "@polyxd/verifier/static";
19
+
20
+ const findings = staticAudit(doc, { registry });
21
+ ```
22
+
23
+ `@polyxd/runtime` uses it as its default checks, and `@polyxd/mcp` and `@polyxd/server` use it for their verify tools. `npm run build:patterns -w @polyxd/verifier` regenerates the pattern checks from the spec; a test fails when they are out of date.
24
+
11
25
  Inside the Polyxd repository, `npm run verify:examples -w @polyxd/verifier` runs every spec example through the full matrix with agent tasks.
12
26
 
13
27
  ## What it checks
@@ -21,6 +35,12 @@ Inside the Polyxd repository, `npm run verify:examples -w @polyxd/verifier` runs
21
35
 
22
36
  Default matrix: Material 3, Carbon and Ant Design × light and dark × 390px and 1100px.
23
37
 
38
+ ## Renderers
39
+
40
+ The verifier renders through a **harness**: a page that draws `window.__PXD__ = { document, theme, mode }` into `#root` and records what the surface sends (`harness/README.md` is the contract). Two ship: the React renderer's (`harness/`) and the Web Components renderer's (`harness-web/`, `@polyxd/web`'s preview bundle). Pass any renderer's page as `verifyDocument(doc, { harness: { url } | { html } })`; `HARNESSES.react` and `HARNESSES.web` name the shipped ones.
41
+
42
+ `npm run conformance -w @polyxd/verifier` renders every spec example and every demo document with both shipped renderers in 13 packs × light/dark × 390/1100 and compares the rendered fingerprint (`fingerprint: true` on `verifyDocument`: components in order, ARIA snapshot, visible text; `compareFingerprints` explains a difference), the findings and the agent tasks. Both must score 100 and the fingerprints must match.
43
+
24
44
  ## Score (v0)
25
45
 
26
46
  Start at 100. Each distinct failing error check costs 20, each distinct warning 4, and failed agent runs cost up to 40 in proportion. A document that fails the schema scores 0. The weights are a starting point; the gold set (`bench/gold`) exists to calibrate them against designers' rankings.
package/dist/browser.d.ts CHANGED
@@ -1,16 +1,33 @@
1
- import { type Browser, type Page } from "playwright";
1
+ import type { Browser, Page } from "playwright";
2
+ /**
3
+ * A page that renders `window.__PXD__` (see harness/README.md): the built-in React harness, the
4
+ * Web Components harness, or any renderer's own page, by URL or as a directory of static files.
5
+ */
6
+ export type RendererHarness = {
7
+ name?: string;
8
+ url: string;
9
+ } | {
10
+ name?: string;
11
+ html: string | URL;
12
+ };
13
+ /** The harnesses this package ships, built by `npm run build:harness`. */
14
+ export declare const HARNESSES: Record<"react" | "web", RendererHarness>;
2
15
  export interface RenderTarget {
3
16
  theme: string;
4
17
  mode: "light" | "dark";
5
18
  /** Viewport width in CSS pixels, e.g. 390 (phone) or 1100 (desktop) */
6
19
  width: number;
7
20
  }
21
+ /**
22
+ * Starts headless Chromium for the rendered checks. Playwright is an optional peer dependency, so
23
+ * nothing loads it until now: the document checks (`@polyxd/verifier/static`) never need it.
24
+ */
8
25
  export declare function launch(): Promise<Browser>;
9
26
  /**
10
27
  * Opens a page with one document rendered in one theme/mode/width. The harness is served from
11
28
  * disk through request interception, so nothing listens on a port.
12
29
  */
13
- export declare function renderPage(browser: Browser, document: unknown, target: RenderTarget): Promise<{
30
+ export declare function renderPage(browser: Browser, document: unknown, target: RenderTarget, harness?: RendererHarness): Promise<{
14
31
  page: Page;
15
32
  errors: string[];
16
33
  }>;
package/dist/browser.js CHANGED
@@ -1,37 +1,70 @@
1
1
  import { readFile } from "node:fs/promises";
2
2
  import { createRequire } from "node:module";
3
3
  import { extname } from "node:path";
4
- import { chromium } from "playwright";
5
- const HARNESS = new URL("../harness-dist/", import.meta.url);
6
4
  const ORIGIN = "http://harness.polyxd.local";
5
+ /** The harnesses this package ships, built by `npm run build:harness`. */
6
+ export const HARNESSES = {
7
+ react: { name: "react", html: new URL("../harness-dist/index.html", import.meta.url) },
8
+ web: { name: "web", html: new URL("../harness-web-dist/index.html", import.meta.url) },
9
+ };
7
10
  const TYPES = { ".html": "text/html", ".js": "text/javascript", ".css": "text/css", ".svg": "image/svg+xml" };
8
- const axeSource = readFile(createRequire(import.meta.url).resolve("axe-core/axe.min.js"), "utf8");
11
+ let axeSource;
12
+ const axe = () => (axeSource ??= readFile(createRequire(import.meta.url).resolve("axe-core/axe.min.js"), "utf8"));
13
+ /**
14
+ * Starts headless Chromium for the rendered checks. Playwright is an optional peer dependency, so
15
+ * nothing loads it until now: the document checks (`@polyxd/verifier/static`) never need it.
16
+ */
9
17
  export async function launch() {
10
- return chromium.launch();
18
+ let playwright;
19
+ try {
20
+ playwright = await import("playwright");
21
+ }
22
+ catch (err) {
23
+ if (err.code !== "ERR_MODULE_NOT_FOUND")
24
+ throw err;
25
+ throw new Error("The rendered checks need Playwright, which @polyxd/verifier doesn't install for you: run `npm install -D playwright && npx playwright install chromium`. The document checks alone are in @polyxd/verifier/static.");
26
+ }
27
+ return playwright.chromium.launch();
11
28
  }
12
29
  /**
13
30
  * Opens a page with one document rendered in one theme/mode/width. The harness is served from
14
31
  * disk through request interception, so nothing listens on a port.
15
32
  */
16
- export async function renderPage(browser, document, target) {
33
+ export async function renderPage(browser, document, target, harness = HARNESSES.react) {
17
34
  const page = await browser.newPage({ viewport: { width: target.width, height: 900 }, reducedMotion: "reduce" });
18
35
  const errors = [];
19
36
  page.on("pageerror", (e) => errors.push(e.message));
20
37
  page.on("console", (m) => m.type() === "error" && errors.push(m.text()));
21
- await page.route(`${ORIGIN}/**`, async (route) => {
22
- const path = new URL(route.request().url()).pathname.slice(1) || "index.html";
23
- try {
24
- const body = await readFile(new URL(path, HARNESS));
25
- await route.fulfill({ body, contentType: TYPES[extname(path)] ?? "application/octet-stream" });
26
- }
27
- catch {
28
- await route.fulfill({ status: 404 });
29
- }
30
- });
38
+ let url;
39
+ if ("url" in harness)
40
+ url = harness.url;
41
+ else {
42
+ // A directory of static files is served from disk through request interception, so nothing listens on a port.
43
+ const index = harness.html instanceof URL ? harness.html : new URL(harness.html, `file://${process.cwd()}/`);
44
+ const dir = new URL("./", index);
45
+ await page.route(`${ORIGIN}/**`, async (route) => {
46
+ const path = new URL(route.request().url()).pathname.slice(1) || "index.html";
47
+ try {
48
+ const body = await readFile(new URL(path, dir));
49
+ await route.fulfill({ body, contentType: TYPES[extname(path)] ?? "application/octet-stream" });
50
+ }
51
+ catch {
52
+ await route.fulfill({ status: 404 });
53
+ }
54
+ });
55
+ url = `${ORIGIN}/${index.pathname.split("/").pop() ?? "index.html"}`;
56
+ }
31
57
  await page.addInitScript((cfg) => (window.__PXD__ = cfg), { document, theme: target.theme, mode: target.mode });
32
- await page.goto(`${ORIGIN}/index.html`);
58
+ await page.goto(url);
33
59
  await page.waitForFunction(() => window.__pxdReady === true, undefined, { timeout: 10_000 });
34
- await page.addScriptTag({ content: await axeSource });
60
+ // Entrances (a panel fading in) finish before anything is measured, so a contrast or layout
61
+ // reading never depends on how far into an animation a renderer's first paint was. Endless
62
+ // animations (a spinner) are left running.
63
+ await page.evaluate(() => {
64
+ const doc = globalThis.document;
65
+ return Promise.all(doc.getAnimations().filter((a) => a.effect?.getTiming().iterations !== Infinity).map((a) => a.finished.catch(() => undefined)));
66
+ });
67
+ await page.addScriptTag({ content: await axe() });
35
68
  return { page, errors };
36
69
  }
37
70
  /** Actions the rendered UI has sent to the host so far. */
@@ -1 +1 @@
1
- {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import type { Page } from "playwright";
2
+ /**
3
+ * What a rendered surface is, independent of who rendered it: the components in reading order,
4
+ * the accessibility tree (roles, names, states, in order) and the visible text. Two renderers
5
+ * are conformant when a document gives the same fingerprint in each, in every pack, mode and
6
+ * width. This is stricter than consistency.ts's signature, which reads the document; this
7
+ * reads the page.
8
+ */
9
+ export interface Fingerprint {
10
+ /** [component id, component name] for every element a component rendered, in DOM order */
11
+ components: [string, string][];
12
+ /** Playwright's ARIA snapshot of the surface: roles and accessible names in reading order */
13
+ aria: string;
14
+ /** The surface's visible text, whitespace collapsed */
15
+ text: string;
16
+ }
17
+ export declare function renderedFingerprint(page: Page): Promise<Fingerprint>;
18
+ /** The differences between two fingerprints, in words; empty when they match. */
19
+ export declare function compareFingerprints(a: Fingerprint, b: Fingerprint, aName?: string, bName?: string): string[];
@@ -0,0 +1,40 @@
1
+ export async function renderedFingerprint(page) {
2
+ const surface = page.locator(".pxd-surface").first();
3
+ const [components, text] = await page.evaluate(() => {
4
+ const root = document.querySelector(".pxd-surface");
5
+ const components = [...root.querySelectorAll("[data-pxd-id]")].map((el) => [el.dataset.pxdId, el.dataset.pxdComponent ?? ""]);
6
+ return [components, root.innerText.replace(/\s+/g, " ").trim()];
7
+ });
8
+ const aria = normaliseAria(await surface.ariaSnapshot());
9
+ return { components, aria, text };
10
+ }
11
+ /** Generated ids differ per renderer (React's :r1:, the Web renderer's pxd-…); they name nothing a person sees. */
12
+ function normaliseAria(snapshot) {
13
+ return snapshot
14
+ .split("\n")
15
+ .map((l) => l.replace(/\s+$/, "").replace(/^(\s*- \/url: ")#[^"]*"$/, "$1#id\""))
16
+ .join("\n");
17
+ }
18
+ /** The differences between two fingerprints, in words; empty when they match. */
19
+ export function compareFingerprints(a, b, aName = "react", bName = "web") {
20
+ const out = [];
21
+ if (a.components.length !== b.components.length || a.components.some(([id, c], i) => b.components[i]?.[0] !== id || b.components[i]?.[1] !== c)) {
22
+ const first = a.components.findIndex(([id, c], i) => b.components[i]?.[0] !== id || b.components[i]?.[1] !== c);
23
+ const at = first < 0 ? a.components.length : first;
24
+ out.push(`components differ at #${at}: ${aName} ${JSON.stringify(a.components[at])} vs ${bName} ${JSON.stringify(b.components[at])} (${a.components.length} vs ${b.components.length})`);
25
+ }
26
+ if (a.aria !== b.aria) {
27
+ const al = a.aria.split("\n");
28
+ const bl = b.aria.split("\n");
29
+ const i = al.findIndex((l, n) => bl[n] !== l);
30
+ out.push(`aria differs at line ${i + 1}:\n ${aName}: ${al[i] ?? "(end)"}\n ${bName}: ${bl[i] ?? "(end)"}`);
31
+ }
32
+ if (a.text !== b.text) {
33
+ let i = 0;
34
+ while (i < a.text.length && a.text[i] === b.text[i])
35
+ i++;
36
+ out.push(`text differs at ${i}: ${aName} "…${a.text.slice(Math.max(0, i - 30), i + 40)}" vs ${bName} "…${b.text.slice(Math.max(0, i - 30), i + 40)}"`);
37
+ }
38
+ return out;
39
+ }
40
+ //# sourceMappingURL=fingerprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../src/fingerprint.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -3,5 +3,6 @@ export { staticAudit, type StaticOptions } from "./static.ts";
3
3
  export { axeAudit, layoutAudit, type Finding } from "./rendered.ts";
4
4
  export { runTask, type Task, type Step, type AgentResult } from "./agent.ts";
5
5
  export { compare, signature, type Consistency } from "./consistency.ts";
6
- export { launch, renderPage, type RenderTarget } from "./browser.ts";
6
+ export { launch, renderPage, HARNESSES, type RenderTarget, type RendererHarness } from "./browser.ts";
7
+ export { renderedFingerprint, compareFingerprints, type Fingerprint } from "./fingerprint.ts";
7
8
  export { rewardFor, wiredCapabilities, probeActionable, coverageOf, type Reward, type RequestExpectation } from "./reward.ts";
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export { staticAudit } from "./static.js";
3
3
  export { axeAudit, layoutAudit } from "./rendered.js";
4
4
  export { runTask } from "./agent.js";
5
5
  export { compare, signature } from "./consistency.js";
6
- export { launch, renderPage } from "./browser.js";
6
+ export { launch, renderPage, HARNESSES } from "./browser.js";
7
+ export { renderedFingerprint, compareFingerprints } from "./fingerprint.js";
7
8
  export { rewardFor, wiredCapabilities, probeActionable, coverageOf } from "./reward.js";
8
9
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,EAAsD,MAAM,aAAa,CAAC;AAClH,OAAO,EAAE,WAAW,EAAsB,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAgB,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,OAAO,EAA0C,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,SAAS,EAAoB,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAqB,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,eAAe,EAAE,UAAU,EAAwC,MAAM,aAAa,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,EAAsD,MAAM,aAAa,CAAC;AAClH,OAAO,EAAE,WAAW,EAAsB,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAgB,MAAM,eAAe,CAAC;AACpE,OAAO,EAAE,OAAO,EAA0C,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,SAAS,EAAoB,MAAM,kBAAkB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAA2C,MAAM,cAAc,CAAC;AACtG,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAoB,MAAM,kBAAkB,CAAC;AAC9F,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,eAAe,EAAE,UAAU,EAAwC,MAAM,aAAa,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Rule } from "@polyxd/spec/patterns";
2
+ /** Each spec pattern's checks, by pattern id. */
3
+ export declare const PATTERN_CHECKS: Record<string, Rule[]>;
@@ -0,0 +1,312 @@
1
+ /** Each spec pattern's checks, by pattern id. */
2
+ export const PATTERN_CHECKS = {
3
+ "compare-and-choose": [
4
+ {
5
+ "id": "one-comparison",
6
+ "description": "Exactly one Comparison",
7
+ "severity": "error",
8
+ "rule": {
9
+ "check": "contains",
10
+ "component": "Comparison",
11
+ "min": 1,
12
+ "max": 1
13
+ }
14
+ },
15
+ {
16
+ "id": "can-choose",
17
+ "description": "Each option can be chosen",
18
+ "severity": "error",
19
+ "rule": {
20
+ "check": "requires",
21
+ "component": "Comparison",
22
+ "props": [
23
+ "choose"
24
+ ]
25
+ }
26
+ },
27
+ {
28
+ "id": "explains",
29
+ "description": "A one-line summary or the recommendation's reason helps the decision",
30
+ "severity": "warning",
31
+ "rule": {
32
+ "check": "anyOf",
33
+ "checks": [
34
+ {
35
+ "check": "requires",
36
+ "component": "Comparison",
37
+ "props": [
38
+ "summary"
39
+ ]
40
+ },
41
+ {
42
+ "check": "requires",
43
+ "component": "Comparison",
44
+ "props": [
45
+ "recommendedReason"
46
+ ]
47
+ }
48
+ ]
49
+ }
50
+ }
51
+ ],
52
+ "confirm-destructive": [
53
+ {
54
+ "id": "root-is-confirm",
55
+ "description": "The surface is a confirmation dialog",
56
+ "severity": "error",
57
+ "rule": {
58
+ "check": "rootIs",
59
+ "components": [
60
+ "Confirm"
61
+ ]
62
+ }
63
+ },
64
+ {
65
+ "id": "states-consequence",
66
+ "description": "It says what will happen and whether it can be undone",
67
+ "severity": "error",
68
+ "rule": {
69
+ "check": "requires",
70
+ "component": "Confirm",
71
+ "props": [
72
+ "consequence"
73
+ ]
74
+ }
75
+ },
76
+ {
77
+ "id": "specific-confirm-label",
78
+ "description": "The confirm button says what it does, never just 'OK' or 'Yes'",
79
+ "severity": "error",
80
+ "rule": {
81
+ "check": "noLabelMatches",
82
+ "pattern": "^(OK|Ok|Yes|Confirm|Submit|Done)$"
83
+ }
84
+ },
85
+ {
86
+ "id": "typed-for-destructive",
87
+ "description": "Irreversible, high-impact deletions ask the user to type a confirmation",
88
+ "severity": "warning",
89
+ "rule": {
90
+ "check": "requires",
91
+ "component": "Confirm",
92
+ "where": {
93
+ "severity": "destructive"
94
+ },
95
+ "props": [
96
+ "typeToConfirm"
97
+ ]
98
+ }
99
+ }
100
+ ],
101
+ "filter-and-browse": [
102
+ {
103
+ "id": "has-results",
104
+ "description": "Results are shown as a Collection or Table",
105
+ "severity": "error",
106
+ "rule": {
107
+ "check": "anyOf",
108
+ "checks": [
109
+ {
110
+ "check": "contains",
111
+ "component": "Collection"
112
+ },
113
+ {
114
+ "check": "contains",
115
+ "component": "Table"
116
+ }
117
+ ]
118
+ }
119
+ },
120
+ {
121
+ "id": "filters-first",
122
+ "description": "Filters come before the results",
123
+ "severity": "error",
124
+ "rule": {
125
+ "check": "precedes",
126
+ "before": [
127
+ "TextInput",
128
+ "Choice",
129
+ "RangeInput",
130
+ "DateInput"
131
+ ],
132
+ "after": [
133
+ "Collection",
134
+ "Table"
135
+ ]
136
+ }
137
+ },
138
+ {
139
+ "id": "empty-state",
140
+ "description": "Empty results explain what to do",
141
+ "severity": "error",
142
+ "rule": {
143
+ "check": "allOf",
144
+ "checks": [
145
+ {
146
+ "check": "requires",
147
+ "component": "Collection",
148
+ "props": [
149
+ "empty"
150
+ ]
151
+ },
152
+ {
153
+ "check": "requires",
154
+ "component": "Table",
155
+ "props": [
156
+ "empty"
157
+ ]
158
+ }
159
+ ]
160
+ }
161
+ }
162
+ ],
163
+ "multi-step-form": [
164
+ {
165
+ "id": "form-or-steps",
166
+ "description": "Inputs are collected in a Form or in Steps",
167
+ "severity": "error",
168
+ "rule": {
169
+ "check": "anyOf",
170
+ "checks": [
171
+ {
172
+ "check": "contains",
173
+ "component": "Form"
174
+ },
175
+ {
176
+ "check": "contains",
177
+ "component": "Steps"
178
+ }
179
+ ]
180
+ }
181
+ },
182
+ {
183
+ "id": "short-views",
184
+ "description": "No more than 6 inputs are visible at once; longer tasks are split into steps",
185
+ "severity": "error",
186
+ "rule": {
187
+ "check": "maxInputsPerView",
188
+ "max": 6
189
+ }
190
+ },
191
+ {
192
+ "id": "specific-submit",
193
+ "description": "The submit button says what happens next",
194
+ "severity": "warning",
195
+ "rule": {
196
+ "check": "noLabelMatches",
197
+ "pattern": "^(OK|Ok|Yes|Confirm|Submit|Done)$"
198
+ }
199
+ }
200
+ ],
201
+ "review-and-submit": [
202
+ {
203
+ "id": "has-summary",
204
+ "description": "What will be submitted is shown as label/value pairs",
205
+ "severity": "error",
206
+ "rule": {
207
+ "check": "contains",
208
+ "component": "DetailList"
209
+ }
210
+ },
211
+ {
212
+ "id": "submits",
213
+ "description": "There is a way to submit",
214
+ "severity": "error",
215
+ "rule": {
216
+ "check": "anyOf",
217
+ "checks": [
218
+ {
219
+ "check": "contains",
220
+ "component": "Form"
221
+ },
222
+ {
223
+ "check": "contains",
224
+ "component": "Steps"
225
+ }
226
+ ]
227
+ }
228
+ },
229
+ {
230
+ "id": "summary-before-inputs",
231
+ "description": "Review content comes before any last inputs",
232
+ "severity": "warning",
233
+ "rule": {
234
+ "check": "anyOf",
235
+ "checks": [
236
+ {
237
+ "check": "not",
238
+ "checks": [
239
+ {
240
+ "check": "contains",
241
+ "component": "Toggle"
242
+ }
243
+ ]
244
+ },
245
+ {
246
+ "check": "precedes",
247
+ "before": [
248
+ "DetailList"
249
+ ],
250
+ "after": [
251
+ "Toggle"
252
+ ]
253
+ }
254
+ ]
255
+ }
256
+ },
257
+ {
258
+ "id": "commitment-label",
259
+ "description": "The submit label states the commitment, not 'Submit'",
260
+ "severity": "error",
261
+ "rule": {
262
+ "check": "noLabelMatches",
263
+ "pattern": "^(OK|Ok|Yes|Confirm|Submit|Done|Continue)$"
264
+ }
265
+ }
266
+ ],
267
+ "undo-over-confirm": [
268
+ {
269
+ "id": "has-undo-status",
270
+ "description": "An undo notice says what happened",
271
+ "severity": "error",
272
+ "rule": {
273
+ "check": "contains",
274
+ "component": "Status",
275
+ "where": {
276
+ "kind": "undo"
277
+ },
278
+ "min": 1
279
+ }
280
+ },
281
+ {
282
+ "id": "undo-has-action",
283
+ "description": "The notice offers Undo",
284
+ "severity": "error",
285
+ "rule": {
286
+ "check": "requires",
287
+ "component": "Status",
288
+ "where": {
289
+ "kind": "undo"
290
+ },
291
+ "props": [
292
+ "action"
293
+ ]
294
+ }
295
+ },
296
+ {
297
+ "id": "no-confirm",
298
+ "description": "Nothing asks for confirmation first",
299
+ "severity": "warning",
300
+ "rule": {
301
+ "check": "not",
302
+ "checks": [
303
+ {
304
+ "check": "contains",
305
+ "component": "Confirm"
306
+ }
307
+ ]
308
+ }
309
+ }
310
+ ]
311
+ };
312
+ //# sourceMappingURL=patterns.generated.js.map