@wcag-checkr/ci 1.0.0-rc.366 → 1.0.0-rc.367
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-BH1wbHb3.js → ErrorBoundary-Ck0XMUiQ.js} +22 -22
- package/dist/assets/{copy-ai-fixer-prompt-FO0N3IMf.js → copy-ai-fixer-prompt-CKp52Td1.js} +2 -2
- package/dist/assets/{devtools-panel-BAHfgRsc.js → devtools-panel-Dw0aFhIc.js} +1 -1
- package/dist/assets/{parallel-tab-flow-C4bjrDqL.js → parallel-tab-flow-BuZZIdRb.js} +1 -1
- package/dist/assets/{scheduled-audit-runner-XCDyFhY6.js → scheduled-audit-runner-9R-aGzwO.js} +137 -137
- package/dist/assets/{service-worker.ts-57FwiS_-.js → service-worker.ts-CyL2aQvI.js} +2 -2
- package/dist/assets/{side-panel-Dl247dJv.js → side-panel-CuEUwRBD.js} +3 -3
- package/dist/assets/{site-report-renderer-CNWZjbOd.js → site-report-renderer-46_JyAkB.js} +1 -1
- package/dist/devtools/panel.html +3 -3
- package/dist/manifest.json +3 -4
- package/dist/service-worker-loader.js +1 -1
- package/dist/side-panel/side-panel.html +4 -4
- package/package.json +1 -1
- package/dist/side-panel/App.tsx +0 -308
- package/dist/side-panel/README.md +0 -57
- package/dist/side-panel/audit-launcher.test.ts +0 -56
- package/dist/side-panel/audit-launcher.ts +0 -98
- package/dist/side-panel/azure-devops-issue.test.ts +0 -68
- package/dist/side-panel/azure-devops-issue.ts +0 -89
- package/dist/side-panel/format-component-id.test.ts +0 -89
- package/dist/side-panel/format-component-id.ts +0 -40
- package/dist/side-panel/github-issue.test.ts +0 -102
- package/dist/side-panel/github-issue.ts +0 -66
- package/dist/side-panel/gitlab-issue.test.ts +0 -53
- package/dist/side-panel/gitlab-issue.ts +0 -78
- package/dist/side-panel/jira-issue.ts +0 -64
- package/dist/side-panel/main.tsx +0 -86
- package/dist/side-panel/store.ts +0 -435
- package/dist/side-panel/styles.css +0 -55
- package/dist/side-panel/use-audit-view-model.ts +0 -116
- package/dist/side-panel/wire-messaging.test.ts +0 -234
- package/dist/side-panel/wire-messaging.ts +0 -521
package/dist/side-panel/store.ts
DELETED
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
// Zustand store for side panel state.
|
|
2
|
-
// Spec: phase-1-specs.md §9.
|
|
3
|
-
|
|
4
|
-
import { create } from 'zustand';
|
|
5
|
-
import type { AuditResult, DeltaResult } from '../types/audit';
|
|
6
|
-
import type { StateConfig } from '../types/state';
|
|
7
|
-
import type { LicenseTier } from '../shared/messages';
|
|
8
|
-
import type { InAppMessage } from '../modules/messages-client';
|
|
9
|
-
import type { SiteCrawlReport } from '../shared/site-aggregator';
|
|
10
|
-
import { saveSiteCrawlReport } from '../shared/site-crawl-storage';
|
|
11
|
-
import type { IncompleteResolution } from '../shared/wcag-verdicts';
|
|
12
|
-
|
|
13
|
-
export type AuditStatus = 'idle' | 'running' | 'complete' | 'failed' | 'interrupted';
|
|
14
|
-
export type View = 'matrix' | 'report' | 'delta' | 'activity' | 'guided' | 'flows' | 'scorecard' | 'crawl' | 'forensic' | 'compliance' | 'schedules' | 'risk' | 'copilot' | 'ax-tree' | 'wcag3' | 'tokens';
|
|
15
|
-
// rc.159 — Findings sub-nav. The "Findings" (report) view is now a hub
|
|
16
|
-
// containing six lenses on the same audit data. The user picks which
|
|
17
|
-
// lens; the slug stays 'report' so existing tests/probes keep working.
|
|
18
|
-
export type FindingsLens = 'overview' | 'per-area' | 'violations' | 'activity' | 'delta' | 'wcag3';
|
|
19
|
-
/** Three personas: site owners (mom-and-pop, no code), power users
|
|
20
|
-
* (streamlined Hero + FindingsStream — fewer surfaces, same data
|
|
21
|
-
* layer as dev), and developers (full feature surface — matrix,
|
|
22
|
-
* all exports, all specialized views).
|
|
23
|
-
*
|
|
24
|
-
* rc.333 — 'power' added as a first-class persona. It maps to what
|
|
25
|
-
* used to be the opt-in v2 UI; the legacy `v2UiEnabled` storage
|
|
26
|
-
* key is now derived from userMode for backward compatibility and
|
|
27
|
-
* no longer the source of truth. */
|
|
28
|
-
export type UserMode = 'owner' | 'power' | 'dev';
|
|
29
|
-
|
|
30
|
-
type Store = {
|
|
31
|
-
// audit
|
|
32
|
-
status: AuditStatus;
|
|
33
|
-
progress: { current: number; total: number; currentState?: StateConfig } | null;
|
|
34
|
-
results: AuditResult[];
|
|
35
|
-
delta: DeltaResult | null;
|
|
36
|
-
componentId: string | null;
|
|
37
|
-
errorMessage: string | null;
|
|
38
|
-
/** True only when results came from a scan that completed during this side-panel
|
|
39
|
-
* session. False when results were rehydrated from `loadLastAudit` — in that case
|
|
40
|
-
* the score UI should mark the grade as "stale, re-scan to refresh" rather than
|
|
41
|
-
* imply it reflects the page's current state. */
|
|
42
|
-
freshThisSession: boolean;
|
|
43
|
-
|
|
44
|
-
// baselines
|
|
45
|
-
baselineList: Array<{
|
|
46
|
-
componentId: string;
|
|
47
|
-
violationCount: number;
|
|
48
|
-
lastUpdated: string;
|
|
49
|
-
seenOnUrlsCount?: number;
|
|
50
|
-
metrics?: import('../shared/messages').BaselineSummaryMetrics;
|
|
51
|
-
}>;
|
|
52
|
-
|
|
53
|
-
// license
|
|
54
|
-
tier: LicenseTier;
|
|
55
|
-
/** Trial-tier countdown. null = unknown / not on trial. */
|
|
56
|
-
trialDaysRemaining: number | null;
|
|
57
|
-
/** Team-tier seat consumption. null = server hasn't emitted seat info. */
|
|
58
|
-
seatsUsed: number | null;
|
|
59
|
-
seatsTotal: number | null;
|
|
60
|
-
/** Server's actual plan code for the active license — `solo`, `solo-yearly`,
|
|
61
|
-
* `solo-single-month`, `team`, `team-15`. Lets the UpgradeCard tell apart
|
|
62
|
-
* the recurring SKUs from the one-time single-month pass even though they
|
|
63
|
-
* all map to tier='solo' or tier='team' for capability gating. */
|
|
64
|
-
planCode: string | null;
|
|
65
|
-
/** Days until the active one-time license (e.g. solo-single-month) expires.
|
|
66
|
-
* null for recurring subscriptions. */
|
|
67
|
-
licenseDaysRemaining: number | null;
|
|
68
|
-
/** True when Stripe reported the most recent invoice payment as failed.
|
|
69
|
-
* License stays valid during the grace period but UI surfaces the
|
|
70
|
-
* failure prominently so the user updates their card before access cuts. */
|
|
71
|
-
pastDue: boolean;
|
|
72
|
-
|
|
73
|
-
// in-app messages
|
|
74
|
-
messages: InAppMessage[];
|
|
75
|
-
unreadMessageCount: number;
|
|
76
|
-
criticalUnacked: boolean;
|
|
77
|
-
|
|
78
|
-
// ui
|
|
79
|
-
view: View;
|
|
80
|
-
/** rc.159 — Which lens to show inside the Findings view. Defaults to
|
|
81
|
-
* 'overview' on every audit completion. */
|
|
82
|
-
findingsLens: FindingsLens;
|
|
83
|
-
/** Persona-driven UI mode. `null` = first-launch (wizard not yet answered). */
|
|
84
|
-
userMode: UserMode | null;
|
|
85
|
-
|
|
86
|
-
// site crawl
|
|
87
|
-
siteCrawlStatus: 'idle' | 'running' | 'complete' | 'failed';
|
|
88
|
-
siteCrawlProgress: { current: number; total: number; url: string; lastViolations?: number } | null;
|
|
89
|
-
siteCrawlReport: SiteCrawlReport | null;
|
|
90
|
-
siteCrawlError: string | null;
|
|
91
|
-
|
|
92
|
-
// rc.344 — interactive crawl discovery/review phase (precedes the audit
|
|
93
|
-
// crawl when "Review pages before auditing" is on). 'discovering' = the
|
|
94
|
-
// SW is walking the site to build the candidate list; 'reviewing' = the
|
|
95
|
-
// list is back and the user is pruning it before auditing.
|
|
96
|
-
crawlDiscoveryStatus: 'idle' | 'discovering' | 'reviewing';
|
|
97
|
-
crawlDiscoveryUrls: string[];
|
|
98
|
-
crawlDiscoveryFound: number;
|
|
99
|
-
crawlDiscoveryLastUrl: string | null;
|
|
100
|
-
|
|
101
|
-
// highlight pin: which violation row is currently showing its overlay on the page,
|
|
102
|
-
// and whether its selector resolved (false → render "not in current state" hint).
|
|
103
|
-
pinnedMatchKey: string | null;
|
|
104
|
-
pinnedFound: boolean;
|
|
105
|
-
|
|
106
|
-
/** rc.222 — Wall-clock audit timing. `startNewScan` writes Date.now()
|
|
107
|
-
* when the user kicks off an audit; `setResults` computes the delta
|
|
108
|
-
* on completion. Pre-rc.222 the GradeCard added per-state durations
|
|
109
|
-
* but missed the AI walkthroughs + pixel-contrast sampler that run
|
|
110
|
-
* AFTER the matrix — making the displayed "21.4s" significantly less
|
|
111
|
-
* than the user's real wait. Now we capture true elapsed time. */
|
|
112
|
-
auditStartedAtMs: number | null;
|
|
113
|
-
lastAuditWallClockMs: number | null;
|
|
114
|
-
|
|
115
|
-
// AI augmentation failure surface — shown as a dismissible banner above the
|
|
116
|
-
// results area when an audit's AI portion errored despite a configured key.
|
|
117
|
-
// Cleared whenever a new audit starts or completes cleanly.
|
|
118
|
-
aiFailure: {
|
|
119
|
-
severity: 'total' | 'partial';
|
|
120
|
-
reason: string;
|
|
121
|
-
checksAttempted: number;
|
|
122
|
-
checksSucceeded: number;
|
|
123
|
-
checksErrored: number;
|
|
124
|
-
errorDetails: string[];
|
|
125
|
-
} | null;
|
|
126
|
-
|
|
127
|
-
// AI augmentation in-flight progress. Drives the post-matrix progress line:
|
|
128
|
-
// matrix loop owns 1..N states, then AI augmentation takes over and the bar
|
|
129
|
-
// shows e.g. "AI: 3/8 — Verifying alt text…". Cleared when AUDIT_COMPLETE
|
|
130
|
-
// fires or a new scan starts.
|
|
131
|
-
aiProgress: {
|
|
132
|
-
currentCheckLabel: string;
|
|
133
|
-
current: number;
|
|
134
|
-
total: number;
|
|
135
|
-
/** Optional sub-progress within the current check: which candidate is
|
|
136
|
-
* being judged (e.g. image 3 of 10). When present the UI shows
|
|
137
|
-
* "AI: 7/9 — Reviewing link clarity… (3/10)" so the user can see
|
|
138
|
-
* movement inside a single check that judges many candidates. */
|
|
139
|
-
candidatesDone?: number;
|
|
140
|
-
candidatesTotal?: number;
|
|
141
|
-
} | null;
|
|
142
|
-
|
|
143
|
-
// actions
|
|
144
|
-
setStatus: (s: AuditStatus) => void;
|
|
145
|
-
/** Begin a new scan: flips status to 'running' AND clears prior results,
|
|
146
|
-
* delta, error, and progress so the panel doesn't show stale data from the
|
|
147
|
-
* previous site under the new "Scanning…" indicator. Use this from every
|
|
148
|
-
* START_AUDIT dispatch site instead of setStatus('running') alone. */
|
|
149
|
-
startNewScan: () => void;
|
|
150
|
-
setProgress: (p: Store['progress']) => void;
|
|
151
|
-
setResults: (results: AuditResult[], delta: DeltaResult | null, componentId: string) => void;
|
|
152
|
-
setDelta: (delta: DeltaResult | null) => void;
|
|
153
|
-
setError: (message: string) => void;
|
|
154
|
-
setBaselineList: (list: Store['baselineList']) => void;
|
|
155
|
-
/** Wipe the current audit (results, delta, componentId, error, progress).
|
|
156
|
-
* Used by the user-triggered Clear button and by auto-clear when the user
|
|
157
|
-
* navigates the audit target to a different URL. Baselines + forensic
|
|
158
|
-
* history are NOT touched — those are persistent records. */
|
|
159
|
-
clearResults: () => void;
|
|
160
|
-
setTier: (
|
|
161
|
-
tier: LicenseTier,
|
|
162
|
-
extras?: {
|
|
163
|
-
trialDaysRemaining?: number;
|
|
164
|
-
seatsUsed?: number;
|
|
165
|
-
seatsTotal?: number;
|
|
166
|
-
planCode?: string | null;
|
|
167
|
-
licenseDaysRemaining?: number;
|
|
168
|
-
pastDue?: boolean;
|
|
169
|
-
}
|
|
170
|
-
) => void;
|
|
171
|
-
setView: (v: View) => void;
|
|
172
|
-
setFindingsLens: (lens: FindingsLens) => void;
|
|
173
|
-
/** rc.205 — When the user clicks a Verification Area tile (or a
|
|
174
|
-
* criterion ID on the WCAG AA Status banner) we want the Per-area
|
|
175
|
-
* lens to render with THAT area's card already expanded. PerAreaCards
|
|
176
|
-
* reads this on mount, expands the matching area, then clears the
|
|
177
|
-
* request via `clearPendingAreaExpand`. Null when no jump is pending. */
|
|
178
|
-
pendingAreaExpand: string | null;
|
|
179
|
-
setPendingAreaExpand: (id: string | null) => void;
|
|
180
|
-
setUserMode: (m: UserMode | null) => void;
|
|
181
|
-
setSiteCrawlStatus: (s: Store['siteCrawlStatus']) => void;
|
|
182
|
-
setSiteCrawlProgress: (p: Store['siteCrawlProgress']) => void;
|
|
183
|
-
setSiteCrawlReport: (r: SiteCrawlReport | null) => void;
|
|
184
|
-
setSiteCrawlError: (e: string | null) => void;
|
|
185
|
-
setCrawlDiscoveryStatus: (s: Store['crawlDiscoveryStatus']) => void;
|
|
186
|
-
setCrawlDiscoveryProgress: (found: number, url: string) => void;
|
|
187
|
-
setCrawlDiscoveryUrls: (urls: string[]) => void;
|
|
188
|
-
resetCrawlDiscovery: () => void;
|
|
189
|
-
/** rc.234 — When the Findings tab is showing a slice of crawl data
|
|
190
|
-
* (either the site-wide aggregate or a specific page from the
|
|
191
|
-
* crawl), this carries the navigation context so the user can
|
|
192
|
-
* flip between them without losing the crawl. Null means "no
|
|
193
|
-
* active crawl context" (regular single-page audit).
|
|
194
|
-
* `mode: 'site-aggregate'` — Findings shows merged crawl data
|
|
195
|
-
* `mode: 'page', url: 'X'` — Findings shows one crawled page
|
|
196
|
-
* The ScopeBanner uses this to surface a page selector +
|
|
197
|
-
* "back to site-wide" affordance. The "Pages, worst first" list
|
|
198
|
-
* on the Crawl panel and the "← Back to site-wide" button on
|
|
199
|
-
* Findings both flip this state. */
|
|
200
|
-
crawlNavContext: { mode: 'site-aggregate' } | { mode: 'page'; url: string } | null;
|
|
201
|
-
setCrawlNavContext: (ctx: Store['crawlNavContext']) => void;
|
|
202
|
-
/** rc.235 — Synchronous cache of incomplete-resolution AI verdicts
|
|
203
|
-
* keyed by pageUrl. Pre-rc.235 the `useCurrentlyRelevantResolutions`
|
|
204
|
-
* hook always started with `[]` and async-loaded from chrome.storage,
|
|
205
|
-
* producing a TWO-RENDER FLICKER: first render counts violations
|
|
206
|
-
* WITHOUT AI fails, second render after async load includes them.
|
|
207
|
-
* Most visible when drilling into a crawled page (cached AI verdicts
|
|
208
|
-
* exist immediately), where the header would show "Fix 1 element"
|
|
209
|
-
* then jump to "Fix 5 elements" within milliseconds — Cliff: "things
|
|
210
|
-
* already do not make sense."
|
|
211
|
-
*
|
|
212
|
-
* The cache is in-memory only (no chrome.storage write). Callers
|
|
213
|
-
* pre-warm via `setResolutionsForUrl(url, resolutions)` BEFORE
|
|
214
|
-
* calling `setResults` so the hook's lazy initial value reads
|
|
215
|
-
* from cache instead of `[]`. The hook still runs its own async
|
|
216
|
-
* refresh to confirm + pick up any updates. */
|
|
217
|
-
resolutionsByUrl: Record<string, IncompleteResolution[]>;
|
|
218
|
-
setResolutionsForUrl: (url: string, resolutions: IncompleteResolution[]) => void;
|
|
219
|
-
setPinned: (matchKey: string | null, found?: boolean) => void;
|
|
220
|
-
setAiFailure: (failure: Store['aiFailure']) => void;
|
|
221
|
-
clearAiFailure: () => void;
|
|
222
|
-
setAiProgress: (progress: Store['aiProgress']) => void;
|
|
223
|
-
/** rc.37 — matchKeys the user has acknowledged as "not an issue." Loaded
|
|
224
|
-
* from chrome.storage at mount. Every ViolationCard reads this directly
|
|
225
|
-
* so the "Acknowledged" pill appears immediately on click, in every
|
|
226
|
-
* view (Matrix, Delta, WCAG-em, Owner — anywhere a card renders),
|
|
227
|
-
* before the SW round-trip to refresh the delta. */
|
|
228
|
-
acknowledgedKeys: Set<string>;
|
|
229
|
-
setAcknowledgedKeys: (keys: Set<string>) => void;
|
|
230
|
-
markAcknowledged: (matchKey: string) => void;
|
|
231
|
-
markUnacknowledged: (matchKey: string) => void;
|
|
232
|
-
/** rc.200 — Dismissal keys for axe violations (and AI-resolved fails).
|
|
233
|
-
* Distinct from `acknowledgedKeys`:
|
|
234
|
-
* - Acknowledged = "I verified the content; the rule was right
|
|
235
|
-
* that this looked worth reviewing, but it's actually fine."
|
|
236
|
-
* matchKey-based; survives re-audits as long as content matches.
|
|
237
|
-
* - Dismissed = "The rule itself is wrong about this page."
|
|
238
|
-
* URL-scoped; filtered from future audits + the AI-fix prompt.
|
|
239
|
-
* Loaded from `shared/dismissals` (chrome.storage.local) on audit-
|
|
240
|
-
* page change. ViolationCard renders the corresponding pill +
|
|
241
|
-
* un-dismiss affordance based on this set. */
|
|
242
|
-
dismissedKeys: Set<string>;
|
|
243
|
-
setDismissedKeys: (keys: Set<string>) => void;
|
|
244
|
-
markDismissed: (dismissalKey: string) => void;
|
|
245
|
-
markUndismissed: (dismissalKey: string) => void;
|
|
246
|
-
/** Replace the in-app messages list + counters. Called by wire-messaging
|
|
247
|
-
* after a refresh from the server. */
|
|
248
|
-
setMessages: (messages: InAppMessage[], unreadCount: number, criticalUnacked: boolean) => void;
|
|
249
|
-
/** Mark a message as locally seen/dismissed/acknowledged so the UI updates
|
|
250
|
-
* immediately without waiting for the next server refresh. */
|
|
251
|
-
applyMessageAck: (messageId: number, action: 'seen' | 'dismissed' | 'acknowledged' | 'clicked') => void;
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
export const useStore = create<Store>((set) => ({
|
|
255
|
-
status: 'idle',
|
|
256
|
-
progress: null,
|
|
257
|
-
results: [],
|
|
258
|
-
delta: null,
|
|
259
|
-
componentId: null,
|
|
260
|
-
errorMessage: null,
|
|
261
|
-
freshThisSession: false,
|
|
262
|
-
baselineList: [],
|
|
263
|
-
tier: 'trial',
|
|
264
|
-
trialDaysRemaining: null,
|
|
265
|
-
seatsUsed: null,
|
|
266
|
-
seatsTotal: null,
|
|
267
|
-
planCode: null,
|
|
268
|
-
licenseDaysRemaining: null,
|
|
269
|
-
pastDue: false,
|
|
270
|
-
messages: [],
|
|
271
|
-
unreadMessageCount: 0,
|
|
272
|
-
criticalUnacked: false,
|
|
273
|
-
view: 'matrix',
|
|
274
|
-
findingsLens: 'overview',
|
|
275
|
-
userMode: null,
|
|
276
|
-
siteCrawlStatus: 'idle',
|
|
277
|
-
siteCrawlProgress: null,
|
|
278
|
-
siteCrawlReport: null,
|
|
279
|
-
siteCrawlError: null,
|
|
280
|
-
crawlDiscoveryStatus: 'idle',
|
|
281
|
-
crawlDiscoveryUrls: [],
|
|
282
|
-
crawlDiscoveryFound: 0,
|
|
283
|
-
crawlDiscoveryLastUrl: null,
|
|
284
|
-
crawlNavContext: null,
|
|
285
|
-
resolutionsByUrl: {},
|
|
286
|
-
pinnedMatchKey: null,
|
|
287
|
-
pinnedFound: true,
|
|
288
|
-
aiFailure: null,
|
|
289
|
-
aiProgress: null,
|
|
290
|
-
acknowledgedKeys: new Set<string>(),
|
|
291
|
-
pendingAreaExpand: null,
|
|
292
|
-
auditStartedAtMs: null,
|
|
293
|
-
lastAuditWallClockMs: null,
|
|
294
|
-
|
|
295
|
-
setStatus: (status) => set({ status }),
|
|
296
|
-
startNewScan: () =>
|
|
297
|
-
set({
|
|
298
|
-
status: 'running',
|
|
299
|
-
results: [],
|
|
300
|
-
delta: null,
|
|
301
|
-
errorMessage: null,
|
|
302
|
-
progress: null,
|
|
303
|
-
componentId: null,
|
|
304
|
-
pinnedMatchKey: null,
|
|
305
|
-
pinnedFound: true,
|
|
306
|
-
aiFailure: null,
|
|
307
|
-
aiProgress: null,
|
|
308
|
-
auditStartedAtMs: Date.now(),
|
|
309
|
-
// rc.234 — Single-page re-audit clears any crawl drill-in
|
|
310
|
-
// context so the user doesn't see a stale "Back to site-wide"
|
|
311
|
-
// affordance pointing at a no-longer-applicable crawl.
|
|
312
|
-
crawlNavContext: null,
|
|
313
|
-
// freshThisSession stays false until setResults fires.
|
|
314
|
-
}),
|
|
315
|
-
setProgress: (progress) => set({ progress }),
|
|
316
|
-
setResults: (results, delta, componentId) =>
|
|
317
|
-
set((state) => {
|
|
318
|
-
const wallClockMs = state.auditStartedAtMs
|
|
319
|
-
? Date.now() - state.auditStartedAtMs
|
|
320
|
-
: null;
|
|
321
|
-
return {
|
|
322
|
-
results,
|
|
323
|
-
delta,
|
|
324
|
-
componentId,
|
|
325
|
-
status: 'complete',
|
|
326
|
-
errorMessage: null,
|
|
327
|
-
freshThisSession: true,
|
|
328
|
-
aiProgress: null,
|
|
329
|
-
pinnedMatchKey: null,
|
|
330
|
-
pinnedFound: true,
|
|
331
|
-
lastAuditWallClockMs: wallClockMs,
|
|
332
|
-
auditStartedAtMs: null,
|
|
333
|
-
};
|
|
334
|
-
}),
|
|
335
|
-
setDelta: (delta) => set({ delta }),
|
|
336
|
-
setError: (errorMessage) => set({ errorMessage, status: 'failed' }),
|
|
337
|
-
setBaselineList: (baselineList) => set({ baselineList }),
|
|
338
|
-
clearResults: () =>
|
|
339
|
-
set({
|
|
340
|
-
results: [],
|
|
341
|
-
delta: null,
|
|
342
|
-
componentId: null,
|
|
343
|
-
errorMessage: null,
|
|
344
|
-
progress: null,
|
|
345
|
-
status: 'idle',
|
|
346
|
-
freshThisSession: false,
|
|
347
|
-
pinnedMatchKey: null,
|
|
348
|
-
pinnedFound: true,
|
|
349
|
-
aiFailure: null,
|
|
350
|
-
aiProgress: null,
|
|
351
|
-
}),
|
|
352
|
-
setTier: (tier, extras) =>
|
|
353
|
-
set({
|
|
354
|
-
tier,
|
|
355
|
-
trialDaysRemaining: extras?.trialDaysRemaining ?? null,
|
|
356
|
-
seatsUsed: extras?.seatsUsed ?? null,
|
|
357
|
-
seatsTotal: extras?.seatsTotal ?? null,
|
|
358
|
-
planCode: extras?.planCode ?? null,
|
|
359
|
-
licenseDaysRemaining: extras?.licenseDaysRemaining ?? null,
|
|
360
|
-
pastDue: extras?.pastDue ?? false,
|
|
361
|
-
}),
|
|
362
|
-
setView: (view) => set({ view }),
|
|
363
|
-
setFindingsLens: (findingsLens) => set({ findingsLens }),
|
|
364
|
-
setPendingAreaExpand: (pendingAreaExpand) => set({ pendingAreaExpand }),
|
|
365
|
-
setUserMode: (userMode) => set({ userMode }),
|
|
366
|
-
setSiteCrawlStatus: (siteCrawlStatus) => set({ siteCrawlStatus }),
|
|
367
|
-
setCrawlNavContext: (crawlNavContext) => set({ crawlNavContext }),
|
|
368
|
-
setResolutionsForUrl: (url, resolutions) =>
|
|
369
|
-
set((state) => ({
|
|
370
|
-
resolutionsByUrl: { ...state.resolutionsByUrl, [url]: resolutions },
|
|
371
|
-
})),
|
|
372
|
-
setSiteCrawlProgress: (siteCrawlProgress) => set({ siteCrawlProgress }),
|
|
373
|
-
setSiteCrawlReport: (siteCrawlReport) => {
|
|
374
|
-
set({ siteCrawlReport });
|
|
375
|
-
// rc.111 — persist so the AI fixer prompt's site-wide context section
|
|
376
|
-
// survives panel close / SW restart. Fire-and-forget; failure to write
|
|
377
|
-
// chrome.storage is non-fatal (the in-memory state still works for the
|
|
378
|
-
// current session).
|
|
379
|
-
void saveSiteCrawlReport(siteCrawlReport);
|
|
380
|
-
},
|
|
381
|
-
setSiteCrawlError: (siteCrawlError) => set({ siteCrawlError }),
|
|
382
|
-
setCrawlDiscoveryStatus: (crawlDiscoveryStatus) => set({ crawlDiscoveryStatus }),
|
|
383
|
-
setCrawlDiscoveryProgress: (found, url) =>
|
|
384
|
-
set({ crawlDiscoveryFound: found, crawlDiscoveryLastUrl: url }),
|
|
385
|
-
setCrawlDiscoveryUrls: (crawlDiscoveryUrls) => set({ crawlDiscoveryUrls }),
|
|
386
|
-
resetCrawlDiscovery: () =>
|
|
387
|
-
set({
|
|
388
|
-
crawlDiscoveryStatus: 'idle',
|
|
389
|
-
crawlDiscoveryUrls: [],
|
|
390
|
-
crawlDiscoveryFound: 0,
|
|
391
|
-
crawlDiscoveryLastUrl: null,
|
|
392
|
-
}),
|
|
393
|
-
setPinned: (matchKey, found = true) => set({ pinnedMatchKey: matchKey, pinnedFound: found }),
|
|
394
|
-
setAiFailure: (aiFailure) => set({ aiFailure }),
|
|
395
|
-
clearAiFailure: () => set({ aiFailure: null }),
|
|
396
|
-
setAiProgress: (aiProgress) => set({ aiProgress }),
|
|
397
|
-
setAcknowledgedKeys: (acknowledgedKeys) => set({ acknowledgedKeys }),
|
|
398
|
-
markAcknowledged: (matchKey) =>
|
|
399
|
-
set((s) => ({ acknowledgedKeys: new Set(s.acknowledgedKeys).add(matchKey) })),
|
|
400
|
-
markUnacknowledged: (matchKey) =>
|
|
401
|
-
set((s) => {
|
|
402
|
-
const next = new Set(s.acknowledgedKeys);
|
|
403
|
-
next.delete(matchKey);
|
|
404
|
-
return { acknowledgedKeys: next };
|
|
405
|
-
}),
|
|
406
|
-
dismissedKeys: new Set<string>(),
|
|
407
|
-
setDismissedKeys: (dismissedKeys) => set({ dismissedKeys }),
|
|
408
|
-
markDismissed: (dismissalKey) =>
|
|
409
|
-
set((s) => ({ dismissedKeys: new Set(s.dismissedKeys).add(dismissalKey) })),
|
|
410
|
-
markUndismissed: (dismissalKey) =>
|
|
411
|
-
set((s) => {
|
|
412
|
-
const next = new Set(s.dismissedKeys);
|
|
413
|
-
next.delete(dismissalKey);
|
|
414
|
-
return { dismissedKeys: next };
|
|
415
|
-
}),
|
|
416
|
-
setMessages: (messages, unreadMessageCount, criticalUnacked) =>
|
|
417
|
-
set({ messages, unreadMessageCount, criticalUnacked }),
|
|
418
|
-
applyMessageAck: (messageId, action) =>
|
|
419
|
-
set((s) => {
|
|
420
|
-
const next = s.messages
|
|
421
|
-
.map((m) => {
|
|
422
|
-
if (m.id !== messageId) return m;
|
|
423
|
-
if (action === 'seen') return { ...m, seen: true };
|
|
424
|
-
if (action === 'dismissed') return { ...m, dismissed: true };
|
|
425
|
-
if (action === 'acknowledged') return { ...m, acknowledged: true };
|
|
426
|
-
if (action === 'clicked') return { ...m, clicked: true };
|
|
427
|
-
return m;
|
|
428
|
-
})
|
|
429
|
-
// Drop fully-handled non-critical messages (mirrors server visibility).
|
|
430
|
-
.filter((m) => (m.severity === 'critical' ? !m.acknowledged : !m.dismissed));
|
|
431
|
-
const unread = next.filter((m) => !m.seen).length;
|
|
432
|
-
const critUnacked = next.some((m) => m.severity === 'critical' && !m.acknowledged);
|
|
433
|
-
return { messages: next, unreadMessageCount: unread, criticalUnacked: critUnacked };
|
|
434
|
-
}),
|
|
435
|
-
}));
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/* rc.159 — Forensic-clarity type system. IBM Plex Sans for body + display
|
|
2
|
-
(humanist sans with strong character for a compliance-tool aesthetic);
|
|
3
|
-
IBM Plex Mono for selectors, matchKeys, JSON output, axe rule IDs.
|
|
4
|
-
Bundled via Google Fonts; Chrome caches woff2 across pages so subsequent
|
|
5
|
-
side-panel opens are offline-friendly. */
|
|
6
|
-
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap');
|
|
7
|
-
|
|
8
|
-
@tailwind base;
|
|
9
|
-
@tailwind components;
|
|
10
|
-
@tailwind utilities;
|
|
11
|
-
|
|
12
|
-
html, body, #root {
|
|
13
|
-
height: 100%;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
html {
|
|
17
|
-
/* Default to the Plex stack; mono utilities switch to the mono stack. */
|
|
18
|
-
font-family:
|
|
19
|
-
'IBM Plex Sans',
|
|
20
|
-
-apple-system,
|
|
21
|
-
BlinkMacSystemFont,
|
|
22
|
-
'Segoe UI Variable Text',
|
|
23
|
-
'Segoe UI',
|
|
24
|
-
system-ui,
|
|
25
|
-
sans-serif;
|
|
26
|
-
/* Slightly tighter feature defaults to match the compliance-doc feel:
|
|
27
|
-
stylistic alternates + tabular numerals so columns of counts and
|
|
28
|
-
percentages align visually inside tables and per-area cards. */
|
|
29
|
-
font-feature-settings: 'tnum' 1, 'ss01' 1, 'cv11' 1;
|
|
30
|
-
-webkit-font-smoothing: antialiased;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
code,
|
|
34
|
-
kbd,
|
|
35
|
-
samp,
|
|
36
|
-
pre,
|
|
37
|
-
.font-mono {
|
|
38
|
-
font-family:
|
|
39
|
-
'IBM Plex Mono',
|
|
40
|
-
ui-monospace,
|
|
41
|
-
'JetBrains Mono',
|
|
42
|
-
'SF Mono',
|
|
43
|
-
'Consolas',
|
|
44
|
-
monospace;
|
|
45
|
-
font-feature-settings: 'tnum' 1;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/* Tailwind's preflight resets the UA focus ring. Restore a high-contrast
|
|
49
|
-
ring for keyboard users (only :focus-visible — pointer focus stays clean).
|
|
50
|
-
Brand-700 #3730a3 over white = 9.6:1 contrast; same ring shows fine on
|
|
51
|
-
bg-brand-500 buttons too. */
|
|
52
|
-
:focus-visible {
|
|
53
|
-
outline: 2px solid #3730a3;
|
|
54
|
-
outline-offset: 2px;
|
|
55
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
// rc.352 — Single canonical audit view-model hook (Phase 2 of the
|
|
2
|
-
// single-data-pool work; see project-single-data-pool-direction memory).
|
|
3
|
-
//
|
|
4
|
-
// This is the ONE place every WCAG-AA-lens surface (v2 Hero/FindingsStream,
|
|
5
|
-
// the v1 dev report headline, exports' on-screen mirror) derives its
|
|
6
|
-
// view-model from the shared pool. It returns either the real crawl
|
|
7
|
-
// `SiteCrawlReport` or a synthesized single-page one — both built by the
|
|
8
|
-
// same `aggregateSiteResults` → `computeScore` path, so the headline grade
|
|
9
|
-
// + counts are identical no matter which UI renders them. (Owner mode is a
|
|
10
|
-
// SEPARATE lens — lawfare/lawsuit-risk — and does not use this.)
|
|
11
|
-
//
|
|
12
|
-
// Was `side-panel/v2/useV2Report.ts` (rc.283–297); moved here + renamed so
|
|
13
|
-
// v1 views can share it without a v1→v2 import. `useV2Report` remains as a
|
|
14
|
-
// re-export for the existing v2 call sites.
|
|
15
|
-
|
|
16
|
-
import { useEffect, useState } from 'react';
|
|
17
|
-
import { useStore } from './store';
|
|
18
|
-
import { aggregateSiteResults, type SiteCrawlReport } from '../shared/site-aggregator';
|
|
19
|
-
import { loadScoreInputsForResults } from '../shared/score-inputs';
|
|
20
|
-
import {
|
|
21
|
-
getResolutionsForPage,
|
|
22
|
-
applySelfCorrectionSalvage,
|
|
23
|
-
applyContrastMathSalvage,
|
|
24
|
-
} from '../shared/incomplete-resolutions';
|
|
25
|
-
import type { IncompleteResolution } from '../shared/wcag-verdicts';
|
|
26
|
-
|
|
27
|
-
export type AuditViewModel = {
|
|
28
|
-
report: SiteCrawlReport | null;
|
|
29
|
-
/** Failing pixel-contrast-sampler resolutions for the URLs in the
|
|
30
|
-
* report. Excludes 'pass'/'uncertain'. Empty when none. */
|
|
31
|
-
resolvedFails: IncompleteResolution[];
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export function useAuditViewModel(): AuditViewModel {
|
|
35
|
-
const storeReport = useStore((s) => s.siteCrawlReport);
|
|
36
|
-
const results = useStore((s) => s.results);
|
|
37
|
-
const delta = useStore((s) => s.delta);
|
|
38
|
-
const componentId = useStore((s) => s.componentId);
|
|
39
|
-
const status = useStore((s) => s.status);
|
|
40
|
-
const siteCrawlStatus = useStore((s) => s.siteCrawlStatus);
|
|
41
|
-
const [bundle, setBundle] = useState<AuditViewModel>({ report: null, resolvedFails: [] });
|
|
42
|
-
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
let cancelled = false;
|
|
45
|
-
|
|
46
|
-
async function loadResolutionsForPages(urls: string[]): Promise<IncompleteResolution[]> {
|
|
47
|
-
if (urls.length === 0) return [];
|
|
48
|
-
const all = await Promise.all(urls.map((u) => getResolutionsForPage(u)));
|
|
49
|
-
return all
|
|
50
|
-
.flat()
|
|
51
|
-
.map(applySelfCorrectionSalvage)
|
|
52
|
-
.map(applyContrastMathSalvage)
|
|
53
|
-
.filter((r) => r.verdict === 'fail');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// During ANY active audit, return empty so consumers don't render
|
|
57
|
-
// stale data underneath progress.
|
|
58
|
-
if (siteCrawlStatus === 'running' || status === 'running') {
|
|
59
|
-
setBundle({ report: null, resolvedFails: [] });
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Real crawl report path.
|
|
64
|
-
if (storeReport) {
|
|
65
|
-
void (async () => {
|
|
66
|
-
const urls = storeReport.pages.map((p) => p.url);
|
|
67
|
-
const fails = await loadResolutionsForPages(urls);
|
|
68
|
-
if (!cancelled) setBundle({ report: storeReport, resolvedFails: fails });
|
|
69
|
-
})();
|
|
70
|
-
return () => { cancelled = true; };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Idle / no data.
|
|
74
|
-
if (results.length === 0 || !componentId) {
|
|
75
|
-
setBundle({ report: null, resolvedFails: [] });
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const url = results[0]?.pageUrl;
|
|
79
|
-
if (!url) {
|
|
80
|
-
setBundle({ report: null, resolvedFails: [] });
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Synthesized single-page report path — same aggregateSiteResults
|
|
85
|
-
// path as a crawl, so the grade matches across every surface.
|
|
86
|
-
void (async () => {
|
|
87
|
-
try {
|
|
88
|
-
const [inputs, fails] = await Promise.all([
|
|
89
|
-
loadScoreInputsForResults(results),
|
|
90
|
-
loadResolutionsForPages([url]),
|
|
91
|
-
]);
|
|
92
|
-
const now = new Date().toISOString();
|
|
93
|
-
const synth = aggregateSiteResults(
|
|
94
|
-
url,
|
|
95
|
-
[{ url, results, delta, componentId, durationMs: 0 }],
|
|
96
|
-
now,
|
|
97
|
-
now,
|
|
98
|
-
0,
|
|
99
|
-
inputs.walkthroughsByComponent,
|
|
100
|
-
inputs.walkthroughAcksByComponent,
|
|
101
|
-
inputs.ackedKeysByComponent,
|
|
102
|
-
inputs.humanVerdictsByComponent,
|
|
103
|
-
inputs.incompleteResolutionsByComponent, // rc.362 — honor sampler-resolved contrasts so v2 grade matches dev
|
|
104
|
-
);
|
|
105
|
-
if (!cancelled) setBundle({ report: synth, resolvedFails: fails });
|
|
106
|
-
} catch (err) {
|
|
107
|
-
// eslint-disable-next-line no-console
|
|
108
|
-
console.warn('[view-model] synthesize single-page report failed', err);
|
|
109
|
-
if (!cancelled) setBundle({ report: null, resolvedFails: [] });
|
|
110
|
-
}
|
|
111
|
-
})();
|
|
112
|
-
return () => { cancelled = true; };
|
|
113
|
-
}, [storeReport, results, delta, componentId, status, siteCrawlStatus]);
|
|
114
|
-
|
|
115
|
-
return bundle;
|
|
116
|
-
}
|