@wcag-checkr/ci 1.0.0-rc.31 → 1.0.0-rc.310

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 (104) hide show
  1. package/dist/assets/ErrorBoundary-C-kswn4E.js +594 -0
  2. package/dist/assets/ai-usage-log-BX3L6bKl.js +1 -0
  3. package/dist/assets/content-script.ts-FuMy_sE5.js +217 -0
  4. package/dist/assets/{content-script.ts-loader-Dfu1UEfD.js → content-script.ts-loader-CBHeu186.js} +1 -1
  5. package/dist/assets/copy-ai-fixer-prompt-DQYkHOv3.js +19 -0
  6. package/dist/assets/{crash-reporter-Dc5lvxvY.js → crash-reporter-Bu2p8K-p.js} +1 -1
  7. package/dist/assets/design-system-audit-DpxJrxnb.js +1 -0
  8. package/dist/assets/devtools-panel-DFQvqKKj.js +1 -0
  9. package/dist/assets/diff-DA41zYPc.js +1 -0
  10. package/dist/assets/dom-criterion-analyzers-DoUaJV5C.js +8 -0
  11. package/dist/assets/fraunces-latin-400-normal-6IfK1voy.woff2 +0 -0
  12. package/dist/assets/fraunces-latin-400-normal-NUPT2cO8.woff +0 -0
  13. package/dist/assets/fraunces-latin-500-normal-BTR4KCeb.woff +0 -0
  14. package/dist/assets/fraunces-latin-500-normal-DnGCNyPD.woff2 +0 -0
  15. package/dist/assets/fraunces-latin-600-normal-BFCDtZfi.woff2 +0 -0
  16. package/dist/assets/fraunces-latin-600-normal-DL5QCzvS.woff +0 -0
  17. package/dist/assets/fraunces-latin-ext-400-normal-D8gbi3Gu.woff2 +0 -0
  18. package/dist/assets/fraunces-latin-ext-400-normal-UihxqfOe.woff +0 -0
  19. package/dist/assets/fraunces-latin-ext-500-normal-BMcFk1Xs.woff +0 -0
  20. package/dist/assets/fraunces-latin-ext-500-normal-Z5DV8IzT.woff2 +0 -0
  21. package/dist/assets/fraunces-latin-ext-600-normal-B0Dy4lqi.woff +0 -0
  22. package/dist/assets/fraunces-latin-ext-600-normal-BtzmzP0X.woff2 +0 -0
  23. package/dist/assets/fraunces-vietnamese-400-normal-B65MOf9T.woff +0 -0
  24. package/dist/assets/fraunces-vietnamese-400-normal-CvGt0Ybw.woff2 +0 -0
  25. package/dist/assets/fraunces-vietnamese-500-normal-B-KbxExq.woff +0 -0
  26. package/dist/assets/fraunces-vietnamese-500-normal-GOH_-EGq.woff2 +0 -0
  27. package/dist/assets/fraunces-vietnamese-600-normal-BjlAJixd.woff2 +0 -0
  28. package/dist/assets/fraunces-vietnamese-600-normal-DlAl5EAR.woff +0 -0
  29. package/dist/assets/geist-sans-latin-400-normal-BOaIZNA2.woff +0 -0
  30. package/dist/assets/geist-sans-latin-400-normal-gapTbOY8.woff2 +0 -0
  31. package/dist/assets/geist-sans-latin-500-normal-CN2lyvyL.woff +0 -0
  32. package/dist/assets/geist-sans-latin-500-normal-uokXdC-Q.woff2 +0 -0
  33. package/dist/assets/geist-sans-latin-600-normal-CA1yjETN.woff +0 -0
  34. package/dist/assets/geist-sans-latin-600-normal-DFOURf8L.woff2 +0 -0
  35. package/dist/assets/geist-sans-latin-700-normal-BmN9tIp5.woff2 +0 -0
  36. package/dist/assets/geist-sans-latin-700-normal-CjScfYeH.woff +0 -0
  37. package/dist/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  38. package/dist/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  39. package/dist/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
  40. package/dist/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
  41. package/dist/assets/jetbrains-mono-cyrillic-600-normal-8K4wrrwR.woff +0 -0
  42. package/dist/assets/jetbrains-mono-cyrillic-600-normal-EVf6-Yzo.woff2 +0 -0
  43. package/dist/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  44. package/dist/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  45. package/dist/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
  46. package/dist/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
  47. package/dist/assets/jetbrains-mono-greek-600-normal-H7WoG9Et.woff2 +0 -0
  48. package/dist/assets/jetbrains-mono-greek-600-normal-mc2nkWzM.woff +0 -0
  49. package/dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  50. package/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  51. package/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  52. package/dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  53. package/dist/assets/jetbrains-mono-latin-600-normal-BfsvjouI.woff +0 -0
  54. package/dist/assets/jetbrains-mono-latin-600-normal-C8RAYTDA.woff2 +0 -0
  55. package/dist/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  56. package/dist/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  57. package/dist/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
  58. package/dist/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
  59. package/dist/assets/jetbrains-mono-latin-ext-600-normal-BfB_LPfz.woff2 +0 -0
  60. package/dist/assets/jetbrains-mono-latin-ext-600-normal-DObL3zCW.woff +0 -0
  61. package/dist/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  62. package/dist/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
  63. package/dist/assets/jetbrains-mono-vietnamese-600-normal-OWROknRo.woff +0 -0
  64. package/dist/assets/options-BPhjrbGI.js +6 -0
  65. package/dist/assets/parallel-tab-flow-Xk9RSjay.js +1 -0
  66. package/dist/assets/scheduled-audit-runner-DyKpb3zg.js +2167 -0
  67. package/dist/assets/service-worker.ts-CMkltOzu.js +2 -0
  68. package/dist/assets/side-panel-Ctm2yXeo.css +1 -0
  69. package/dist/assets/side-panel-f_X4NOJt.js +4 -0
  70. package/dist/assets/site-report-renderer-DNgytqhZ.js +189 -0
  71. package/dist/assets/{styles-C4Kq0zOO.js → styles-Cn731SYD.js} +13 -13
  72. package/dist/assets/styles-d5msFsnl.css +1 -0
  73. package/dist/assets/zip-encoder-CtULHXx_.js +1 -0
  74. package/dist/axe.min.js +12 -0
  75. package/dist/devtools/panel.html +9 -8
  76. package/dist/manifest.json +11 -8
  77. package/dist/options/options.html +5 -6
  78. package/dist/service-worker-loader.js +1 -1
  79. package/dist/side-panel/App.tsx +129 -5
  80. package/dist/side-panel/audit-launcher.ts +21 -1
  81. package/dist/side-panel/azure-devops-issue.test.ts +68 -0
  82. package/dist/side-panel/azure-devops-issue.ts +89 -0
  83. package/dist/side-panel/gitlab-issue.test.ts +53 -0
  84. package/dist/side-panel/gitlab-issue.ts +78 -0
  85. package/dist/side-panel/main.tsx +39 -2
  86. package/dist/side-panel/side-panel.html +11 -8
  87. package/dist/side-panel/store.ts +149 -13
  88. package/dist/side-panel/styles.css +39 -0
  89. package/dist/side-panel/wire-messaging.ts +146 -9
  90. package/package.json +1 -1
  91. package/wcagcheckr-ci.mjs +193 -32
  92. package/dist/assets/ErrorBoundary-BLcMSVSr.js +0 -524
  93. package/dist/assets/ai-usage-log-Dj9Ub_DT.js +0 -1
  94. package/dist/assets/content-script.ts-CwcUMq3e.js +0 -181
  95. package/dist/assets/devtools-panel-DQ3Bbomf.js +0 -1
  96. package/dist/assets/diff-D4sCAdXf.js +0 -1
  97. package/dist/assets/forensic-log-B1UCXZ23.js +0 -129
  98. package/dist/assets/options-BG2i5vFf.js +0 -6
  99. package/dist/assets/preload-helper-D7HrI6pR.js +0 -1
  100. package/dist/assets/service-worker.ts-CO86CV_p.js +0 -715
  101. package/dist/assets/side-panel-XSB07vDa.js +0 -1
  102. package/dist/assets/site-report-renderer-CyHkM6hB.js +0 -147
  103. package/dist/assets/state-PELIq3oj.js +0 -1
  104. package/dist/assets/styles-Cevp58mS.css +0 -1
@@ -3,16 +3,17 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <title>wcagcheckr</title>
6
- <script type="module" crossorigin src="/assets/devtools-panel-DQ3Bbomf.js"></script>
6
+ <script type="module" crossorigin src="/assets/devtools-panel-DFQvqKKj.js"></script>
7
7
  <link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-B5Qt9EMX.js">
8
8
  <link rel="modulepreload" crossorigin href="/assets/_commonjsHelpers-Cpj98o6Y.js">
9
- <link rel="modulepreload" crossorigin href="/assets/crash-reporter-Dc5lvxvY.js">
10
- <link rel="modulepreload" crossorigin href="/assets/state-PELIq3oj.js">
11
- <link rel="modulepreload" crossorigin href="/assets/styles-C4Kq0zOO.js">
12
- <link rel="modulepreload" crossorigin href="/assets/preload-helper-D7HrI6pR.js">
13
- <link rel="modulepreload" crossorigin href="/assets/forensic-log-B1UCXZ23.js">
14
- <link rel="modulepreload" crossorigin href="/assets/ErrorBoundary-BLcMSVSr.js">
15
- <link rel="stylesheet" crossorigin href="/assets/styles-Cevp58mS.css">
9
+ <link rel="modulepreload" crossorigin href="/assets/crash-reporter-Bu2p8K-p.js">
10
+ <link rel="modulepreload" crossorigin href="/assets/ai-usage-log-BX3L6bKl.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/styles-Cn731SYD.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/diff-DA41zYPc.js">
13
+ <link rel="modulepreload" crossorigin href="/assets/scheduled-audit-runner-DyKpb3zg.js">
14
+ <link rel="modulepreload" crossorigin href="/assets/design-system-audit-DpxJrxnb.js">
15
+ <link rel="modulepreload" crossorigin href="/assets/ErrorBoundary-C-kswn4E.js">
16
+ <link rel="stylesheet" crossorigin href="/assets/styles-d5msFsnl.css">
16
17
  </head>
17
18
  <body class="bg-slate-50">
18
19
  <div id="root"></div>
@@ -2,8 +2,8 @@
2
2
  "manifest_version": 3,
3
3
  "name": "wcagcheckr",
4
4
  "description": "Audit components across hover, focus, dark mode, forced colors, RTL — every state your users actually encounter. Per-component baselines surface only NEW violations.",
5
- "version": "1.0.0.31",
6
- "version_name": "1.0.0-rc.31",
5
+ "version": "1.0.0.310",
6
+ "version_name": "1.0.0-rc.310",
7
7
  "author": "Locustware",
8
8
  "homepage_url": "https://wcagcheckr.com",
9
9
  "icons": {
@@ -19,7 +19,8 @@
19
19
  "scripting",
20
20
  "tabs",
21
21
  "alarms",
22
- "nativeMessaging"
22
+ "nativeMessaging",
23
+ "downloads"
23
24
  ],
24
25
  "host_permissions": [
25
26
  "<all_urls>"
@@ -36,7 +37,7 @@
36
37
  "content_scripts": [
37
38
  {
38
39
  "js": [
39
- "assets/content-script.ts-loader-Dfu1UEfD.js"
40
+ "assets/content-script.ts-loader-CBHeu186.js"
40
41
  ],
41
42
  "matches": [
42
43
  "<all_urls>"
@@ -58,13 +59,15 @@
58
59
  "side-panel/*",
59
60
  "assets/*",
60
61
  "fonts/*",
61
- "assets/preload-helper-D7HrI6pR.js",
62
- "assets/crash-reporter-Dc5lvxvY.js",
63
- "assets/diff-D4sCAdXf.js",
62
+ "axe.min.js",
63
+ "assets/diff-DA41zYPc.js",
64
+ "assets/crash-reporter-Bu2p8K-p.js",
64
65
  "assets/_commonjsHelpers-Cpj98o6Y.js",
65
66
  "assets/custom-rules-CAe0nbES.js",
67
+ "assets/design-system-audit-DpxJrxnb.js",
66
68
  "assets/reflow-analyzer-DNgBX8N_.js",
67
- "assets/content-script.ts-CwcUMq3e.js"
69
+ "assets/dom-criterion-analyzers-DoUaJV5C.js",
70
+ "assets/content-script.ts-FuMy_sE5.js"
68
71
  ],
69
72
  "use_dynamic_url": false
70
73
  }
@@ -4,15 +4,14 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>wcagcheckr — Settings</title>
7
- <script type="module" crossorigin src="/assets/options-BG2i5vFf.js"></script>
7
+ <script type="module" crossorigin src="/assets/options-BPhjrbGI.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="/assets/modulepreload-polyfill-B5Qt9EMX.js">
9
9
  <link rel="modulepreload" crossorigin href="/assets/_commonjsHelpers-Cpj98o6Y.js">
10
- <link rel="modulepreload" crossorigin href="/assets/crash-reporter-Dc5lvxvY.js">
11
- <link rel="modulepreload" crossorigin href="/assets/state-PELIq3oj.js">
12
- <link rel="modulepreload" crossorigin href="/assets/styles-C4Kq0zOO.js">
13
- <link rel="modulepreload" crossorigin href="/assets/ai-usage-log-Dj9Ub_DT.js">
10
+ <link rel="modulepreload" crossorigin href="/assets/crash-reporter-Bu2p8K-p.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/ai-usage-log-BX3L6bKl.js">
12
+ <link rel="modulepreload" crossorigin href="/assets/styles-Cn731SYD.js">
14
13
  <link rel="modulepreload" crossorigin href="/assets/custom-rules-CAe0nbES.js">
15
- <link rel="stylesheet" crossorigin href="/assets/styles-Cevp58mS.css">
14
+ <link rel="stylesheet" crossorigin href="/assets/styles-d5msFsnl.css">
16
15
  </head>
17
16
  <body class="m-0 p-0 bg-slate-50">
18
17
  <div id="root"></div>
@@ -1 +1 @@
1
- import './assets/service-worker.ts-CO86CV_p.js';
1
+ import './assets/service-worker.ts-CMkltOzu.js';
@@ -1,5 +1,15 @@
1
- import { useEffect } from 'react';
1
+ import { useEffect, useState } from 'react';
2
2
  import { useStore } from './store';
3
+ import {
4
+ HEADLESS_BUILD_LOCKDOWN,
5
+ isLockoutInEffectForTier,
6
+ } from '../shared/headless-build-lockdown';
7
+ // rc.79 — Reverted the rc.72 lazy-load NormalApp routing. Owner mode
8
+ // goes back to using OwnerView (lawfare-risk grade, Quick/Thorough
9
+ // scan depth, USER_RECIPES, full export set, no Suspense boundary).
10
+ // rc.72's NormalApp stripped those features and added a chunk-load
11
+ // step that wasn't there before; the regression wasn't worth the
12
+ // "two code paths" structural symmetry it claimed.
3
13
  import {
4
14
  wireMessaging,
5
15
  refreshBaselineList,
@@ -11,11 +21,15 @@ import {
11
21
  import { primeAuditLauncher } from './audit-launcher';
12
22
  import { sendToTab } from '../shared/messaging';
13
23
  import { getAuditTargetTabId } from '../shared/active-tab';
24
+ import { loadAcknowledged } from '../shared/acknowledged-findings';
25
+ import { getDismissalsForUrl } from '../shared/dismissals';
26
+ import { loadSiteCrawlReport } from '../shared/site-crawl-storage';
14
27
  import { Header } from './components/Header';
15
28
  import { AuditControls } from './components/AuditControls';
16
29
  import { VisualizerTools } from './components/VisualizerTools';
17
30
  import { ProgressBar } from './components/ProgressBar';
18
31
  import { MatrixView } from './views/MatrixView';
32
+ import { GeneralReportView } from './views/GeneralReportView';
19
33
  import { DeltaView } from './views/DeltaView';
20
34
  import { ActivityView } from './views/ActivityView';
21
35
  import { GuidedView } from './views/GuidedView';
@@ -35,12 +49,36 @@ import { UserModeWizard } from './components/UserModeWizard';
35
49
  import { OwnerView } from './views/OwnerView';
36
50
  import { ForensicLogView } from './views/ForensicLogView';
37
51
  import { WcagEmView } from './views/WcagEmView';
52
+ import { SchedulesView } from './views/SchedulesView';
53
+ import { RiskView } from './views/RiskView';
54
+ import { CopilotView } from './views/CopilotView';
55
+ import { AxTreeView } from './views/AxTreeView';
56
+ import { Wcag3View } from './views/Wcag3View';
57
+ import { DesignSystemView } from './views/DesignSystemView';
58
+ import { OnboardingChecklist } from './components/OnboardingChecklist';
38
59
  import { UpgradeCard } from './components/UpgradeCard';
39
60
  import { CriticalBanner } from './components/MessagesBell';
61
+ import { LicenseRequiredView } from './views/LicenseRequiredView';
40
62
 
41
63
  export function App() {
42
64
  const view = useStore((s) => s.view);
43
65
  const userMode = useStore((s) => s.userMode);
66
+ const tier = useStore((s) => s.tier);
67
+ // rc.124 — Headless-build lockdown. When active, only team-license holders
68
+ // get the full UI; everyone else sees the license-entry surface. The check
69
+ // is rendered EARLY so no other UI mounts for locked-out users (no audit
70
+ // controls, no visualizers, no exports). The audit dispatchers + export
71
+ // handlers also gate on the same helper for defense-in-depth.
72
+ const [tierResolved, setTierResolved] = useState(false);
73
+ useEffect(() => {
74
+ // Wait one tick for the initial refreshLicenseTier() call below to land
75
+ // before deciding lockout — otherwise the store's default 'trial' would
76
+ // render LicenseRequiredView for a real team license that's still being
77
+ // validated.
78
+ const t = setTimeout(() => setTierResolved(true), 600);
79
+ return () => clearTimeout(t);
80
+ }, []);
81
+ const lockoutActive = HEADLESS_BUILD_LOCKDOWN && tierResolved && isLockoutInEffectForTier(tier);
44
82
 
45
83
  useEffect(() => {
46
84
  const port = startKeepalive();
@@ -74,6 +112,55 @@ export function App() {
74
112
  // The Header shows the audited domain so the user always knows which
75
113
  // page the displayed results belong to.
76
114
  loadLastAudit().catch(() => {});
115
+ // rc.37 — load the user's "acknowledged" matchKeys so every
116
+ // ViolationCard can render the acknowledged pill immediately on
117
+ // mount (in every view, not just Delta).
118
+ loadAcknowledged()
119
+ .then((all) => {
120
+ useStore.getState().setAcknowledgedKeys(new Set(all.map((a) => a.matchKey)));
121
+ })
122
+ .catch(() => {});
123
+
124
+ // rc.200 — Subscribe to results changes and reload dismissals for
125
+ // the audited page URL. Dismissals are URL-scoped (not matchKey-
126
+ // scoped like acks), so we re-fetch every time a new audit
127
+ // completes against a different page.
128
+ const unsubResults = useStore.subscribe((state, prev) => {
129
+ const url = state.results[0]?.pageUrl;
130
+ const prevUrl = prev.results[0]?.pageUrl;
131
+ if (!url || url === prevUrl) return;
132
+ void getDismissalsForUrl(url)
133
+ .then((d) => {
134
+ useStore.getState().setDismissedKeys(new Set(Object.keys(d)));
135
+ })
136
+ .catch(() => {});
137
+ });
138
+
139
+ // rc.111 — rehydrate the most-recent site-crawl report so the AI fixer
140
+ // prompt's "## Site-wide context" section survives panel close / SW
141
+ // restart. setState (not setSiteCrawlReport) so we don't re-write the
142
+ // same data back to storage on hydration.
143
+ // rc.263 — Phase 2 of screen consolidation: when a crawl is in
144
+ // storage on side-panel open AND the user hasn't navigated
145
+ // somewhere else yet, default the view to 'crawl' so they land
146
+ // on the new inline-expandable master view instead of the empty
147
+ // Dashboard. Cliff: "we have so many screens for the user that
148
+ // it is fully confusing." If results from a single-page audit
149
+ // also exist, prefer 'report' (the user just did an audit and
150
+ // wants to see those results). View stays at whatever the user
151
+ // explicitly switches to after first paint.
152
+ loadSiteCrawlReport()
153
+ .then((report) => {
154
+ if (!report) return;
155
+ useStore.setState((prev) => {
156
+ const next: Partial<typeof prev> = { siteCrawlReport: report };
157
+ if (prev.view === 'matrix' && prev.results.length === 0) {
158
+ next.view = 'crawl';
159
+ }
160
+ return next;
161
+ });
162
+ })
163
+ .catch(() => {});
77
164
 
78
165
  // Esc inside the side panel clears the highlight pin. The page's own keydown
79
166
  // handler (in element-highlighter) only fires when the page has focus.
@@ -98,9 +185,21 @@ export function App() {
98
185
  port.disconnect();
99
186
  trackerPort?.disconnect();
100
187
  window.removeEventListener('keydown', onEsc);
188
+ unsubResults();
101
189
  };
102
190
  }, []);
103
191
 
192
+ // rc.124 — Headless-build lockdown takes precedence over EVERY other view.
193
+ // No persona wizard, no owner view, no dev view. Just the license entry.
194
+ if (lockoutActive) {
195
+ return (
196
+ <>
197
+ <LicenseRequiredView />
198
+ <Announcer />
199
+ </>
200
+ );
201
+ }
202
+
104
203
  // First launch — show the persona picker. Once chosen it persists in
105
204
  // chrome.storage.local; subsequent loads bypass the wizard.
106
205
  if (userMode === null) {
@@ -114,6 +213,8 @@ export function App() {
114
213
 
115
214
  // Owner mode — simplified surface. No view tabs, no visualizers, no IGT.
116
215
  // OwnerView holds its own header + footer for the simplified flow.
216
+ // (rc.79 reverted the rc.72 NormalApp routing; OwnerView is the right
217
+ // surface for Owner mode and was working before I broke it.)
117
218
  if (userMode === 'owner') {
118
219
  return (
119
220
  <>
@@ -140,20 +241,37 @@ export function App() {
140
241
  <UpgradeCard />
141
242
  <CriticalBanner />
142
243
  <Header />
244
+ <OnboardingChecklist />
143
245
  <main
144
246
  id="main-content"
145
247
  className="flex-1 flex flex-col overflow-hidden"
146
248
  aria-label="wcagcheckr"
147
249
  >
148
- <AuditControls />
149
- <VisualizerTools />
150
- <StorybookHint />
151
- <ResumeAuditBanner />
250
+ {/* rc.163 — Audit-launching surface (mode picker, throttle, target
251
+ chooser, visualizer toggles, storybook hint, resume banner) is
252
+ only relevant on the Dashboard, where the user is configuring
253
+ and starting an audit. On Findings and the other specialized
254
+ views these widgets just clutter the screen — Cliff: "remove
255
+ the form fields from the findings tab — distracting and
256
+ serves no real purpose being there — the tab will be better
257
+ fully focused on the findings."
258
+ Progress / error / AI-failure banners stay GLOBAL because
259
+ they're audit-state alerts the user needs to see regardless
260
+ of which view they happened to navigate to. */}
261
+ {view === 'matrix' && (
262
+ <>
263
+ <AuditControls />
264
+ <VisualizerTools />
265
+ <StorybookHint />
266
+ <ResumeAuditBanner />
267
+ </>
268
+ )}
152
269
  <ProgressBar />
153
270
  <ErrorBanner />
154
271
  <AiFailureBanner />
155
272
  <div className="flex-1 overflow-y-auto" role="region" aria-label={`${view} view`}>
156
273
  {view === 'matrix' && <MatrixView />}
274
+ {view === 'report' && <GeneralReportView />}
157
275
  {view === 'delta' && <DeltaView />}
158
276
  {view === 'activity' && <ActivityView />}
159
277
  {view === 'guided' && <GuidedView />}
@@ -162,6 +280,12 @@ export function App() {
162
280
  {view === 'crawl' && <SiteCrawlPanel />}
163
281
  {view === 'forensic' && <ForensicLogView />}
164
282
  {view === 'compliance' && <WcagEmView />}
283
+ {view === 'schedules' && <SchedulesView />}
284
+ {view === 'risk' && <RiskView />}
285
+ {view === 'copilot' && <CopilotView />}
286
+ {view === 'ax-tree' && <AxTreeView />}
287
+ {view === 'wcag3' && <Wcag3View />}
288
+ {view === 'tokens' && <DesignSystemView />}
165
289
  </div>
166
290
  </main>
167
291
  <ComplianceDisclaimer />
@@ -19,6 +19,8 @@
19
19
 
20
20
  import { send } from '../shared/messaging';
21
21
  import type { StartAuditAction, StartSiteCrawl } from '../shared/messages';
22
+ import { saveSiteCrawlReport } from '../shared/site-crawl-storage';
23
+ import { useStore } from './store';
22
24
 
23
25
  let cachedWindowId: number | null = null;
24
26
 
@@ -58,8 +60,26 @@ export function openFallbackPanel(): void {
58
60
  /** Drop-in replacement for `send(startAuditMessage)` at audit-start sites.
59
61
  * Opens chrome.sidePanel first (preserving user-gesture), then dispatches
60
62
  * the audit start to the service worker. Use for both START_AUDIT and
61
- * START_SITE_CRAWL — both run the state-matrix audit pipeline. */
63
+ * START_SITE_CRAWL — both run the state-matrix audit pipeline.
64
+ *
65
+ * rc.165 — Clear the cached site-crawl report at every audit start.
66
+ * Cliff's mental model: "remember when I click baseline, else not."
67
+ * Pre-rc.165 the crawl report persisted in chrome.storage.local
68
+ * forever (rc.111 added the persistence to survive panel close mid-
69
+ * crawl), so a single-page audit run days after a crawl would pull
70
+ * the stale crawl into the AI fix prompt's "Site-wide context"
71
+ * section. Now the crawl is session-ephemeral: any new audit start
72
+ * (single-page OR a fresh crawl) wipes the cached report. If the
73
+ * audit is itself a crawl, the new crawl will write a fresh report
74
+ * at completion. */
62
75
  export function launchAudit(msg: StartAuditAction | StartSiteCrawl): void {
63
76
  openFallbackPanel();
77
+ // Clear in-memory store first so any UI reads the empty state
78
+ // immediately, then clear persisted storage (best-effort).
79
+ useStore.getState().setSiteCrawlReport(null);
80
+ void saveSiteCrawlReport(null).catch(() => {
81
+ // Storage clears are best-effort; the in-memory clear above is the
82
+ // user-visible source of truth for the current session.
83
+ });
64
84
  void send(msg);
65
85
  }
@@ -0,0 +1,68 @@
1
+ // rc.139 — G9 — Unit tests for the Azure DevOps issue URL builder.
2
+
3
+ import { describe, it, expect } from 'vitest';
4
+ import { buildAzureDevOpsIssueUrl } from './azure-devops-issue';
5
+ import type { Violation } from '../types/audit';
6
+
7
+ function v(over: Partial<Violation> = {}): Violation {
8
+ return {
9
+ ruleId: 'image-alt', wcagCriterion: '1.1.1', wcagLevel: 'A', impact: 'serious',
10
+ description: 'Images must have alt', helpUrl: 'https://x',
11
+ target: { selector: 'img.hero', outerHTML: '<img>', failureSummary: '', tagName: 'IMG', role: null, accessibleName: null, textSnippet: null, attrId: null, attrTestid: null },
12
+ componentId: '::c', currentState: { pseudoState: 'default', theme: 'light', direction: 'ltr', viewport: 'desktop' } as unknown as Violation['currentState'],
13
+ axeVersion: '4.11.4', detectedAt: '2026-05-22T00:00:00Z', matchKey: 'mk',
14
+ ...over,
15
+ };
16
+ }
17
+
18
+ describe('buildAzureDevOpsIssueUrl (rc.139)', () => {
19
+ it('builds a URL targeting the Bug type by default', () => {
20
+ const r = buildAzureDevOpsIssueUrl('https://dev.azure.com/myorg/myproject', '::c', [v()]);
21
+ expect(r.url.startsWith('https://dev.azure.com/myorg/myproject/_workitems/create/Bug?')).toBe(true);
22
+ });
23
+
24
+ it('honors a custom work-item type', () => {
25
+ const r = buildAzureDevOpsIssueUrl(
26
+ 'https://dev.azure.com/myorg/myproject',
27
+ '::c',
28
+ [v()],
29
+ 'Accessibility Defect',
30
+ );
31
+ expect(r.url).toContain('/_workitems/create/Accessibility%20Defect?');
32
+ });
33
+
34
+ it('embeds title + description as bracketed params', () => {
35
+ const r = buildAzureDevOpsIssueUrl('https://dev.azure.com/myorg/myproject', '::c', [v()]);
36
+ // URLSearchParams encodes the bracket bytes; verify presence in the URL.
37
+ expect(r.url).toMatch(/%5BTitle%5D=/);
38
+ expect(r.url).toMatch(/%5BDescription%5D=/);
39
+ expect(r.title).toContain('1 new violation');
40
+ expect(r.description).toContain('image-alt');
41
+ expect(r.description).toContain('1.1.1');
42
+ });
43
+
44
+ it('caps URL length at 7800', () => {
45
+ // Build a huge violation set to overflow.
46
+ const many = Array.from({ length: 200 }, (_, i) =>
47
+ v({
48
+ ruleId: `rule-${i}`,
49
+ target: { ...v().target, selector: `.really.long.selector.with.lots.of.classes.number-${i}` },
50
+ }),
51
+ );
52
+ const r = buildAzureDevOpsIssueUrl('https://dev.azure.com/org/proj', '::c', many);
53
+ expect(r.url.length).toBeLessThanOrEqual(7800);
54
+ });
55
+
56
+ it('dedupes by rule + selector + aggregates state labels', () => {
57
+ const viols = [
58
+ v({ matchKey: 'a', currentState: { pseudoState: 'default', theme: 'light', direction: 'ltr', viewport: 'desktop' } as unknown as Violation['currentState'] }),
59
+ v({ matchKey: 'b', currentState: { pseudoState: 'hover', theme: 'light', direction: 'ltr', viewport: 'desktop' } as unknown as Violation['currentState'] }),
60
+ ];
61
+ const r = buildAzureDevOpsIssueUrl('https://dev.azure.com/org/proj', '::c', viols);
62
+ // Single ticket section since the rule + selector matched.
63
+ expect(r.description.match(/### `image-alt`/g)?.length).toBe(1);
64
+ // But both states aggregated.
65
+ expect(r.description).toContain(':default');
66
+ expect(r.description).toContain(':hover');
67
+ });
68
+ });
@@ -0,0 +1,89 @@
1
+ // rc.139 — G9 — Azure DevOps work-item pre-fill URL builder.
2
+ //
3
+ // Mirrors the GitHub + Jira filer pattern: take a base URL (Azure
4
+ // DevOps org+project), the audited component id, and the list of NEW
5
+ // violations; return a clickable URL pre-filled with a structured
6
+ // title + description so the user lands on a ready-to-save work item.
7
+ //
8
+ // Azure DevOps create-work-item URL pattern (Cloud + Server):
9
+ // https://dev.azure.com/{org}/{project}/_workitems/create/{type}
10
+ // ?[Title]=...&[Description]=...
11
+ //
12
+ // Description is markdown — Azure DevOps' work-item editor renders
13
+ // markdown in the description field as of 2023. Falls back to plain
14
+ // text for older Server installs.
15
+
16
+ import type { Violation } from '../types/audit';
17
+
18
+ export type AzureDevOpsIssueBuild = { url: string; title: string; description: string };
19
+
20
+ const URL_LIMIT = 7800;
21
+
22
+ export function buildAzureDevOpsIssueUrl(
23
+ /** Base URL — e.g. `https://dev.azure.com/myorg/myproject`. */
24
+ baseUrl: string,
25
+ componentId: string | null,
26
+ newViolations: Violation[],
27
+ /** Work-item type. Default 'Bug' fits accessibility regressions.
28
+ * Users with custom types ('Accessibility Defect', 'Issue', etc.)
29
+ * can pass the exact label. */
30
+ workItemType: string = 'Bug',
31
+ ): AzureDevOpsIssueBuild {
32
+ const cleaned = baseUrl.replace(/\/$/, '');
33
+
34
+ const groups: Array<Violation & { _states: string[] }> = [];
35
+ for (const v of newViolations) {
36
+ const key = `${v.ruleId}::${v.target.selector}`;
37
+ const stateLabel = `:${v.currentState.pseudoState} · ${v.currentState.theme} · ${v.currentState.direction}`;
38
+ const existing = groups.find((g) => `${g.ruleId}::${g.target.selector}` === key);
39
+ if (existing) {
40
+ if (!existing._states.includes(stateLabel)) existing._states.push(stateLabel);
41
+ continue;
42
+ }
43
+ groups.push({ ...v, _states: [stateLabel] });
44
+ }
45
+
46
+ const title = `a11y: ${groups.length} new violation${groups.length === 1 ? '' : 's'} in ${componentId ?? 'audited component'}`;
47
+
48
+ const lines: string[] = [];
49
+ lines.push(`**Component:** \`${componentId ?? 'unknown'}\``);
50
+ lines.push('');
51
+ lines.push(
52
+ 'Detected by WCAG Component Auditor as **new** vs the saved baseline — these are violations introduced since the last accepted baseline.',
53
+ );
54
+ lines.push('');
55
+ lines.push('---');
56
+ lines.push('');
57
+ for (const v of groups) {
58
+ lines.push(`### \`${v.ruleId}\` — ${v.impact}`);
59
+ lines.push('');
60
+ lines.push(v.description);
61
+ lines.push('');
62
+ lines.push(`- **WCAG:** ${v.wcagCriterion} (${v.wcagLevel})`);
63
+ lines.push(`- **Selector:** \`${v.target.selector}\``);
64
+ lines.push(`- **Found in state(s):** ${v._states.join(', ')}`);
65
+ if (v.helpUrl) lines.push(`- **More info:** ${v.helpUrl}`);
66
+ lines.push('');
67
+ lines.push('```html');
68
+ lines.push(v.target.outerHTML);
69
+ lines.push('```');
70
+ lines.push('');
71
+ }
72
+ lines.push('---');
73
+ lines.push('');
74
+ lines.push('_Filed via WCAG Component Auditor (delta-only — inherited debt not included)._');
75
+
76
+ const description = lines.join('\n');
77
+ // Azure DevOps work-item URL params:
78
+ // ?[Title]=... → title field
79
+ // ?[Description]=... → description (rendered as markdown)
80
+ // Square brackets are part of the param name (a reference syntax).
81
+ const params = new URLSearchParams({
82
+ '[Title]': title,
83
+ '[Description]': description,
84
+ });
85
+ let url = `${cleaned}/_workitems/create/${encodeURIComponent(workItemType)}?${params.toString()}`;
86
+ if (url.length > URL_LIMIT) url = url.slice(0, URL_LIMIT);
87
+
88
+ return { url, title, description };
89
+ }
@@ -0,0 +1,53 @@
1
+ // rc.139 — G9 — Unit tests for the GitLab issue URL builder.
2
+
3
+ import { describe, it, expect } from 'vitest';
4
+ import { buildGitLabIssueUrl } from './gitlab-issue';
5
+ import type { Violation } from '../types/audit';
6
+
7
+ function v(over: Partial<Violation> = {}): Violation {
8
+ return {
9
+ ruleId: 'image-alt', wcagCriterion: '1.1.1', wcagLevel: 'A', impact: 'serious',
10
+ description: 'Images must have alt', helpUrl: 'https://x',
11
+ target: { selector: 'img.hero', outerHTML: '<img>', failureSummary: '', tagName: 'IMG', role: null, accessibleName: null, textSnippet: null, attrId: null, attrTestid: null },
12
+ componentId: '::c', currentState: { pseudoState: 'default', theme: 'light', direction: 'ltr', viewport: 'desktop' } as unknown as Violation['currentState'],
13
+ axeVersion: '4.11.4', detectedAt: '2026-05-22T00:00:00Z', matchKey: 'mk',
14
+ ...over,
15
+ };
16
+ }
17
+
18
+ describe('buildGitLabIssueUrl (rc.139)', () => {
19
+ it('builds a URL targeting GitLab.com new-issue endpoint', () => {
20
+ const r = buildGitLabIssueUrl('https://gitlab.com/mygroup/myproject', '::c', [v()]);
21
+ expect(r.url.startsWith('https://gitlab.com/mygroup/myproject/-/issues/new?')).toBe(true);
22
+ });
23
+
24
+ it('handles self-hosted GitLab instances with the same pattern', () => {
25
+ const r = buildGitLabIssueUrl('https://gitlab.my-org.com/team/repo', '::c', [v()]);
26
+ expect(r.url.startsWith('https://gitlab.my-org.com/team/repo/-/issues/new?')).toBe(true);
27
+ });
28
+
29
+ it('embeds title + description as issue[*] params', () => {
30
+ const r = buildGitLabIssueUrl('https://gitlab.com/g/p', '::c', [v()]);
31
+ expect(r.url).toMatch(/issue%5Btitle%5D=/);
32
+ expect(r.url).toMatch(/issue%5Bdescription%5D=/);
33
+ expect(r.title).toContain('1 new violation');
34
+ expect(r.description).toContain('image-alt');
35
+ expect(r.description).toContain('1.1.1');
36
+ });
37
+
38
+ it('caps URL length at 7800', () => {
39
+ const many = Array.from({ length: 200 }, (_, i) =>
40
+ v({
41
+ ruleId: `rule-${i}`,
42
+ target: { ...v().target, selector: `.really.long.selector.number-${i}` },
43
+ }),
44
+ );
45
+ const r = buildGitLabIssueUrl('https://gitlab.com/g/p', '::c', many);
46
+ expect(r.url.length).toBeLessThanOrEqual(7800);
47
+ });
48
+
49
+ it('strips the trailing slash from the project URL', () => {
50
+ const r = buildGitLabIssueUrl('https://gitlab.com/g/p/', '::c', [v()]);
51
+ expect(r.url.startsWith('https://gitlab.com/g/p/-/issues/new?')).toBe(true);
52
+ });
53
+ });
@@ -0,0 +1,78 @@
1
+ // rc.139 — G9 — GitLab issue pre-fill URL builder.
2
+ //
3
+ // Mirrors the GitHub flow but targets GitLab's new-issue endpoint.
4
+ // GitLab's URL pattern is /-/issues/new (vs GitHub's /issues/new).
5
+ // Description is markdown; renders natively.
6
+ //
7
+ // https://gitlab.com/{group}/{project}/-/issues/new
8
+ // ?issue[title]=...&issue[description]=...
9
+
10
+ import type { Violation } from '../types/audit';
11
+
12
+ export type GitLabIssueBuild = { url: string; title: string; description: string };
13
+
14
+ const URL_LIMIT = 7800;
15
+
16
+ export function buildGitLabIssueUrl(
17
+ /** Project URL — e.g. `https://gitlab.com/group/project`.
18
+ * Self-hosted instances work the same: pass `https://gitlab.your-org.com/group/project`. */
19
+ projectUrl: string,
20
+ componentId: string | null,
21
+ newViolations: Violation[],
22
+ ): GitLabIssueBuild {
23
+ const cleaned = projectUrl.replace(/\/$/, '');
24
+
25
+ const groups: Array<Violation & { _states: string[] }> = [];
26
+ for (const v of newViolations) {
27
+ const key = `${v.ruleId}::${v.target.selector}`;
28
+ const stateLabel = `:${v.currentState.pseudoState} · ${v.currentState.theme} · ${v.currentState.direction}`;
29
+ const existing = groups.find((g) => `${g.ruleId}::${g.target.selector}` === key);
30
+ if (existing) {
31
+ if (!existing._states.includes(stateLabel)) existing._states.push(stateLabel);
32
+ continue;
33
+ }
34
+ groups.push({ ...v, _states: [stateLabel] });
35
+ }
36
+
37
+ const title = `a11y: ${groups.length} new violation${groups.length === 1 ? '' : 's'} in ${componentId ?? 'audited component'}`;
38
+
39
+ const lines: string[] = [];
40
+ lines.push(`**Component:** \`${componentId ?? 'unknown'}\``);
41
+ lines.push('');
42
+ lines.push(
43
+ 'Detected by WCAG Component Auditor as **new** vs the saved baseline — these are violations introduced since the last accepted baseline.',
44
+ );
45
+ lines.push('');
46
+ lines.push('---');
47
+ lines.push('');
48
+ for (const v of groups) {
49
+ lines.push(`### \`${v.ruleId}\` — ${v.impact}`);
50
+ lines.push('');
51
+ lines.push(v.description);
52
+ lines.push('');
53
+ lines.push(`- **WCAG:** ${v.wcagCriterion} (${v.wcagLevel})`);
54
+ lines.push(`- **Selector:** \`${v.target.selector}\``);
55
+ lines.push(`- **Found in state(s):** ${v._states.join(', ')}`);
56
+ if (v.helpUrl) lines.push(`- **More info:** ${v.helpUrl}`);
57
+ lines.push('');
58
+ lines.push('```html');
59
+ lines.push(v.target.outerHTML);
60
+ lines.push('```');
61
+ lines.push('');
62
+ }
63
+ lines.push('---');
64
+ lines.push('');
65
+ lines.push('_Filed via WCAG Component Auditor (delta-only — inherited debt not included)._');
66
+
67
+ const description = lines.join('\n');
68
+ // GitLab's new-issue endpoint uses bracketed param names that
69
+ // express a nested form payload.
70
+ const params = new URLSearchParams({
71
+ 'issue[title]': title,
72
+ 'issue[description]': description,
73
+ });
74
+ let url = `${cleaned}/-/issues/new?${params.toString()}`;
75
+ if (url.length > URL_LIMIT) url = url.slice(0, URL_LIMIT);
76
+
77
+ return { url, title, description };
78
+ }