@diegovelasquezweb/a11y-engine 0.11.46 → 0.11.48

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.
@@ -1 +1 @@
1
- export default {"patterns":[{"id":"placeholder-only-label","title":"Input uses placeholder as its only label","severity":"Critical","wcag":"WCAG 1.3.1 / 4.1.2 A","wcag_criterion":"1.3.1","wcag_level":"A","type":"structural","fix_description":"Placeholder text is not a label — it disappears on input and is not reliably announced by screen readers. This applies to ALL form controls: <input>, <textarea>, and <select>. Fix using one of these approaches:\n\n**Option A — Visually hidden label (preferred when no visible label exists):**\n```jsx\n<label htmlFor=\"field-id\" className=\"sr-only\">Field name</label>\n<input id=\"field-id\" placeholder=\"...\" />\n```\n\n**Option B — aria-label (acceptable for inputs and textareas without a visible label):**\n```jsx\n<input aria-label=\"Search\" placeholder=\"Search\" />\n<textarea aria-label=\"Your message\" placeholder=\"Your message\"></textarea>\n```\n\n**Option C — Visible label already exists nearby (wire it up):**\n```jsx\n<label htmlFor=\"field-id\">Field name</label>\n<input id=\"field-id\" placeholder=\"...\" />\n```\n\n**For textarea elements**, apply the same approach as for input:\n```jsx\n<label htmlFor=\"message\" className=\"sr-only\">Your message</label>\n<textarea id=\"message\" placeholder=\"Your message\"></textarea>\n```\n\nNever rely on `placeholder` alone as the accessible name. Always verify the label is announced correctly with a screen reader.","requires_manual_verification":true,"regex":"\\bplaceholder=[\"']","globs":["**/*.tsx","**/*.jsx","**/*.html","**/*.vue","**/*.svelte","**/*.astro"],"context_reject_regex":"aria-label|aria-labelledby|for=|htmlFor=|<label","context_window":6},{"id":"mouseover-without-focus","title":"Hover handler has no keyboard focus equivalent","severity":"Serious","wcag":"WCAG 2.1.1 A","wcag_criterion":"2.1.1","wcag_level":"A","type":"structural","fix_description":"Add `onFocus`/`onBlur` (or `onFocusIn`/`onFocusOut`) handlers alongside every `onMouseOver`/`onMouseEnter` handler so keyboard users receive the same interaction.","requires_manual_verification":true,"regex":"onMouseOver=|onMouseEnter=","globs":["**/*.tsx","**/*.jsx","**/*.vue","**/*.svelte","**/*.html"],"context_reject_regex":"onFocus[^I]|onFocusIn","context_window":10},{"id":"new-window-no-warning","title":"Link opens in new tab without warning","severity":"Serious","wcag":"WCAG 3.2.2 A","wcag_criterion":"3.2.2","wcag_level":"A","type":"structural","fix_description":"Add visible text such as `(opens in new tab)` or an `aria-label` containing that warning. Always pair `target=\"_blank\"` with `rel=\"noopener noreferrer\"`.","requires_manual_verification":true,"regex":"target=[\"']_blank[\"']","globs":["**/*.tsx","**/*.jsx","**/*.vue","**/*.svelte","**/*.html","**/*.astro"],"context_reject_regex":"new.tab|new.window|opens.in|sr-only","context_window":5},{"id":"spa-route-title","title":"SPA navigation does not update document.title","severity":"Serious","wcag":"WCAG 2.4.2 A","wcag_criterion":"2.4.2","wcag_level":"A","type":"structural","fix_description":"Set `document.title` after every router navigation call to reflect the new route's page title. Screen reader users rely on the title to know when the page has changed.","requires_manual_verification":true,"regex":"router\\.push\\(|router\\.replace\\(|navigate\\(|useNavigate\\(","globs":["**/*.tsx","**/*.jsx","**/*.ts","**/*.js","**/*.vue"],"context_reject_regex":"document\\.title","context_window":20},{"id":"focus-outline-suppressed","title":"Focus outline suppressed without replacement","severity":"Serious","wcag":"WCAG 2.4.7 AA","wcag_criterion":"2.4.7","wcag_level":"AA","type":"style","fix_description":"Replace `outline: none` / `outline: 0` with a `:focus-visible` rule that renders a clearly visible custom focus indicator (e.g., a 2px solid high-contrast outline). Never suppress focus outlines globally.","requires_manual_verification":true,"regex":"outline:\\s*none|outline:\\s*0|focus:outline-none","globs":["**/*.css","**/*.scss","**/*.sass","**/*.tsx","**/*.jsx","**/*.html","**/*.vue","**/*.svelte","**/*.astro"],"context_reject_regex":":focus-visible","context_window":5},{"id":"orientation-lock","title":"Screen orientation locked programmatically","severity":"Moderate","wcag":"WCAG 1.3.4 AA","wcag_criterion":"1.3.4","wcag_level":"AA","type":"structural","fix_description":"Remove the programmatic orientation lock. If a specific orientation is essential for the content, provide an accessible alternative layout for the other orientation.","requires_manual_verification":false,"regex":"screen\\.orientation\\.lock\\(|lockOrientation\\(","globs":["**/*.ts","**/*.js","**/*.tsx","**/*.jsx","**/*.vue","**/*.svelte"],"context_reject_regex":null,"context_window":0},{"id":"character-key-shortcut","title":"Single-character accesskey shortcut with no override mechanism","severity":"Moderate","wcag":"WCAG 2.1.4 A","wcag_criterion":"2.1.4","wcag_level":"A","type":"structural","fix_description":"Remove the `accesskey` attribute or provide a user-facing mechanism to remap or disable it. Single-character shortcuts conflict with screen reader and speech-input keystroke commands.","requires_manual_verification":false,"regex":"\\baccesskey=","globs":["**/*.html","**/*.tsx","**/*.jsx","**/*.vue","**/*.svelte","**/*.astro"],"context_reject_regex":null,"context_window":0}]};
1
+ export default {"patterns":[{"id":"placeholder-only-label","title":"Input uses placeholder as its only label","severity":"Critical","wcag":"WCAG 1.3.1 / 4.1.2 A","wcag_criterion":"1.3.1","wcag_level":"A","type":"structural","fix_description":"Placeholder text is not a label — it disappears on input and is not reliably announced by screen readers. This applies to ALL form controls: <input>, <textarea>, and <select>. Fix using one of these approaches:\n\n**Option A — Visually hidden label (preferred when no visible label exists):**\n```jsx\n<label htmlFor=\"field-id\" className=\"sr-only\">Field name</label>\n<input id=\"field-id\" placeholder=\"...\" />\n```\n\n**Option B — aria-label (acceptable for inputs and textareas without a visible label):**\n```jsx\n<input aria-label=\"Search\" placeholder=\"Search\" />\n<textarea aria-label=\"Your message\" placeholder=\"Your message\"></textarea>\n```\n\n**Option C — Visible label already exists nearby (wire it up):**\n```jsx\n<label htmlFor=\"field-id\">Field name</label>\n<input id=\"field-id\" placeholder=\"...\" />\n```\n\n**For textarea elements**, apply the same approach as for input:\n```jsx\n<label htmlFor=\"message\" className=\"sr-only\">Your message</label>\n<textarea id=\"message\" placeholder=\"Your message\"></textarea>\n```\n\nNever rely on `placeholder` alone as the accessible name. Always verify the label is announced correctly with a screen reader.","requires_manual_verification":true,"regex":"\\bplaceholder=[\"']","globs":["**/*.tsx","**/*.jsx","**/*.html","**/*.vue","**/*.svelte","**/*.astro"],"context_reject_regex":"aria-label|aria-labelledby|for=|htmlFor=|<label","context_window":6},{"id":"mouseover-without-focus","title":"Hover handler has no keyboard focus equivalent","severity":"Serious","wcag":"WCAG 2.1.1 A","wcag_criterion":"2.1.1","wcag_level":"A","type":"structural","fix_description":"Add `onFocus`/`onBlur` (or `onFocusIn`/`onFocusOut`) handlers alongside every `onMouseOver`/`onMouseEnter` handler so keyboard users receive the same interaction.","requires_manual_verification":true,"regex":"onMouseOver=|onMouseEnter=","globs":["**/*.tsx","**/*.jsx","**/*.vue","**/*.svelte","**/*.html"],"context_reject_regex":"onFocus[^I]|onFocusIn","context_window":10},{"id":"new-window-no-warning","title":"Link opens in new tab without warning","severity":"Serious","wcag":"WCAG 3.2.2 A","wcag_criterion":"3.2.2","wcag_level":"A","type":"structural","fix_description":"Add visible text such as `(opens in new tab)` or an `aria-label` containing that warning. Always pair `target=\"_blank\"` with `rel=\"noopener noreferrer\"`.","requires_manual_verification":true,"regex":"target=[\"']_blank[\"']","globs":["**/*.tsx","**/*.jsx","**/*.vue","**/*.svelte","**/*.html","**/*.astro"],"context_reject_regex":"new.tab|new.window|opens.in|sr-only","context_window":5},{"id":"spa-route-title","title":"SPA navigation does not update document.title","severity":"Serious","wcag":"WCAG 2.4.2 A","wcag_criterion":"2.4.2","wcag_level":"A","type":"structural","fix_description":"Set `document.title` after every router navigation call to reflect the new route's page title. Screen reader users rely on the title to know when the page has changed.","requires_manual_verification":true,"regex":"router\\.push\\(|router\\.replace\\(|navigate\\(|useNavigate\\(","globs":["**/*.tsx","**/*.jsx","**/*.ts","**/*.js","**/*.vue"],"context_reject_regex":"document\\.title","context_window":20},{"id":"focus-outline-suppressed","title":"Focus outline suppressed without replacement","severity":"Serious","wcag":"WCAG 2.4.7 AA","wcag_criterion":"2.4.7","wcag_level":"AA","type":"style","fix_description":"Replace `outline: none` / `outline: 0` with a `:focus-visible` rule that renders a clearly visible custom focus indicator (e.g., a 2px solid high-contrast outline). Never suppress focus outlines globally.\n\n**If the suppression is in a CSS/SCSS file:**\n```css\n/* Before */\n.btn { outline: none; }\n\n/* After */\n.btn:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }\n```\n\n**If the suppression is in an inline `style` attribute in HTML/JSX:**\nRemove `outline: none` (or `outline: 0`) from the `style` attribute entirely. Inline styles cannot use `:focus-visible`, so the fix is to remove the suppression and rely on the browser default or a CSS class.\n```html\n<!-- Before -->\n<button style=\"outline: none; padding: 8px 16px;\">Label</button>\n\n<!-- After -->\n<button style=\"padding: 8px 16px;\">Label</button>\n```\nIf a custom focus indicator is needed, add a CSS class with `:focus-visible` instead of using an inline style.","requires_manual_verification":true,"regex":"outline:\\s*none|outline:\\s*0|focus:outline-none","globs":["**/*.css","**/*.scss","**/*.sass","**/*.tsx","**/*.jsx","**/*.html","**/*.vue","**/*.svelte","**/*.astro"],"context_reject_regex":":focus-visible","context_window":5},{"id":"orientation-lock","title":"Screen orientation locked programmatically","severity":"Moderate","wcag":"WCAG 1.3.4 AA","wcag_criterion":"1.3.4","wcag_level":"AA","type":"structural","fix_description":"Remove the programmatic orientation lock. If a specific orientation is essential for the content, provide an accessible alternative layout for the other orientation.","requires_manual_verification":false,"regex":"screen\\.orientation\\.lock\\(|lockOrientation\\(","globs":["**/*.ts","**/*.js","**/*.tsx","**/*.jsx","**/*.vue","**/*.svelte"],"context_reject_regex":null,"context_window":0},{"id":"character-key-shortcut","title":"Single-character accesskey shortcut with no override mechanism","severity":"Moderate","wcag":"WCAG 2.1.4 A","wcag_criterion":"2.1.4","wcag_level":"A","type":"structural","fix_description":"Remove the `accesskey` attribute or provide a user-facing mechanism to remap or disable it. Single-character shortcuts conflict with screen reader and speech-input keystroke commands.","requires_manual_verification":false,"regex":"\\baccesskey=","globs":["**/*.html","**/*.tsx","**/*.jsx","**/*.vue","**/*.svelte","**/*.astro"],"context_reject_regex":null,"context_window":0}]};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diegovelasquezweb/a11y-engine",
3
- "version": "0.11.46",
3
+ "version": "0.11.48",
4
4
  "description": "WCAG 2.2 accessibility audit engine — scanner, analyzer, and report builders",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -32,6 +32,9 @@
32
32
  "CHANGELOG.md",
33
33
  "LICENSE"
34
34
  ],
35
+ "scripts": {
36
+ "test": "vitest run"
37
+ },
35
38
  "dependencies": {
36
39
  "@axe-core/playwright": "^4.11.1",
37
40
  "axe-core": "^4.11.1",
@@ -41,7 +44,5 @@
41
44
  "devDependencies": {
42
45
  "vitest": "^4.0.18"
43
46
  },
44
- "scripts": {
45
- "test": "vitest run"
46
- }
47
- }
47
+ "packageManager": "pnpm@10.22.0+sha512.bf049efe995b28f527fd2b41ae0474ce29186f7edcb3bf545087bd61fbbebb2bf75362d1307fda09c2d288e1e499787ac12d4fcb617a974718a6051f2eee741c"
48
+ }
File without changes
@@ -1059,7 +1059,9 @@ export async function applyFindingsFix(input) {
1059
1059
 
1060
1060
  // If Claude tagged changes with findingId, use those for per-finding success tracking.
1061
1061
  // If no findingId tags were emitted, fall back to group-level success for all findings.
1062
+ // If no files were changed at all, the issue was already resolved — mark accordingly.
1062
1063
  const hasFindingIdTracking = applied.succeededFindingIds.size > 0;
1064
+ const anyFileChanged = applied.changedFiles && applied.changedFiles.length > 0;
1063
1065
 
1064
1066
  for (const finding of withRules) {
1065
1067
  const ruleId = typeof finding.rule_id === "string" ? finding.rule_id.trim() : "";
@@ -1068,14 +1070,18 @@ export async function applyFindingsFix(input) {
1068
1070
 
1069
1071
  const findingApplied = hasFindingIdTracking
1070
1072
  ? applied.succeededFindingIds.has(finding.id)
1071
- : true;
1073
+ : anyFileChanged;
1074
+
1075
+ const alreadyResolved = !hasFindingIdTracking && !anyFileChanged;
1072
1076
 
1073
1077
  resultMap.set(
1074
1078
  finding.id,
1075
1079
  makeResult(finding.id, {
1076
- applied: findingApplied,
1077
- reason: findingApplied ? "" : FIX_ERROR_CODES.PATCH_APPLY_FAILED,
1078
- message: findingApplied
1080
+ applied: alreadyResolved ? true : findingApplied,
1081
+ reason: findingApplied || alreadyResolved ? "" : FIX_ERROR_CODES.PATCH_APPLY_FAILED,
1082
+ message: alreadyResolved
1083
+ ? "Already resolved — no changes needed."
1084
+ : findingApplied
1079
1085
  ? "Patch applied successfully."
1080
1086
  : "The change for this finding could not be applied (search block not found).",
1081
1087
  changedFiles: applied.changedFiles,
File without changes
File without changes
package/assets/.DS_Store DELETED
Binary file