@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.
- package/dist/assets/ErrorBoundary-C-kswn4E.js +594 -0
- package/dist/assets/ai-usage-log-BX3L6bKl.js +1 -0
- package/dist/assets/content-script.ts-FuMy_sE5.js +217 -0
- package/dist/assets/{content-script.ts-loader-Dfu1UEfD.js → content-script.ts-loader-CBHeu186.js} +1 -1
- package/dist/assets/copy-ai-fixer-prompt-DQYkHOv3.js +19 -0
- package/dist/assets/{crash-reporter-Dc5lvxvY.js → crash-reporter-Bu2p8K-p.js} +1 -1
- package/dist/assets/design-system-audit-DpxJrxnb.js +1 -0
- package/dist/assets/devtools-panel-DFQvqKKj.js +1 -0
- package/dist/assets/diff-DA41zYPc.js +1 -0
- package/dist/assets/dom-criterion-analyzers-DoUaJV5C.js +8 -0
- package/dist/assets/fraunces-latin-400-normal-6IfK1voy.woff2 +0 -0
- package/dist/assets/fraunces-latin-400-normal-NUPT2cO8.woff +0 -0
- package/dist/assets/fraunces-latin-500-normal-BTR4KCeb.woff +0 -0
- package/dist/assets/fraunces-latin-500-normal-DnGCNyPD.woff2 +0 -0
- package/dist/assets/fraunces-latin-600-normal-BFCDtZfi.woff2 +0 -0
- package/dist/assets/fraunces-latin-600-normal-DL5QCzvS.woff +0 -0
- package/dist/assets/fraunces-latin-ext-400-normal-D8gbi3Gu.woff2 +0 -0
- package/dist/assets/fraunces-latin-ext-400-normal-UihxqfOe.woff +0 -0
- package/dist/assets/fraunces-latin-ext-500-normal-BMcFk1Xs.woff +0 -0
- package/dist/assets/fraunces-latin-ext-500-normal-Z5DV8IzT.woff2 +0 -0
- package/dist/assets/fraunces-latin-ext-600-normal-B0Dy4lqi.woff +0 -0
- package/dist/assets/fraunces-latin-ext-600-normal-BtzmzP0X.woff2 +0 -0
- package/dist/assets/fraunces-vietnamese-400-normal-B65MOf9T.woff +0 -0
- package/dist/assets/fraunces-vietnamese-400-normal-CvGt0Ybw.woff2 +0 -0
- package/dist/assets/fraunces-vietnamese-500-normal-B-KbxExq.woff +0 -0
- package/dist/assets/fraunces-vietnamese-500-normal-GOH_-EGq.woff2 +0 -0
- package/dist/assets/fraunces-vietnamese-600-normal-BjlAJixd.woff2 +0 -0
- package/dist/assets/fraunces-vietnamese-600-normal-DlAl5EAR.woff +0 -0
- package/dist/assets/geist-sans-latin-400-normal-BOaIZNA2.woff +0 -0
- package/dist/assets/geist-sans-latin-400-normal-gapTbOY8.woff2 +0 -0
- package/dist/assets/geist-sans-latin-500-normal-CN2lyvyL.woff +0 -0
- package/dist/assets/geist-sans-latin-500-normal-uokXdC-Q.woff2 +0 -0
- package/dist/assets/geist-sans-latin-600-normal-CA1yjETN.woff +0 -0
- package/dist/assets/geist-sans-latin-600-normal-DFOURf8L.woff2 +0 -0
- package/dist/assets/geist-sans-latin-700-normal-BmN9tIp5.woff2 +0 -0
- package/dist/assets/geist-sans-latin-700-normal-CjScfYeH.woff +0 -0
- package/dist/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
- package/dist/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
- package/dist/assets/jetbrains-mono-cyrillic-500-normal-DJqRU3vO.woff +0 -0
- package/dist/assets/jetbrains-mono-cyrillic-500-normal-DmUKJPL_.woff2 +0 -0
- package/dist/assets/jetbrains-mono-cyrillic-600-normal-8K4wrrwR.woff +0 -0
- package/dist/assets/jetbrains-mono-cyrillic-600-normal-EVf6-Yzo.woff2 +0 -0
- package/dist/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
- package/dist/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
- package/dist/assets/jetbrains-mono-greek-500-normal-D7SFKleX.woff +0 -0
- package/dist/assets/jetbrains-mono-greek-500-normal-JpySY46c.woff2 +0 -0
- package/dist/assets/jetbrains-mono-greek-600-normal-H7WoG9Et.woff2 +0 -0
- package/dist/assets/jetbrains-mono-greek-600-normal-mc2nkWzM.woff +0 -0
- package/dist/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/dist/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/dist/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/dist/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/dist/assets/jetbrains-mono-latin-600-normal-BfsvjouI.woff +0 -0
- package/dist/assets/jetbrains-mono-latin-600-normal-C8RAYTDA.woff2 +0 -0
- package/dist/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
- package/dist/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
- package/dist/assets/jetbrains-mono-latin-ext-500-normal-Cut-4mMH.woff2 +0 -0
- package/dist/assets/jetbrains-mono-latin-ext-500-normal-ckzbgY84.woff +0 -0
- package/dist/assets/jetbrains-mono-latin-ext-600-normal-BfB_LPfz.woff2 +0 -0
- package/dist/assets/jetbrains-mono-latin-ext-600-normal-DObL3zCW.woff +0 -0
- package/dist/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
- package/dist/assets/jetbrains-mono-vietnamese-500-normal-DNRqzVM1.woff +0 -0
- package/dist/assets/jetbrains-mono-vietnamese-600-normal-OWROknRo.woff +0 -0
- package/dist/assets/options-BPhjrbGI.js +6 -0
- package/dist/assets/parallel-tab-flow-Xk9RSjay.js +1 -0
- package/dist/assets/scheduled-audit-runner-DyKpb3zg.js +2167 -0
- package/dist/assets/service-worker.ts-CMkltOzu.js +2 -0
- package/dist/assets/side-panel-Ctm2yXeo.css +1 -0
- package/dist/assets/side-panel-f_X4NOJt.js +4 -0
- package/dist/assets/site-report-renderer-DNgytqhZ.js +189 -0
- package/dist/assets/{styles-C4Kq0zOO.js → styles-Cn731SYD.js} +13 -13
- package/dist/assets/styles-d5msFsnl.css +1 -0
- package/dist/assets/zip-encoder-CtULHXx_.js +1 -0
- package/dist/axe.min.js +12 -0
- package/dist/devtools/panel.html +9 -8
- package/dist/manifest.json +11 -8
- package/dist/options/options.html +5 -6
- package/dist/service-worker-loader.js +1 -1
- package/dist/side-panel/App.tsx +129 -5
- package/dist/side-panel/audit-launcher.ts +21 -1
- package/dist/side-panel/azure-devops-issue.test.ts +68 -0
- package/dist/side-panel/azure-devops-issue.ts +89 -0
- package/dist/side-panel/gitlab-issue.test.ts +53 -0
- package/dist/side-panel/gitlab-issue.ts +78 -0
- package/dist/side-panel/main.tsx +39 -2
- package/dist/side-panel/side-panel.html +11 -8
- package/dist/side-panel/store.ts +149 -13
- package/dist/side-panel/styles.css +39 -0
- package/dist/side-panel/wire-messaging.ts +146 -9
- package/package.json +1 -1
- package/wcagcheckr-ci.mjs +193 -32
- package/dist/assets/ErrorBoundary-BLcMSVSr.js +0 -524
- package/dist/assets/ai-usage-log-Dj9Ub_DT.js +0 -1
- package/dist/assets/content-script.ts-CwcUMq3e.js +0 -181
- package/dist/assets/devtools-panel-DQ3Bbomf.js +0 -1
- package/dist/assets/diff-D4sCAdXf.js +0 -1
- package/dist/assets/forensic-log-B1UCXZ23.js +0 -129
- package/dist/assets/options-BG2i5vFf.js +0 -6
- package/dist/assets/preload-helper-D7HrI6pR.js +0 -1
- package/dist/assets/service-worker.ts-CO86CV_p.js +0 -715
- package/dist/assets/side-panel-XSB07vDa.js +0 -1
- package/dist/assets/site-report-renderer-CyHkM6hB.js +0 -147
- package/dist/assets/state-PELIq3oj.js +0 -1
- package/dist/assets/styles-Cevp58mS.css +0 -1
package/dist/devtools/panel.html
CHANGED
|
@@ -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-
|
|
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-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/styles-
|
|
12
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
13
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
14
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
15
|
-
<link rel="
|
|
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>
|
package/dist/manifest.json
CHANGED
|
@@ -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.
|
|
6
|
-
"version_name": "1.0.0-rc.
|
|
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-
|
|
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
|
-
"
|
|
62
|
-
"assets/
|
|
63
|
-
"assets/
|
|
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/
|
|
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-
|
|
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-
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/
|
|
12
|
-
<link rel="modulepreload" crossorigin href="/assets/styles-
|
|
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-
|
|
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-
|
|
1
|
+
import './assets/service-worker.ts-CMkltOzu.js';
|
package/dist/side-panel/App.tsx
CHANGED
|
@@ -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
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
+
}
|