ultra11y 5.3.2 → 5.3.3

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.
@@ -10,7 +10,7 @@
10
10
  "name": "ultra11y",
11
11
  "source": "./",
12
12
  "description": "Automatic accessibility review of the code under change before every commit, push and pull request, plus full WCAG 2.2 AA / RGAA audits and conformance deliverables on demand.",
13
- "version": "5.3.2",
13
+ "version": "5.3.3",
14
14
  "author": { "name": "maxgfr" },
15
15
  "homepage": "https://github.com/maxgfr/ultra11y",
16
16
  "repository": "https://github.com/maxgfr/ultra11y",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra11y",
3
3
  "description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
4
- "version": "5.3.2",
4
+ "version": "5.3.3",
5
5
  "author": { "name": "maxgfr" },
6
6
  "homepage": "https://github.com/maxgfr/ultra11y",
7
7
  "repository": "https://github.com/maxgfr/ultra11y",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ultra11y",
3
3
  "description": "Accessibility for WCAG 2.2 AA and country standards (RGAA…): an automatic review of the code under change before every commit, push and pull request, plus full audits and conformance deliverables on demand.",
4
- "version": "5.3.2",
4
+ "version": "5.3.3",
5
5
  "author": { "name": "maxgfr", "url": "https://github.com/maxgfr" },
6
6
  "homepage": "https://github.com/maxgfr/ultra11y",
7
7
  "repository": "https://github.com/maxgfr/ultra11y",
@@ -27,7 +27,7 @@ import { fileURLToPath } from "node:url";
27
27
  // Ownership marker: `ultra11y install --opencode` refuses to overwrite a plugin file that
28
28
  // does not carry it, so a user's own ultra11y.js is never clobbered.
29
29
  const ULTRA11Y_OPENCODE_PLUGIN = "ULTRA11Y_OPENCODE_PLUGIN";
30
- const ULTRA11Y_PLUGIN_VERSION = "5.3.2";
30
+ const ULTRA11Y_PLUGIN_VERSION = "5.3.3";
31
31
 
32
32
  const here = dirname(fileURLToPath(import.meta.url));
33
33
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ultra11y",
3
- "version": "5.3.2",
3
+ "version": "5.3.3",
4
4
  "description": "Audit HTML/CSS/JSX against WCAG 2.2 AA and produce a dated compliance report, or author/review accessible markup (native-HTML-first). A skills.sh agent skill: an install-free static engine (bundled, no npm install) plus the agent's judgment, with check/verify gates against hallucinated non-conformities, and pluggable in-repo standards packs (RGAA France first; Section 508 & EN 301 549 next).",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.33.0",
@@ -6,7 +6,7 @@ import { join as join50, relative as relative5, resolve as resolve15, sep as sep
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "5.3.2";
9
+ var VERSION = "5.3.3";
10
10
  var SCHEMA_VERSION = 2;
11
11
 
12
12
  // src/audit.ts
@@ -60058,10 +60058,14 @@ var S = {
60058
60058
  clamped: (n) => `_${n} groupe(s) retir\xE9(s) de ce commentaire pour tenir dans la limite de GitHub \u2014 le r\xE9sum\xE9 de job les porte tous._`,
60059
60059
  unanchored: (n) => `${n} constat(s) rattach\xE9(s) \xE0 une URL, sans ligne de code \xE0 annoter \u2014 voir le rapport.`,
60060
60060
  unattributed: (n) => `${n} constat(s) ne sont rattach\xE9s \xE0 aucune page (code partag\xE9, fichier hors routes) \u2014 compt\xE9s dans l'audit global, jamais r\xE9partis d'office.`,
60061
- sourceBasis: "Une page marqu\xE9e \xAB source \xBB n'a pas d'instantan\xE9 : l'absence de constat n'y vaut PAS conformit\xE9, et son taux ne porte que sur ce que le moteur a pu d\xE9cider ailleurs.",
60061
+ sourceBasis: "Une page marqu\xE9e \xAB source \xBB n'a pas d'instantan\xE9 : l'absence de constat n'y vaut PAS conformit\xE9. Ses crit\xE8res non d\xE9cid\xE9s restent \xAB \xE0 \xE9valuer \xBB, ils ne basculent jamais en conformes par silence.",
60062
60062
  pageByPage: "page par page",
60063
60063
  pagesCount: (n) => `${n} page(s)`,
60064
60064
  testsCol: "Tests",
60065
+ conformingCol: "C",
60066
+ nonConformingCol: "NC",
60067
+ toAssessCol: "\xC0 \xE9valuer",
60068
+ scoreboardNote: "`C` conforme \xB7 `NC` non conforme \xB7 \xAB \xC0 \xE9valuer \xBB : personne ne l'a encore tranch\xE9 \u2014 ni conforme ni non conforme. Pas de pourcentage ici : un taux calcul\xE9 sur les seuls crit\xE8res d\xE9cid\xE9s se lit comme une note de page, et vaut la m\xEAme chose sur une bonne page que sur une mauvaise. Le taux et sa couverture sont dans la fiche par page de l'artefact.",
60065
60069
  noPages: "Aucune page dans le p\xE9rim\xE8tre de ce run : le balayage n'a produit aucun instantan\xE9. Ce n'est pas un bilan vide, c'est un bilan absent \u2014 les crit\xE8res au rendu restent \xE0 \xE9valuer.",
60066
60070
  pagesDetailNote: "Un bloc par page portant au moins une non-conformit\xE9, et seulement ses crit\xE8res **non conformes** \u2014 la grille compl\xE8te (tous les crit\xE8res de chaque page, avec leurs tests et leurs captures) vit dans l'artefact.",
60067
60071
  pagesClamped: (n) => `_Le d\xE9tail de ${n} page(s) a \xE9t\xE9 retir\xE9 de ce commentaire pour tenir dans la limite de GitHub \u2014 l'artefact les porte toutes._`,
@@ -60099,10 +60103,14 @@ var S = {
60099
60103
  clamped: (n) => `_${n} group(s) dropped from this comment to fit GitHub's limit \u2014 the job summary carries them all._`,
60100
60104
  unanchored: (n) => `${n} finding(s) keyed to a URL, with no code line to annotate \u2014 see the report.`,
60101
60105
  unattributed: (n) => `${n} finding(s) are attributed to no page (shared code, file outside any route) \u2014 counted in the overall audit, never spread across pages.`,
60102
- sourceBasis: 'A page marked "source" has no snapshot: the absence of a finding there does NOT mean conforming, and its rate covers only what the engine could decide elsewhere.',
60106
+ sourceBasis: 'A page marked "source" has no snapshot: the absence of a finding there does NOT mean conforming. Its undecided criteria stay \u201Cto assess\u201D; they never turn conforming by silence.',
60103
60107
  pageByPage: "page by page",
60104
60108
  pagesCount: (n) => `${n} page(s)`,
60105
60109
  testsCol: "Tests",
60110
+ conformingCol: "C",
60111
+ nonConformingCol: "NC",
60112
+ toAssessCol: "To assess",
60113
+ scoreboardNote: "`C` conforming \xB7 `NC` non-conforming \xB7 \u201CTo assess\u201D: nobody has ruled on it yet \u2014 neither conforming nor not. No percentage here: a rate over the decided criteria alone reads as a page score, and reads the same on a good page as on a bad one. The rate and its coverage live in the artifact's per-page sheet.",
60106
60114
  noPages: "No page in this run's scope: the sweep produced no snapshot. This is not an empty scoreboard, it is a missing one \u2014 the rendering criteria stay to assess.",
60107
60115
  pagesDetailNote: "One block per page carrying at least one non-conformity, and only its **non-conforming** criteria \u2014 the full grid (every criterion of every page, with its tests and its screenshot) lives in the artifact.",
60108
60116
  pagesClamped: (n) => `_The detail of ${n} page(s) was dropped from this comment to fit GitHub's limit \u2014 the artifact carries them all._`,
@@ -60226,13 +60234,15 @@ function perPageTable(result, standard = CORE2, lang = "en") {
60226
60234
  return [`### ${s.perPage}`, "", ...scoreboardTable(result, derived, standard, s, lang), "", ...basisCaveats(result, derived, s, lang)].join("\n");
60227
60235
  }
60228
60236
  function scoreboardTable(result, derived, standard, s, lang) {
60229
- const out2 = [`| ${s.page} | ${s.basis} | ${s.pageRate} | \u{1F534} | \u{1F7E0} | \u{1F7E1} |`, "| --- | --- | ---: | ---: | ---: | ---: |"];
60237
+ const out2 = [
60238
+ `| ${s.page} | ${s.basis} | ${s.conformingCol} | ${s.nonConformingCol} | ${s.toAssessCol} | \u{1F534} | \u{1F7E0} | \u{1F7E1} |`,
60239
+ "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |"
60240
+ ];
60230
60241
  for (const pg of derived) {
60231
60242
  const n = (sev) => severityCount(pg, sev);
60232
- const rows = pageCriterionRows(result, pg, standard, lang);
60233
- const cov = pageCoverage(rows);
60243
+ const t2 = pageTally(pageCriterionRows(result, pg, standard, lang));
60234
60244
  out2.push(
60235
- `| ${pg.name}${pg.auth ? " \u{1F512}" : ""} \u2014 \`${pg.url}\` | ${basisLabel(pg.basis, lang)} | ${formatRate(pageRatePct(rows), cov.decided, cov.total)} | ${n("bloquant")} | ${n("majeur")} | ${n("mineur")} |`
60245
+ `| ${pg.name}${pg.auth ? " \u{1F512}" : ""} \u2014 \`${pg.url}\` | ${basisLabel(pg.basis, lang)} | ${t2.c} | ${t2.nc} | ${t2.manual} | ${n("bloquant")} | ${n("majeur")} | ${n("mineur")} |`
60236
60246
  );
60237
60247
  }
60238
60248
  return out2;
@@ -60241,7 +60251,7 @@ function severityCount(pg, sev) {
60241
60251
  return pg.findings.filter((f) => !f.advisory && f.severity === sev).length;
60242
60252
  }
60243
60253
  function basisCaveats(result, derived, s, lang) {
60244
- const out2 = [];
60254
+ const out2 = [`> ${s.scoreboardNote}`, ""];
60245
60255
  const orphans = unattributedFindings(result).filter((f) => !f.advisory).length;
60246
60256
  if (orphans) out2.push(`> ${s.unattributed(orphans)}`, "");
60247
60257
  if (derived.some((p) => p.basis === "attributed")) out2.push(`> ${s.sourceBasis}`, "");
@@ -5,7 +5,7 @@ when_to_use: "Invoke WITHOUT being asked whenever changed front-end code should
5
5
  allowed-tools: Bash(node ${CLAUDE_SKILL_DIR}/scripts/ultra11y.mjs *), Read, Grep, Glob
6
6
  license: MIT
7
7
  metadata:
8
- version: 5.3.2
8
+ version: 5.3.3
9
9
  ---
10
10
 
11
11
  # review-a11y — accessibility review of changed code
@@ -6,7 +6,7 @@ import { join as join50, relative as relative5, resolve as resolve15, sep as sep
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "5.3.2";
9
+ var VERSION = "5.3.3";
10
10
  var SCHEMA_VERSION = 2;
11
11
 
12
12
  // src/audit.ts
@@ -60058,10 +60058,14 @@ var S = {
60058
60058
  clamped: (n) => `_${n} groupe(s) retir\xE9(s) de ce commentaire pour tenir dans la limite de GitHub \u2014 le r\xE9sum\xE9 de job les porte tous._`,
60059
60059
  unanchored: (n) => `${n} constat(s) rattach\xE9(s) \xE0 une URL, sans ligne de code \xE0 annoter \u2014 voir le rapport.`,
60060
60060
  unattributed: (n) => `${n} constat(s) ne sont rattach\xE9s \xE0 aucune page (code partag\xE9, fichier hors routes) \u2014 compt\xE9s dans l'audit global, jamais r\xE9partis d'office.`,
60061
- sourceBasis: "Une page marqu\xE9e \xAB source \xBB n'a pas d'instantan\xE9 : l'absence de constat n'y vaut PAS conformit\xE9, et son taux ne porte que sur ce que le moteur a pu d\xE9cider ailleurs.",
60061
+ sourceBasis: "Une page marqu\xE9e \xAB source \xBB n'a pas d'instantan\xE9 : l'absence de constat n'y vaut PAS conformit\xE9. Ses crit\xE8res non d\xE9cid\xE9s restent \xAB \xE0 \xE9valuer \xBB, ils ne basculent jamais en conformes par silence.",
60062
60062
  pageByPage: "page par page",
60063
60063
  pagesCount: (n) => `${n} page(s)`,
60064
60064
  testsCol: "Tests",
60065
+ conformingCol: "C",
60066
+ nonConformingCol: "NC",
60067
+ toAssessCol: "\xC0 \xE9valuer",
60068
+ scoreboardNote: "`C` conforme \xB7 `NC` non conforme \xB7 \xAB \xC0 \xE9valuer \xBB : personne ne l'a encore tranch\xE9 \u2014 ni conforme ni non conforme. Pas de pourcentage ici : un taux calcul\xE9 sur les seuls crit\xE8res d\xE9cid\xE9s se lit comme une note de page, et vaut la m\xEAme chose sur une bonne page que sur une mauvaise. Le taux et sa couverture sont dans la fiche par page de l'artefact.",
60065
60069
  noPages: "Aucune page dans le p\xE9rim\xE8tre de ce run : le balayage n'a produit aucun instantan\xE9. Ce n'est pas un bilan vide, c'est un bilan absent \u2014 les crit\xE8res au rendu restent \xE0 \xE9valuer.",
60066
60070
  pagesDetailNote: "Un bloc par page portant au moins une non-conformit\xE9, et seulement ses crit\xE8res **non conformes** \u2014 la grille compl\xE8te (tous les crit\xE8res de chaque page, avec leurs tests et leurs captures) vit dans l'artefact.",
60067
60071
  pagesClamped: (n) => `_Le d\xE9tail de ${n} page(s) a \xE9t\xE9 retir\xE9 de ce commentaire pour tenir dans la limite de GitHub \u2014 l'artefact les porte toutes._`,
@@ -60099,10 +60103,14 @@ var S = {
60099
60103
  clamped: (n) => `_${n} group(s) dropped from this comment to fit GitHub's limit \u2014 the job summary carries them all._`,
60100
60104
  unanchored: (n) => `${n} finding(s) keyed to a URL, with no code line to annotate \u2014 see the report.`,
60101
60105
  unattributed: (n) => `${n} finding(s) are attributed to no page (shared code, file outside any route) \u2014 counted in the overall audit, never spread across pages.`,
60102
- sourceBasis: 'A page marked "source" has no snapshot: the absence of a finding there does NOT mean conforming, and its rate covers only what the engine could decide elsewhere.',
60106
+ sourceBasis: 'A page marked "source" has no snapshot: the absence of a finding there does NOT mean conforming. Its undecided criteria stay \u201Cto assess\u201D; they never turn conforming by silence.',
60103
60107
  pageByPage: "page by page",
60104
60108
  pagesCount: (n) => `${n} page(s)`,
60105
60109
  testsCol: "Tests",
60110
+ conformingCol: "C",
60111
+ nonConformingCol: "NC",
60112
+ toAssessCol: "To assess",
60113
+ scoreboardNote: "`C` conforming \xB7 `NC` non-conforming \xB7 \u201CTo assess\u201D: nobody has ruled on it yet \u2014 neither conforming nor not. No percentage here: a rate over the decided criteria alone reads as a page score, and reads the same on a good page as on a bad one. The rate and its coverage live in the artifact's per-page sheet.",
60106
60114
  noPages: "No page in this run's scope: the sweep produced no snapshot. This is not an empty scoreboard, it is a missing one \u2014 the rendering criteria stay to assess.",
60107
60115
  pagesDetailNote: "One block per page carrying at least one non-conformity, and only its **non-conforming** criteria \u2014 the full grid (every criterion of every page, with its tests and its screenshot) lives in the artifact.",
60108
60116
  pagesClamped: (n) => `_The detail of ${n} page(s) was dropped from this comment to fit GitHub's limit \u2014 the artifact carries them all._`,
@@ -60226,13 +60234,15 @@ function perPageTable(result, standard = CORE2, lang = "en") {
60226
60234
  return [`### ${s.perPage}`, "", ...scoreboardTable(result, derived, standard, s, lang), "", ...basisCaveats(result, derived, s, lang)].join("\n");
60227
60235
  }
60228
60236
  function scoreboardTable(result, derived, standard, s, lang) {
60229
- const out2 = [`| ${s.page} | ${s.basis} | ${s.pageRate} | \u{1F534} | \u{1F7E0} | \u{1F7E1} |`, "| --- | --- | ---: | ---: | ---: | ---: |"];
60237
+ const out2 = [
60238
+ `| ${s.page} | ${s.basis} | ${s.conformingCol} | ${s.nonConformingCol} | ${s.toAssessCol} | \u{1F534} | \u{1F7E0} | \u{1F7E1} |`,
60239
+ "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |"
60240
+ ];
60230
60241
  for (const pg of derived) {
60231
60242
  const n = (sev) => severityCount(pg, sev);
60232
- const rows = pageCriterionRows(result, pg, standard, lang);
60233
- const cov = pageCoverage(rows);
60243
+ const t2 = pageTally(pageCriterionRows(result, pg, standard, lang));
60234
60244
  out2.push(
60235
- `| ${pg.name}${pg.auth ? " \u{1F512}" : ""} \u2014 \`${pg.url}\` | ${basisLabel(pg.basis, lang)} | ${formatRate(pageRatePct(rows), cov.decided, cov.total)} | ${n("bloquant")} | ${n("majeur")} | ${n("mineur")} |`
60245
+ `| ${pg.name}${pg.auth ? " \u{1F512}" : ""} \u2014 \`${pg.url}\` | ${basisLabel(pg.basis, lang)} | ${t2.c} | ${t2.nc} | ${t2.manual} | ${n("bloquant")} | ${n("majeur")} | ${n("mineur")} |`
60236
60246
  );
60237
60247
  }
60238
60248
  return out2;
@@ -60241,7 +60251,7 @@ function severityCount(pg, sev) {
60241
60251
  return pg.findings.filter((f) => !f.advisory && f.severity === sev).length;
60242
60252
  }
60243
60253
  function basisCaveats(result, derived, s, lang) {
60244
- const out2 = [];
60254
+ const out2 = [`> ${s.scoreboardNote}`, ""];
60245
60255
  const orphans = unattributedFindings(result).filter((f) => !f.advisory).length;
60246
60256
  if (orphans) out2.push(`> ${s.unattributed(orphans)}`, "");
60247
60257
  if (derived.some((p) => p.basis === "attributed")) out2.push(`> ${s.sourceBasis}`, "");
@@ -4,7 +4,7 @@ description: "Use to AUDIT existing HTML/CSS/JSX against WCAG 2.2 AA accessibili
4
4
  when_to_use: "Invoke when the user asks for an accessibility AUDIT or a formal deliverable: audit a repo, site or page against WCAG 2.2 AA or a country standard (RGAA, Section 508, EN 301 549); produce a dated conformance report, a criterion grid, a PRD backlog, or tickets (GitHub/GitLab/Jira); author accessible markup from scratch; wire the repo gate (init --hook/--ci) or a rendered-DOM capture pipeline. For a review of the code UNDER CHANGE (diff, branch, PR), use the `review-a11y` skill instead."
5
5
  license: MIT
6
6
  metadata:
7
- version: 5.3.2
7
+ version: 5.3.3
8
8
  ---
9
9
 
10
10
  # ultra11y — audit WCAG 2.2 AA and write accessible markup
@@ -6,7 +6,7 @@ import { join as join50, relative as relative5, resolve as resolve15, sep as sep
6
6
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL3 } from "url";
7
7
 
8
8
  // src/types.ts
9
- var VERSION = "5.3.2";
9
+ var VERSION = "5.3.3";
10
10
  var SCHEMA_VERSION = 2;
11
11
 
12
12
  // src/audit.ts
@@ -60058,10 +60058,14 @@ var S = {
60058
60058
  clamped: (n) => `_${n} groupe(s) retir\xE9(s) de ce commentaire pour tenir dans la limite de GitHub \u2014 le r\xE9sum\xE9 de job les porte tous._`,
60059
60059
  unanchored: (n) => `${n} constat(s) rattach\xE9(s) \xE0 une URL, sans ligne de code \xE0 annoter \u2014 voir le rapport.`,
60060
60060
  unattributed: (n) => `${n} constat(s) ne sont rattach\xE9s \xE0 aucune page (code partag\xE9, fichier hors routes) \u2014 compt\xE9s dans l'audit global, jamais r\xE9partis d'office.`,
60061
- sourceBasis: "Une page marqu\xE9e \xAB source \xBB n'a pas d'instantan\xE9 : l'absence de constat n'y vaut PAS conformit\xE9, et son taux ne porte que sur ce que le moteur a pu d\xE9cider ailleurs.",
60061
+ sourceBasis: "Une page marqu\xE9e \xAB source \xBB n'a pas d'instantan\xE9 : l'absence de constat n'y vaut PAS conformit\xE9. Ses crit\xE8res non d\xE9cid\xE9s restent \xAB \xE0 \xE9valuer \xBB, ils ne basculent jamais en conformes par silence.",
60062
60062
  pageByPage: "page par page",
60063
60063
  pagesCount: (n) => `${n} page(s)`,
60064
60064
  testsCol: "Tests",
60065
+ conformingCol: "C",
60066
+ nonConformingCol: "NC",
60067
+ toAssessCol: "\xC0 \xE9valuer",
60068
+ scoreboardNote: "`C` conforme \xB7 `NC` non conforme \xB7 \xAB \xC0 \xE9valuer \xBB : personne ne l'a encore tranch\xE9 \u2014 ni conforme ni non conforme. Pas de pourcentage ici : un taux calcul\xE9 sur les seuls crit\xE8res d\xE9cid\xE9s se lit comme une note de page, et vaut la m\xEAme chose sur une bonne page que sur une mauvaise. Le taux et sa couverture sont dans la fiche par page de l'artefact.",
60065
60069
  noPages: "Aucune page dans le p\xE9rim\xE8tre de ce run : le balayage n'a produit aucun instantan\xE9. Ce n'est pas un bilan vide, c'est un bilan absent \u2014 les crit\xE8res au rendu restent \xE0 \xE9valuer.",
60066
60070
  pagesDetailNote: "Un bloc par page portant au moins une non-conformit\xE9, et seulement ses crit\xE8res **non conformes** \u2014 la grille compl\xE8te (tous les crit\xE8res de chaque page, avec leurs tests et leurs captures) vit dans l'artefact.",
60067
60071
  pagesClamped: (n) => `_Le d\xE9tail de ${n} page(s) a \xE9t\xE9 retir\xE9 de ce commentaire pour tenir dans la limite de GitHub \u2014 l'artefact les porte toutes._`,
@@ -60099,10 +60103,14 @@ var S = {
60099
60103
  clamped: (n) => `_${n} group(s) dropped from this comment to fit GitHub's limit \u2014 the job summary carries them all._`,
60100
60104
  unanchored: (n) => `${n} finding(s) keyed to a URL, with no code line to annotate \u2014 see the report.`,
60101
60105
  unattributed: (n) => `${n} finding(s) are attributed to no page (shared code, file outside any route) \u2014 counted in the overall audit, never spread across pages.`,
60102
- sourceBasis: 'A page marked "source" has no snapshot: the absence of a finding there does NOT mean conforming, and its rate covers only what the engine could decide elsewhere.',
60106
+ sourceBasis: 'A page marked "source" has no snapshot: the absence of a finding there does NOT mean conforming. Its undecided criteria stay \u201Cto assess\u201D; they never turn conforming by silence.',
60103
60107
  pageByPage: "page by page",
60104
60108
  pagesCount: (n) => `${n} page(s)`,
60105
60109
  testsCol: "Tests",
60110
+ conformingCol: "C",
60111
+ nonConformingCol: "NC",
60112
+ toAssessCol: "To assess",
60113
+ scoreboardNote: "`C` conforming \xB7 `NC` non-conforming \xB7 \u201CTo assess\u201D: nobody has ruled on it yet \u2014 neither conforming nor not. No percentage here: a rate over the decided criteria alone reads as a page score, and reads the same on a good page as on a bad one. The rate and its coverage live in the artifact's per-page sheet.",
60106
60114
  noPages: "No page in this run's scope: the sweep produced no snapshot. This is not an empty scoreboard, it is a missing one \u2014 the rendering criteria stay to assess.",
60107
60115
  pagesDetailNote: "One block per page carrying at least one non-conformity, and only its **non-conforming** criteria \u2014 the full grid (every criterion of every page, with its tests and its screenshot) lives in the artifact.",
60108
60116
  pagesClamped: (n) => `_The detail of ${n} page(s) was dropped from this comment to fit GitHub's limit \u2014 the artifact carries them all._`,
@@ -60226,13 +60234,15 @@ function perPageTable(result, standard = CORE2, lang = "en") {
60226
60234
  return [`### ${s.perPage}`, "", ...scoreboardTable(result, derived, standard, s, lang), "", ...basisCaveats(result, derived, s, lang)].join("\n");
60227
60235
  }
60228
60236
  function scoreboardTable(result, derived, standard, s, lang) {
60229
- const out2 = [`| ${s.page} | ${s.basis} | ${s.pageRate} | \u{1F534} | \u{1F7E0} | \u{1F7E1} |`, "| --- | --- | ---: | ---: | ---: | ---: |"];
60237
+ const out2 = [
60238
+ `| ${s.page} | ${s.basis} | ${s.conformingCol} | ${s.nonConformingCol} | ${s.toAssessCol} | \u{1F534} | \u{1F7E0} | \u{1F7E1} |`,
60239
+ "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |"
60240
+ ];
60230
60241
  for (const pg of derived) {
60231
60242
  const n = (sev) => severityCount(pg, sev);
60232
- const rows = pageCriterionRows(result, pg, standard, lang);
60233
- const cov = pageCoverage(rows);
60243
+ const t2 = pageTally(pageCriterionRows(result, pg, standard, lang));
60234
60244
  out2.push(
60235
- `| ${pg.name}${pg.auth ? " \u{1F512}" : ""} \u2014 \`${pg.url}\` | ${basisLabel(pg.basis, lang)} | ${formatRate(pageRatePct(rows), cov.decided, cov.total)} | ${n("bloquant")} | ${n("majeur")} | ${n("mineur")} |`
60245
+ `| ${pg.name}${pg.auth ? " \u{1F512}" : ""} \u2014 \`${pg.url}\` | ${basisLabel(pg.basis, lang)} | ${t2.c} | ${t2.nc} | ${t2.manual} | ${n("bloquant")} | ${n("majeur")} | ${n("mineur")} |`
60236
60246
  );
60237
60247
  }
60238
60248
  return out2;
@@ -60241,7 +60251,7 @@ function severityCount(pg, sev) {
60241
60251
  return pg.findings.filter((f) => !f.advisory && f.severity === sev).length;
60242
60252
  }
60243
60253
  function basisCaveats(result, derived, s, lang) {
60244
- const out2 = [];
60254
+ const out2 = [`> ${s.scoreboardNote}`, ""];
60245
60255
  const orphans = unattributedFindings(result).filter((f) => !f.advisory).length;
60246
60256
  if (orphans) out2.push(`> ${s.unattributed(orphans)}`, "");
60247
60257
  if (derived.some((p) => p.basis === "attributed")) out2.push(`> ${s.sourceBasis}`, "");
package/src/annotate.ts CHANGED
@@ -36,7 +36,7 @@ import {
36
36
  renderRedirected,
37
37
  unattributedFindings,
38
38
  } from "./pages.js";
39
- import { pageCoverage, pageCriterionRows, pageRatePct, pageTally, pageTallyNote } from "./pages-report.js";
39
+ import { pageCriterionRows, pageTally, pageTallyNote } from "./pages-report.js";
40
40
 
41
41
  export interface AnnotateOptions {
42
42
  standard?: StandardId;
@@ -125,10 +125,15 @@ const S = {
125
125
  unattributed: (n: number) =>
126
126
  `${n} constat(s) ne sont rattachés à aucune page (code partagé, fichier hors routes) — comptés dans l'audit global, jamais répartis d'office.`,
127
127
  sourceBasis:
128
- "Une page marquée « source » n'a pas d'instantané : l'absence de constat n'y vaut PAS conformité, et son taux ne porte que sur ce que le moteur a pu décider ailleurs.",
128
+ "Une page marquée « source » n'a pas d'instantané : l'absence de constat n'y vaut PAS conformité. Ses critères non décidés restent « à évaluer », ils ne basculent jamais en conformes par silence.",
129
129
  pageByPage: "page par page",
130
130
  pagesCount: (n: number) => `${n} page(s)`,
131
131
  testsCol: "Tests",
132
+ conformingCol: "C",
133
+ nonConformingCol: "NC",
134
+ toAssessCol: "À évaluer",
135
+ scoreboardNote:
136
+ "`C` conforme · `NC` non conforme · « À évaluer » : personne ne l'a encore tranché — ni conforme ni non conforme. Pas de pourcentage ici : un taux calculé sur les seuls critères décidés se lit comme une note de page, et vaut la même chose sur une bonne page que sur une mauvaise. Le taux et sa couverture sont dans la fiche par page de l'artefact.",
132
137
  noPages:
133
138
  "Aucune page dans le périmètre de ce run : le balayage n'a produit aucun instantané. Ce n'est pas un bilan vide, c'est un bilan absent — les critères au rendu restent à évaluer.",
134
139
  pagesDetailNote:
@@ -173,10 +178,15 @@ const S = {
173
178
  unattributed: (n: number) =>
174
179
  `${n} finding(s) are attributed to no page (shared code, file outside any route) — counted in the overall audit, never spread across pages.`,
175
180
  sourceBasis:
176
- 'A page marked "source" has no snapshot: the absence of a finding there does NOT mean conforming, and its rate covers only what the engine could decide elsewhere.',
181
+ 'A page marked "source" has no snapshot: the absence of a finding there does NOT mean conforming. Its undecided criteria stay “to assess”; they never turn conforming by silence.',
177
182
  pageByPage: "page by page",
178
183
  pagesCount: (n: number) => `${n} page(s)`,
179
184
  testsCol: "Tests",
185
+ conformingCol: "C",
186
+ nonConformingCol: "NC",
187
+ toAssessCol: "To assess",
188
+ scoreboardNote:
189
+ "`C` conforming · `NC` non-conforming · “To assess”: nobody has ruled on it yet — neither conforming nor not. No percentage here: a rate over the decided criteria alone reads as a page score, and reads the same on a good page as on a bad one. The rate and its coverage live in the artifact's per-page sheet.",
180
190
  noPages:
181
191
  "No page in this run's scope: the sweep produced no snapshot. This is not an empty scoreboard, it is a missing one — the rendering criteria stay to assess.",
182
192
  pagesDetailNote:
@@ -391,20 +401,27 @@ export function perPageTable(result: AuditResult, standard: StandardId = CORE, l
391
401
  * comment draw ONE table from one projection — a second copy of this loop is a second chance
392
402
  * to disagree about what a page's rate is.
393
403
  *
394
- * The rate comes from `pageCriterionRows`, in the ACTIVE standard's vocabulary — NOT from
395
- * `PageResult.conformancePct`, which is always WCAG-keyed. Under a pack the two count
396
- * different things, and the gap is visible in one glance: a page rendered « 50 % (4/55) »
397
- * here while its own sheet in the artifact counted those 4 against RGAA's 106, and the run
398
- * headline right above said `(9/106)`. One document, one standard, two denominators —
399
- * exactly the drift `formatRate` exists to prevent. */
404
+ * COUNTS, not a percentage. Every status comes from `pageCriterionRows`, in the ACTIVE
405
+ * standard's vocabulary — `PageResult.conformancePct` is always WCAG-keyed and would report a
406
+ * pack page against the core's 55.
407
+ *
408
+ * A percentage was worse than useless here. When the judgment criteria are undecided — which
409
+ * is the NORMAL state without an adjudication pass, and the state a rejected one falls back to
410
+ * — a page has 2 conforming and 2 non-conforming out of 106, and the cell read « 50 % (4/106) »
411
+ * on every row. Half of four criteria is not half a page, but the eye reads a page score, and
412
+ * it read the same score for a good page and a bad one. The three counts cannot be misread:
413
+ * what conforms, what does not, and how much nobody has ruled on yet. The percentage stays in
414
+ * the artifact's per-page sheet, next to the coverage sentence that qualifies it. */
400
415
  function scoreboardTable(result: AuditResult, derived: PageResult[], standard: StandardId, s: (typeof S)[Lang], lang: Lang): string[] {
401
- const out: string[] = [`| ${s.page} | ${s.basis} | ${s.pageRate} | 🔴 | 🟠 | 🟡 |`, "| --- | --- | ---: | ---: | ---: | ---: |"];
416
+ const out: string[] = [
417
+ `| ${s.page} | ${s.basis} | ${s.conformingCol} | ${s.nonConformingCol} | ${s.toAssessCol} | 🔴 | 🟠 | 🟡 |`,
418
+ "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |",
419
+ ];
402
420
  for (const pg of derived) {
403
421
  const n = (sev: Severity): number => severityCount(pg, sev);
404
- const rows = pageCriterionRows(result, pg, standard, lang);
405
- const cov = pageCoverage(rows);
422
+ const t = pageTally(pageCriterionRows(result, pg, standard, lang));
406
423
  out.push(
407
- `| ${pg.name}${pg.auth ? " 🔒" : ""} — \`${pg.url}\` | ${basisLabel(pg.basis, lang)} | ${formatRate(pageRatePct(rows), cov.decided, cov.total)} | ${n("bloquant")} | ${n("majeur")} | ${n("mineur")} |`,
424
+ `| ${pg.name}${pg.auth ? " 🔒" : ""} — \`${pg.url}\` | ${basisLabel(pg.basis, lang)} | ${t.c} | ${t.nc} | ${t.manual} | ${n("bloquant")} | ${n("majeur")} | ${n("mineur")} |`,
408
425
  );
409
426
  }
410
427
  return out;
@@ -418,7 +435,8 @@ function severityCount(pg: PageResult, sev: Severity): number {
418
435
  * than a snapshot actually present. One caveat per basis, from the shared sentences — a
419
436
  * « non audité » page must not be explained by the note that asserts it has no snapshot. */
420
437
  function basisCaveats(result: AuditResult, derived: PageResult[], s: (typeof S)[Lang], lang: Lang): string[] {
421
- const out: string[] = [];
438
+ // What the three count columns mean, and why there is no percentage beside them.
439
+ const out: string[] = [`> ${s.scoreboardNote}`, ""];
422
440
  const orphans = unattributedFindings(result).filter((f) => !f.advisory).length;
423
441
  if (orphans) out.push(`> ${s.unattributed(orphans)}`, "");
424
442
  if (derived.some((p) => p.basis === "attributed")) out.push(`> ${s.sourceBasis}`, "");
package/src/types.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // Single source of truth for shared types + the embedded version. sync-version.mjs
2
2
  // rewrites VERSION at release time (kept in lockstep with package.json + SKILL.md).
3
- export const VERSION = "5.3.2";
3
+ export const VERSION = "5.3.3";
4
4
  export const SCHEMA_VERSION = 2;
5
5
 
6
6
  // Lang = the UI FRAME's language — the `L` localization tables each module (report,