a11ign 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 (59) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +140 -0
  3. package/dist/action/post-comment.d.ts +20 -0
  4. package/dist/action/post-comment.d.ts.map +1 -0
  5. package/dist/action/post-comment.js +93 -0
  6. package/dist/action/post-comment.js.map +1 -0
  7. package/dist/action/run.d.ts +2 -0
  8. package/dist/action/run.d.ts.map +1 -0
  9. package/dist/action/run.js +119 -0
  10. package/dist/action/run.js.map +1 -0
  11. package/dist/action/summary.d.ts +180 -0
  12. package/dist/action/summary.d.ts.map +1 -0
  13. package/dist/action/summary.js +355 -0
  14. package/dist/action/summary.js.map +1 -0
  15. package/dist/cli.d.ts +279 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/cli.js +1028 -0
  18. package/dist/cli.js.map +1 -0
  19. package/dist/fault-remediation.d.ts +87 -0
  20. package/dist/fault-remediation.d.ts.map +1 -0
  21. package/dist/fault-remediation.js +156 -0
  22. package/dist/fault-remediation.js.map +1 -0
  23. package/dist/forms/config.d.ts +69 -0
  24. package/dist/forms/config.d.ts.map +1 -0
  25. package/dist/forms/config.js +185 -0
  26. package/dist/forms/config.js.map +1 -0
  27. package/dist/forms/coverage.d.ts +51 -0
  28. package/dist/forms/coverage.d.ts.map +1 -0
  29. package/dist/forms/coverage.js +80 -0
  30. package/dist/forms/coverage.js.map +1 -0
  31. package/dist/forms/draft.d.ts +62 -0
  32. package/dist/forms/draft.d.ts.map +1 -0
  33. package/dist/forms/draft.js +165 -0
  34. package/dist/forms/draft.js.map +1 -0
  35. package/dist/index.d.ts +10 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +9 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/report.d.ts +48 -0
  40. package/dist/report.d.ts.map +1 -0
  41. package/dist/report.js +322 -0
  42. package/dist/report.js.map +1 -0
  43. package/dist/scan/axe-results.d.ts +25 -0
  44. package/dist/scan/axe-results.d.ts.map +1 -0
  45. package/dist/scan/axe-results.js +111 -0
  46. package/dist/scan/axe-results.js.map +1 -0
  47. package/dist/scan/axe.d.ts +158 -0
  48. package/dist/scan/axe.d.ts.map +1 -0
  49. package/dist/scan/axe.js +203 -0
  50. package/dist/scan/axe.js.map +1 -0
  51. package/dist/scan/page-title.d.ts +3 -0
  52. package/dist/scan/page-title.d.ts.map +1 -0
  53. package/dist/scan/page-title.js +41 -0
  54. package/dist/scan/page-title.js.map +1 -0
  55. package/dist/scan/run-axe.d.ts +2 -0
  56. package/dist/scan/run-axe.d.ts.map +1 -0
  57. package/dist/scan/run-axe.js +39 -0
  58. package/dist/scan/run-axe.js.map +1 -0
  59. package/package.json +60 -0
@@ -0,0 +1,51 @@
1
+ /**
2
+ * What a forms config can and cannot answer — ADR 0024's "properly tested", made computable.
3
+ *
4
+ * The states model exists so this is a CALCULATION rather than a judgement. Each criterion declares which
5
+ * states its evidence needs, so a config carrying only an error state reports 4.1.3 as half-answered and
6
+ * names the missing half, instead of the tool quietly assessing what it happens to have and calling it
7
+ * done.
8
+ *
9
+ * The three outcomes it keeps apart are the point, and today they collapse into one:
10
+ * NOT CONFIGURED the author's to supply, and the report says how
11
+ * CONFIGURED the states needed are present
12
+ * PARTLY CONFIGURED some evidence, and a named gap
13
+ */
14
+ import type { FormSpec, StateName } from "./config.js";
15
+ /**
16
+ * Which states each criterion's evidence needs.
17
+ *
18
+ * A single table, so the report and the docs cannot drift — ADR 0024 renders the same rows, and a second
19
+ * hand-kept copy of this mapping is the fact-stated-twice defect with a criterion attached.
20
+ *
21
+ * `either` means the evidence comes from FILLING rather than from submitting: 3.2.2 asks whether entering
22
+ * data changes context, so any state with values answers it. That is also why supplying values enables
23
+ * the typing probe by construction — we type the author's own value, into the field they named, at their
24
+ * instruction, so consent is not a second question.
25
+ */
26
+ export declare const CRITERION_STATES: Readonly<Record<string, {
27
+ needs: StateName[];
28
+ mode: "all" | "either" | "partial";
29
+ }>>;
30
+ export type CriterionReadiness = "configured" | "partly" | "notConfigured";
31
+ export interface FormCoverage {
32
+ form: string;
33
+ states: StateName[];
34
+ criteria: {
35
+ criterion: string;
36
+ readiness: CriterionReadiness;
37
+ why: string;
38
+ }[];
39
+ }
40
+ /** What this form's configuration can answer, criterion by criterion. */
41
+ export declare function formCoverage(form: FormSpec): FormCoverage;
42
+ /**
43
+ * What a run would SUBMIT, said before it submits anything — the `--plan` output.
44
+ *
45
+ * A guard that names the irreversible act in advance. `origin:` already stops a staging config being
46
+ * aimed at production, but it cannot tell an author that the file they just wrote completes a booking
47
+ * twice on every CI run. This can, and it is the only thing between "I configured a success state" and
48
+ * finding out what that meant.
49
+ */
50
+ export declare function submissionPlan(forms: readonly FormSpec[], origin: string): string[];
51
+ //# sourceMappingURL=coverage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.d.ts","sourceRoot":"","sources":["../../src/forms/coverage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAAC,IAAI,EAAE,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAA;CAAE,CAAC,CAM9G,CAAC;AAEL,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,QAAQ,GAAG,eAAe,CAAC;AAE3E,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,QAAQ,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,kBAAkB,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC/E;AA8BD,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,YAAY,CAUzD;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAgBnF"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Which states each criterion's evidence needs.
3
+ *
4
+ * A single table, so the report and the docs cannot drift — ADR 0024 renders the same rows, and a second
5
+ * hand-kept copy of this mapping is the fact-stated-twice defect with a criterion attached.
6
+ *
7
+ * `either` means the evidence comes from FILLING rather than from submitting: 3.2.2 asks whether entering
8
+ * data changes context, so any state with values answers it. That is also why supplying values enables
9
+ * the typing probe by construction — we type the author's own value, into the field they named, at their
10
+ * instruction, so consent is not a second question.
11
+ */
12
+ export const CRITERION_STATES = Object.freeze({
13
+ "3.3.1": { needs: ["error"], mode: "all" },
14
+ "3.3.3": { needs: ["error"], mode: "all" },
15
+ "4.1.3": { needs: ["error", "success"], mode: "partial" },
16
+ "3.2.2": { needs: ["error", "success"], mode: "either" },
17
+ });
18
+ function readinessFor(criterion, present) {
19
+ const spec = CRITERION_STATES[criterion];
20
+ const missing = spec.needs.filter((state) => !present.has(state));
21
+ if (missing.length === 0) {
22
+ return { readiness: "configured", why: `every state this criterion needs is configured.` };
23
+ }
24
+ if (missing.length === spec.needs.length) {
25
+ return {
26
+ readiness: "notConfigured",
27
+ why: `needs a ${spec.needs.join(" or ")} state, and this form declares neither.`,
28
+ };
29
+ }
30
+ // Some present, some missing. What that COSTS differs by mode, and saying which is the whole value.
31
+ if (spec.mode === "either") {
32
+ return { readiness: "configured", why: "answered by filling the form; submitting is not required." };
33
+ }
34
+ if (spec.mode === "partial") {
35
+ return {
36
+ readiness: "partly",
37
+ why: `answered for the ${[...present].join(" and ")} path only. No ${missing.join(" or ")} state was `
38
+ + `supplied, so whether a ${missing.join("/")} status is announced is unknown.`,
39
+ };
40
+ }
41
+ return { readiness: "notConfigured", why: `needs a ${missing.join(" and ")} state.` };
42
+ }
43
+ /** What this form's configuration can answer, criterion by criterion. */
44
+ export function formCoverage(form) {
45
+ const present = new Set(form.states.map((state) => state.state));
46
+ return {
47
+ form: form.form,
48
+ states: [...present],
49
+ criteria: Object.keys(CRITERION_STATES).map((criterion) => ({
50
+ criterion,
51
+ ...readinessFor(criterion, present),
52
+ })),
53
+ };
54
+ }
55
+ /**
56
+ * What a run would SUBMIT, said before it submits anything — the `--plan` output.
57
+ *
58
+ * A guard that names the irreversible act in advance. `origin:` already stops a staging config being
59
+ * aimed at production, but it cannot tell an author that the file they just wrote completes a booking
60
+ * twice on every CI run. This can, and it is the only thing between "I configured a success state" and
61
+ * finding out what that meant.
62
+ */
63
+ export function submissionPlan(forms, origin) {
64
+ const lines = [];
65
+ for (const form of forms) {
66
+ // Error states first, matching the order a run uses: the less destructive state is observed before
67
+ // the one that completes the form, so a run that dies midway has done the safer thing.
68
+ const ordered = [...form.states].sort((a, b) => Number(a.state === "success") - Number(b.state === "success"));
69
+ lines.push(`Would submit ${JSON.stringify(form.form)} ${ordered.length} time(s) against ${origin}, via `
70
+ + `${JSON.stringify(form.submit)}:`);
71
+ ordered.forEach((state, index) => {
72
+ const because = state.because ? ` (because: ${state.because})` : "";
73
+ const warning = state.state === "success" ? " <- THIS COMPLETES THE FORM" : "";
74
+ lines.push(` ${index + 1}. state ${JSON.stringify(state.state)}${because}${warning}`);
75
+ });
76
+ }
77
+ lines.push("", "Nothing was submitted. Remove --plan to run.");
78
+ return lines;
79
+ }
80
+ //# sourceMappingURL=coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/forms/coverage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ /** What a draft found, so a caller can report on it rather than only print it. */
2
+ export interface FormsDraft {
3
+ /** The YAML text. */
4
+ yaml: string;
5
+ /** Fields that can be addressed AND filled, each with the verb its control's role takes. */
6
+ addressable: {
7
+ name: string;
8
+ verb: "value" | "choose" | "check";
9
+ within?: string;
10
+ nth?: number;
11
+ }[];
12
+ /**
13
+ * Fields NVDA announced with NO name, by position in reading order.
14
+ *
15
+ * Not an error and not a warning — a finding. It is reported whether or not the author ever configures
16
+ * this form, because it is a fact about the page rather than about the config.
17
+ */
18
+ unnamed: {
19
+ position: number;
20
+ announced: string;
21
+ }[];
22
+ /**
23
+ * Buttons, offered as `submit:` candidates rather than as things to type into.
24
+ *
25
+ * `structure.formFields` is NVDA's FORM-FIELD quick-nav, and it visits buttons — the census comment says
26
+ * so outright: *"the census counts the roles NVDA's form-field quick-nav actually visits — buttons
27
+ * included."* Found by running the emitter against a real page, where `"Submit Search", button` was
28
+ * drafted with `value: ""`. Typing into a button is not a thing, so the verb was wrong for the control,
29
+ * which is the exact confusion the three-verb schema exists to prevent — arriving through the generator
30
+ * instead of through the config.
31
+ */
32
+ submitCandidates: string[];
33
+ /**
34
+ * Announcements the grammar could not resolve into an object at all.
35
+ *
36
+ * SEPARATE from `unnamed`, and keeping them apart is the whole point. `unnamed` is a claim about the
37
+ * PAGE — a control with no accessible name, which is 4.1.2. This is a claim about our own parser, and
38
+ * conflating them puts a false accessibility finding into a generated artefact.
39
+ *
40
+ * It is not hypothetical: measured 2026-09-02, `parseAnnouncement` returns NO objects for every
41
+ * checkbox, because `CONTROL_ROLES` carries `"checkbox"` and NVDA says `"check box"`. The local corpus
42
+ * copy holds 22 announcements with NVDA's spelling and 0 with the grammar's. So a real W3C tutorial page
43
+ * with a correctly-labelled `"Subscribe to newsletter, check box"` was reported as an unnamed field —
44
+ * a false 4.1.2 against a conformant control. See the backlog.
45
+ */
46
+ unparsed: {
47
+ position: number;
48
+ announced: string;
49
+ }[];
50
+ }
51
+ /**
52
+ * @param formFields the capture's `structure.formFields` — NVDA's announcements, in reading order
53
+ * @param options `origin` is required in the output, so it is required here rather than left as a TODO:
54
+ * a config that is missing it is refused at parse time, and emitting a file that cannot load is worse
55
+ * than emitting one that is incomplete.
56
+ */
57
+ export declare function draftFormsConfig(formFields: readonly string[], options: {
58
+ origin: string;
59
+ formName?: string;
60
+ submitName?: string;
61
+ }): FormsDraft;
62
+ //# sourceMappingURL=draft.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft.d.ts","sourceRoot":"","sources":["../../src/forms/draft.ts"],"names":[],"mappings":"AAmBA,kFAAkF;AAClF,MAAM,WAAW,UAAU;IACzB,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,4FAA4F;IAC5F,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACnG;;;;;OAKG;IACH,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACnD;;;;;;;;;OASG;IACH,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B;;;;;;;;;;;;OAYG;IACH,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACrD;AAmGD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,OAAO,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAClE,UAAU,CAwDZ"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Draft a forms config from what the screen reader actually announced — ADR 0024's "make it easy" half.
3
+ *
4
+ * Nobody writes the config from scratch. A capture already records every form field's announced name in
5
+ * `structure.formFields`, so the skeleton can be generated and the author only supplies values.
6
+ *
7
+ * **The draft is itself an accessibility report**, and that is the part worth protecting. A field NVDA
8
+ * announced with no name cannot be addressed by this config — and cannot be addressed by a screen reader
9
+ * user either, which is the 4.1.2 failure. So it is emitted as a NAMED COMMENT rather than skipped: an
10
+ * author who never fills the file in has still learned something, and a silently omitted field would be
11
+ * the empty-channel defect arriving in a generated artefact.
12
+ *
13
+ * Disambiguation is solved HERE rather than in the schema, because the easiest API for a name collision
14
+ * is one the author never writes. Two fields sharing a name get `within:` filled in from the group NVDA
15
+ * announced around them — which is how a screen reader user tells them apart — and `nth:` only when there
16
+ * is no group to name.
17
+ */
18
+ import { parseAnnouncement } from "@a11ign/evidence";
19
+ /**
20
+ * Which VERB a control's role takes — the schema's three verbs, matched to the controls that accept them.
21
+ *
22
+ * A button is absent on purpose (it is a `submit:` candidate, not something to fill), and so is static
23
+ * prose. The mapping exists because getting it wrong is not cosmetic: a draft that offers `value: ""` on
24
+ * a checkbox has told the author to type into it, which is the confusion the three-verb schema was
25
+ * designed to prevent. Both halves of that were live bugs found by running this against real pages —
26
+ * a button drafted as typeable, then a checkbox drafted the same way the moment the grammar could see one.
27
+ */
28
+ const VERB_FOR_ROLE = Object.freeze({
29
+ "edit": "value", "edit text": "value", "spin button": "value", "slider": "value",
30
+ "combo box": "choose", "list box": "choose",
31
+ "check box": "check", "radio button": "check", "radio": "check",
32
+ });
33
+ /**
34
+ * One field's name, role and enclosing group, from the capture's OWN grammar.
35
+ *
36
+ * `role` is carried rather than discarded because the control's type decides which verb applies, and
37
+ * running this against a real page proved the point: without it, a button was drafted as something to
38
+ * type into.
39
+ */
40
+ function describe(announced) {
41
+ const parsed = parseAnnouncement(announced, "sweep");
42
+ const object = parsed.objects.find((o) => o.name !== "" || o.role !== "");
43
+ // The INNERMOST container is the useful one for disambiguation: "Billing address" tells the two
44
+ // "Address line 1" fields apart, where the page-wrapping landmark they share tells you nothing.
45
+ const group = parsed.containers.length ? parsed.containers[parsed.containers.length - 1].name : undefined;
46
+ return { name: object?.name ?? "", role: object?.role ?? "", group: group || undefined };
47
+ }
48
+ const quote = (value) => JSON.stringify(value);
49
+ /**
50
+ * Build the addressable list, filling in a disambiguator ONLY where a name actually collides.
51
+ *
52
+ * Adding `within:` everywhere would be noise, and worse: it would bind every field to a container that
53
+ * may change for reasons unrelated to the field, so a page edit elsewhere breaks a config that did not
54
+ * need the constraint.
55
+ */
56
+ function addressable(fields) {
57
+ const seen = new Map();
58
+ for (const field of fields)
59
+ seen.set(field.name, (seen.get(field.name) ?? 0) + 1);
60
+ const used = new Map();
61
+ return fields.map((field) => {
62
+ const base = { name: field.name, verb: field.verb };
63
+ if ((seen.get(field.name) ?? 0) < 2)
64
+ return base;
65
+ const ordinal = (used.get(field.name) ?? 0) + 1;
66
+ used.set(field.name, ordinal);
67
+ // `within` first, `nth` only when there is no group to name — the ordering ADR 0024 settled, because
68
+ // a group is how a person distinguishes them and a count is how a machine does.
69
+ return field.group ? { ...base, within: field.group } : { ...base, nth: ordinal };
70
+ });
71
+ }
72
+ /** The starting value a verb is drafted with — blank for the author to fill, never a guess. */
73
+ const BLANK_FOR = Object.freeze({
74
+ value: '""', choose: '""', check: "false",
75
+ });
76
+ function fieldLines(entries) {
77
+ return entries.flatMap((entry) => [
78
+ ` - field: ${quote(entry.name)}`,
79
+ ...(entry.within ? [` within: ${quote(entry.within)} # DRAFTED: two fields share this name`] : []),
80
+ ...(entry.nth ? [` nth: ${entry.nth} # DRAFTED: two fields share this name and no group names them`] : []),
81
+ ` ${entry.verb}: ${BLANK_FOR[entry.verb]} # TODO`,
82
+ ]);
83
+ }
84
+ function unnamedLines(unnamed) {
85
+ return unnamed.flatMap((field) => [
86
+ "",
87
+ ` # UNNAMED FIELD, ${field.position} in reading order. NVDA announced ${quote(field.announced)}.`,
88
+ " # This tool cannot address it, and neither can a screen reader user.",
89
+ " # Reported as 4.1.2 whether or not you configure this form.",
90
+ ]);
91
+ }
92
+ /**
93
+ * Phrases this tool could not read, said as such.
94
+ *
95
+ * Deliberately worded as a limitation of the tool rather than of the page. The author cannot fix our
96
+ * parser and must not be sent looking for a defect that is ours — which is what putting these under
97
+ * "UNNAMED FIELD" did.
98
+ */
99
+ function unparsedLines(unparsed) {
100
+ return unparsed.flatMap((field) => [
101
+ "",
102
+ ` # NOT UNDERSTOOD by a11ign, ${field.position} in reading order:`,
103
+ ` # ${field.announced}`,
104
+ " # This is a gap in THIS TOOL's announcement grammar, not a finding about your page.",
105
+ " # Add the field by hand if you need it configured.",
106
+ ]);
107
+ }
108
+ /**
109
+ * @param formFields the capture's `structure.formFields` — NVDA's announcements, in reading order
110
+ * @param options `origin` is required in the output, so it is required here rather than left as a TODO:
111
+ * a config that is missing it is refused at parse time, and emitting a file that cannot load is worse
112
+ * than emitting one that is incomplete.
113
+ */
114
+ export function draftFormsConfig(formFields, options) {
115
+ const described = formFields.map((announced, index) => ({ announced, index, ...describe(announced) }));
116
+ const at = (field) => ({ position: field.index + 1, announced: field.announced });
117
+ // FOUR outcomes, and collapsing any two of them is how this goes wrong.
118
+ //
119
+ // A button is a submit candidate, not something to type into. A named fillable control is what the
120
+ // config is for. A control announced with no name is a 4.1.2 finding about the PAGE. And a phrase the
121
+ // grammar could not resolve at all is a fact about OUR PARSER, which must never be reported as the
122
+ // third — that is a false accessibility finding, and it is what this emitter did on its first run
123
+ // against a real page.
124
+ const unparsed = described.filter((field) => field.role === "" && field.name === "").map(at);
125
+ const resolved = described.filter((field) => field.role !== "" || field.name !== "");
126
+ const submitCandidates = [...new Set(resolved.filter((field) => field.role === "button" && field.name !== "").map((field) => field.name))];
127
+ const fillable = resolved.filter((field) => VERB_FOR_ROLE[field.role] !== undefined);
128
+ // EVERY resolved-but-unnamed control, not only the fillable ones -- an unnamed BUTTON is exactly as
129
+ // much a 4.1.2 finding as an unnamed edit field, and deriving this from `fillable` silently dropped it:
130
+ // a button is excluded from `fillable` (it takes no verb) and from `submitCandidates` (no name to
131
+ // offer), so it vanished from the draft entirely. Measured: `[", button"]` produced `unnamed: []`,
132
+ // contradicting this file's own header -- "an author who never fills the file in has still learned
133
+ // something" promises no silent omission, and this was one.
134
+ const unnamed = resolved.filter((field) => field.name === "").map(at);
135
+ const entries = addressable(fillable
136
+ .filter((field) => field.name !== "")
137
+ .map((f) => ({ name: f.name, verb: VERB_FOR_ROLE[f.role], group: f.group })));
138
+ const yaml = [
139
+ "# Drafted by a11ign from what NVDA announced. Fill in the values; the names are already right.",
140
+ "#",
141
+ "# A `success` state COMPLETES the form. Supplying one is how you say that is acceptable — leave it out",
142
+ "# and nothing is submitted with valid data. Run with --plan to see exactly what would be submitted.",
143
+ "version: 1",
144
+ `origin: ${quote(options.origin)}`,
145
+ "forms:",
146
+ ` - form: ${quote(options.formName ?? "TODO the form's accessible name")}`,
147
+ // DRAFTED from the buttons actually announced, rather than left as a TODO the author has to go and
148
+ // look up. Where several were found the rest are listed beside it, because guessing which one submits
149
+ // is not something this tool can do and pretending otherwise would put the wrong control in the file.
150
+ ` submit: ${quote(options.submitName ?? submitCandidates[0] ?? "TODO the control that submits it")}`
151
+ + (submitCandidates.length > 1
152
+ ? ` # buttons found: ${submitCandidates.map((c) => JSON.stringify(c)).join(", ")}`
153
+ : ""),
154
+ " states:",
155
+ " - state: error",
156
+ ' because: "" # TODO what does this form reject?',
157
+ " fields:",
158
+ ...fieldLines(entries),
159
+ ...unnamedLines(unnamed),
160
+ ...unparsedLines(unparsed),
161
+ "",
162
+ ].join("\n");
163
+ return { yaml, addressable: entries, unnamed, submitCandidates, unparsed };
164
+ }
165
+ //# sourceMappingURL=draft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft.js","sourceRoot":"","sources":["../../src/forms/draft.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The renderer, so a consumer can print our report shape without reimplementing it.
3
+ *
4
+ * Deliberately the whole public surface. The CLI itself is a `bin`, not a library: it leases a worker, drives a
5
+ * capture over HTTP, judges it, optionally merges an axe run, and prints. Every one of those pieces is already
6
+ * a package of its own, so exporting a second way to orchestrate them would be two APIs to keep honest.
7
+ */
8
+ export { reportLines } from "./report.js";
9
+ export type { Report } from "./report.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,YAAY,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The renderer, so a consumer can print our report shape without reimplementing it.
3
+ *
4
+ * Deliberately the whole public surface. The CLI itself is a `bin`, not a library: it leases a worker, drives a
5
+ * capture over HTTP, judges it, optionally merges an axe run, and prints. Every one of those pieces is already
6
+ * a package of its own, so exporting a second way to orchestrate them would be two APIs to keep honest.
7
+ */
8
+ export { reportLines } from "./report.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The report a person reads, as lines of text.
3
+ *
4
+ * Separated from printing it because this is the product's output — the thing a user judges their
5
+ * site by — and it was the only part of the CLI that could not be tested at all: `cli.ts` calls
6
+ * `main()` on import, so importing it to check a heading runs a capture.
7
+ *
8
+ * Building the lines here and letting the CLI do nothing but `console.log` them is the Humble Object
9
+ * pattern: the interesting half becomes a pure function, and the half that touches the world becomes
10
+ * too small to hold a bug.
11
+ */
12
+ import type { Judgment } from "@a11ign/judge";
13
+ import type { AxeFinding } from "./scan/axe.js";
14
+ import { type ConformanceRequirement } from "@a11ign/evidence/conformance";
15
+ import { type CriterionOutcome } from "@a11ign/judge/outcomes";
16
+ export interface Report {
17
+ url: string;
18
+ task: string;
19
+ screenReader: string;
20
+ announcements: number;
21
+ verdict: Judgment;
22
+ /** null when the rule-based layer did not run — distinct from "ran and found nothing". */
23
+ axe: AxeFinding[] | null;
24
+ /**
25
+ * What this run establishes against WCAG's five CONFORMANCE REQUIREMENTS (§5.2), which govern whether
26
+ * a conformance claim is valid at all and are not success criteria. Optional so an older caller still
27
+ * renders — but when it is absent the section says so rather than being silently dropped, because a
28
+ * missing limit is exactly what makes a findings list read as a clean bill of health.
29
+ */
30
+ conformance?: ConformanceRequirement[];
31
+ /**
32
+ * Per-criterion ACT outcomes. Optional so an older caller still renders, but its absence is stated
33
+ * rather than skipped — see `outcomesSection`.
34
+ */
35
+ outcomes?: CriterionOutcome[];
36
+ /**
37
+ * The capture's own `environment` block, from the RUNNING WORKER, never from a pin or a manifest —
38
+ * publish blocker B4. `screenReaderVersion` and `guidepupVersion` matter most: the shipped scorer was
39
+ * trained on evidence from the fleet's NVDA, so a consumer's report has to say which NVDA build (and
40
+ * which client drove it) actually produced these announcements, not which one a lockfile or an
41
+ * installer manifest merely NAMES. The `browserVersion` memo defect, precisely: a pin says what was
42
+ * asked for, and only the instrument can say what was there. Optional so an older caller still renders.
43
+ */
44
+ environment?: Record<string, string>;
45
+ }
46
+ /** The whole report, ready to print. */
47
+ export declare function reportLines({ url, task, screenReader, announcements, verdict, axe, conformance, outcomes, environment }: Report): string[];
48
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAwB,KAAK,sBAAsB,EAAE,MACrD,8BAA8B,CAAC;AACtC,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAM7E,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,QAAQ,CAAC;IAClB,0FAA0F;IAC1F,GAAG,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IACzB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,sBAAsB,EAAE,CAAC;IACvC;;;OAGG;IACH,QAAQ,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC9B;;;;;;;OAOG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AA6SD,wCAAwC;AACxC,wBAAgB,WAAW,CACzB,EAAE,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,EAAE,MAAM,GACnG,MAAM,EAAE,CA2BV"}