@reticular/speakable 1.6.0 → 1.7.0

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.
@@ -0,0 +1,86 @@
1
+ // ../src/browser/upgrade.ts
2
+ var DEFAULT_UPGRADE_TIMEOUT_MS = 2e3;
3
+ function withTimeout(promise, timeoutMs) {
4
+ return new Promise((resolve) => {
5
+ let settled = false;
6
+ const timer = setTimeout(() => {
7
+ if (!settled) {
8
+ settled = true;
9
+ resolve(false);
10
+ }
11
+ }, timeoutMs);
12
+ promise.then(
13
+ () => {
14
+ if (!settled) {
15
+ settled = true;
16
+ clearTimeout(timer);
17
+ resolve(true);
18
+ }
19
+ },
20
+ () => {
21
+ if (!settled) {
22
+ settled = true;
23
+ clearTimeout(timer);
24
+ resolve(true);
25
+ }
26
+ }
27
+ );
28
+ });
29
+ }
30
+ function collectCustomElementTags(root) {
31
+ const tags = /* @__PURE__ */ new Set();
32
+ const rootEl = "documentElement" in root ? root.documentElement : root;
33
+ if (!rootEl) return [];
34
+ const consider = (el) => {
35
+ if (el.localName && el.localName.includes("-")) {
36
+ tags.add(el.localName);
37
+ }
38
+ };
39
+ consider(rootEl);
40
+ const all = rootEl.querySelectorAll("*");
41
+ all.forEach(consider);
42
+ return Array.from(tags);
43
+ }
44
+ function collectUpdateCompletables(root) {
45
+ const rootEl = "documentElement" in root ? root.documentElement : root;
46
+ if (!rootEl) return [];
47
+ const promises = [];
48
+ const consider = (el) => {
49
+ const uc = el.updateComplete;
50
+ if (uc && typeof uc.then === "function") {
51
+ promises.push(uc);
52
+ }
53
+ };
54
+ consider(rootEl);
55
+ rootEl.querySelectorAll("*").forEach(consider);
56
+ return promises;
57
+ }
58
+ async function awaitCustomElementsReady(root, timeoutMs = DEFAULT_UPGRADE_TIMEOUT_MS) {
59
+ const warnings = [];
60
+ const hasCustomElements = typeof customElements !== "undefined" && customElements !== null;
61
+ if (!hasCustomElements) {
62
+ return warnings;
63
+ }
64
+ const tags = collectCustomElementTags(root);
65
+ await Promise.all(
66
+ tags.map(async (tag) => {
67
+ if (customElements.get(tag)) return;
68
+ const defined = await withTimeout(customElements.whenDefined(tag), timeoutMs);
69
+ if (!defined) {
70
+ warnings.push({
71
+ tag,
72
+ message: `Custom element "${tag}" did not upgrade within ${timeoutMs}ms; analysis may reflect a pre-hydration state.`
73
+ });
74
+ }
75
+ })
76
+ );
77
+ const updateCompletables = collectUpdateCompletables(root);
78
+ if (updateCompletables.length > 0) {
79
+ await withTimeout(Promise.all(updateCompletables), timeoutMs);
80
+ }
81
+ return warnings;
82
+ }
83
+
84
+ export {
85
+ awaitCustomElementsReady
86
+ };
@@ -24,17 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  mod
25
25
  ));
26
26
 
27
- // src/index.ts
28
- var ADDON_ID = "speakable";
29
- var PANEL_ID = `${ADDON_ID}/panel`;
30
- var EVENT_RENDER = `${ADDON_ID}/render`;
31
- var EVENT_RESULT = `${ADDON_ID}/result`;
32
-
33
27
  export {
34
28
  __commonJS,
35
- __toESM,
36
- ADDON_ID,
37
- PANEL_ID,
38
- EVENT_RENDER,
39
- EVENT_RESULT
29
+ __toESM
40
30
  };
@@ -0,0 +1,16 @@
1
+ // src/index.ts
2
+ var ADDON_ID = "speakable";
3
+ var PANEL_ID = `${ADDON_ID}/panel`;
4
+ var EVENT_RENDER = `${ADDON_ID}/render`;
5
+ var EVENT_RESULT = `${ADDON_ID}/result`;
6
+ var EVENT_TIMELINE = `${ADDON_ID}/timeline`;
7
+ var EVENT_DIFF = `${ADDON_ID}/diff`;
8
+
9
+ export {
10
+ ADDON_ID,
11
+ PANEL_ID,
12
+ EVENT_RENDER,
13
+ EVENT_RESULT,
14
+ EVENT_TIMELINE,
15
+ EVENT_DIFF
16
+ };
@@ -1,12 +1,17 @@
1
1
  import {
2
2
  ADDON_ID,
3
+ EVENT_DIFF,
3
4
  EVENT_RENDER,
4
5
  EVENT_RESULT,
6
+ EVENT_TIMELINE,
5
7
  PANEL_ID
6
- } from "./chunk-CL3DJBD7.js";
8
+ } from "./chunk-QVJRQZT5.js";
9
+ import "./chunk-JSBRDJBE.js";
7
10
  export {
8
11
  ADDON_ID,
12
+ EVENT_DIFF,
9
13
  EVENT_RENDER,
10
14
  EVENT_RESULT,
15
+ EVENT_TIMELINE,
11
16
  PANEL_ID
12
17
  };
@@ -1,31 +1,325 @@
1
1
  import {
2
2
  ADDON_ID,
3
3
  EVENT_RESULT,
4
+ EVENT_TIMELINE,
4
5
  PANEL_ID
5
- } from "./chunk-CL3DJBD7.js";
6
+ } from "./chunk-QVJRQZT5.js";
7
+ import "./chunk-JSBRDJBE.js";
6
8
 
7
9
  // src/manager.tsx
8
10
  import { useState, useEffect } from "react";
9
11
  import { addons, types } from "@storybook/manager-api";
12
+
13
+ // ../src/runtime/diff-engine.ts
14
+ function matchingKey(event) {
15
+ return `${event.type}::${event.target.selector}`;
16
+ }
17
+ function payloadsEqual(a, b) {
18
+ if (a.type !== b.type) return false;
19
+ if (!targetsEqual(a.target, b.target)) return false;
20
+ return deepEqual(a.payload, b.payload);
21
+ }
22
+ function targetsEqual(a, b) {
23
+ return a.role === b.role && a.accessibleName === b.accessibleName && a.selector === b.selector;
24
+ }
25
+ function deepEqual(a, b) {
26
+ if (a === b) return true;
27
+ if (a === null || b === null) return a === b;
28
+ if (typeof a !== typeof b) return false;
29
+ if (typeof a !== "object") return a === b;
30
+ const aObj = a;
31
+ const bObj = b;
32
+ const aKeys = Object.keys(aObj).sort();
33
+ const bKeys = Object.keys(bObj).sort();
34
+ if (aKeys.length !== bKeys.length) return false;
35
+ for (let i = 0; i < aKeys.length; i++) {
36
+ if (aKeys[i] !== bKeys[i]) return false;
37
+ if (!deepEqual(aObj[aKeys[i]], bObj[bKeys[i]])) return false;
38
+ }
39
+ return true;
40
+ }
41
+ function buildModifiedMessage(baseline, current) {
42
+ const type = current.type;
43
+ const selector = current.target.selector;
44
+ if (baseline.target.accessibleName !== current.target.accessibleName) {
45
+ return `${type} on "${selector}": accessible name changed from "${baseline.target.accessibleName}" to "${current.target.accessibleName}"`;
46
+ }
47
+ if (baseline.target.role !== current.target.role) {
48
+ return `${type} on "${selector}": role changed from "${baseline.target.role}" to "${current.target.role}"`;
49
+ }
50
+ return `${type} on "${selector}": payload values differ`;
51
+ }
52
+ function diffTimelines(baseline, current) {
53
+ const added = [];
54
+ const removed = [];
55
+ const modified = [];
56
+ const currentByKey = /* @__PURE__ */ new Map();
57
+ for (const event of current.events) {
58
+ const key = matchingKey(event);
59
+ const existing = currentByKey.get(key);
60
+ if (existing) {
61
+ existing.push(event);
62
+ } else {
63
+ currentByKey.set(key, [event]);
64
+ }
65
+ }
66
+ const matchedCurrentIndices = /* @__PURE__ */ new Set();
67
+ for (const baselineEvent of baseline.events) {
68
+ const key = matchingKey(baselineEvent);
69
+ const candidates = currentByKey.get(key);
70
+ if (!candidates || candidates.length === 0) {
71
+ removed.push({
72
+ event: baselineEvent,
73
+ message: `${baselineEvent.type} on "${baselineEvent.target.selector}" was removed`
74
+ });
75
+ continue;
76
+ }
77
+ let matched = false;
78
+ for (let i = 0; i < candidates.length; i++) {
79
+ const candidateId = `${key}::${i}`;
80
+ if (matchedCurrentIndices.has(candidateId)) continue;
81
+ const currentEvent = candidates[i];
82
+ matchedCurrentIndices.add(candidateId);
83
+ if (!payloadsEqual(baselineEvent, currentEvent)) {
84
+ modified.push({
85
+ event: currentEvent,
86
+ previousEvent: baselineEvent,
87
+ message: buildModifiedMessage(baselineEvent, currentEvent)
88
+ });
89
+ }
90
+ matched = true;
91
+ break;
92
+ }
93
+ if (!matched) {
94
+ removed.push({
95
+ event: baselineEvent,
96
+ message: `${baselineEvent.type} on "${baselineEvent.target.selector}" was removed`
97
+ });
98
+ }
99
+ }
100
+ for (const [key, candidates] of currentByKey.entries()) {
101
+ for (let i = 0; i < candidates.length; i++) {
102
+ const candidateId = `${key}::${i}`;
103
+ if (!matchedCurrentIndices.has(candidateId)) {
104
+ const event = candidates[i];
105
+ added.push({
106
+ event,
107
+ message: `${event.type} on "${event.target.selector}" was added`
108
+ });
109
+ }
110
+ }
111
+ }
112
+ return {
113
+ baseline: { totalEvents: baseline.events.length },
114
+ current: { totalEvents: current.events.length },
115
+ added,
116
+ removed,
117
+ modified,
118
+ summary: {
119
+ totalBaseline: baseline.events.length,
120
+ totalCurrent: current.events.length,
121
+ added: added.length,
122
+ removed: removed.length,
123
+ modified: modified.length
124
+ }
125
+ };
126
+ }
127
+
128
+ // ../src/runtime/severity.ts
129
+ function classifyDiff(report) {
130
+ const entries = [];
131
+ for (const entry of report.removed) {
132
+ const severity = classifyRemoved(entry);
133
+ entries.push({ ...entry, severity });
134
+ }
135
+ for (const entry of report.added) {
136
+ const severity = classifyAdded(entry);
137
+ entries.push({ ...entry, severity });
138
+ }
139
+ for (const entry of report.modified) {
140
+ const severity = classifyModified(entry);
141
+ entries.push({ ...entry, severity });
142
+ }
143
+ const criticalCount = entries.filter((e) => e.severity === "critical").length;
144
+ const highCount = entries.filter((e) => e.severity === "high").length;
145
+ const mediumCount = entries.filter((e) => e.severity === "medium").length;
146
+ const lowCount = entries.filter((e) => e.severity === "low").length;
147
+ let highestSeverity = null;
148
+ if (criticalCount > 0) highestSeverity = "critical";
149
+ else if (highCount > 0) highestSeverity = "high";
150
+ else if (mediumCount > 0) highestSeverity = "medium";
151
+ else if (lowCount > 0) highestSeverity = "low";
152
+ return { entries, highestSeverity, criticalCount, highCount, mediumCount, lowCount };
153
+ }
154
+ function classifyRemoved(entry) {
155
+ switch (entry.event.type) {
156
+ case "FOCUS_CHANGED":
157
+ return "critical";
158
+ case "ANNOUNCEMENT":
159
+ return "critical";
160
+ case "DIALOG_OPENED":
161
+ return "high";
162
+ case "DIALOG_CLOSED":
163
+ return "high";
164
+ case "STATE_CHANGED":
165
+ return "high";
166
+ case "KEYBOARD_ACTION":
167
+ return "medium";
168
+ default:
169
+ return "medium";
170
+ }
171
+ }
172
+ function classifyAdded(entry) {
173
+ switch (entry.event.type) {
174
+ case "WARNING":
175
+ return "high";
176
+ case "REGRESSION":
177
+ return "critical";
178
+ case "ANNOUNCEMENT":
179
+ return "medium";
180
+ case "FOCUS_CHANGED":
181
+ return "medium";
182
+ default:
183
+ return "low";
184
+ }
185
+ }
186
+ function classifyModified(entry) {
187
+ switch (entry.event.type) {
188
+ case "FOCUS_CHANGED":
189
+ return "high";
190
+ case "STATE_CHANGED":
191
+ return "high";
192
+ case "ACCESSIBLE_NAME_CHANGED":
193
+ return "medium";
194
+ case "ANNOUNCEMENT":
195
+ return "medium";
196
+ default:
197
+ return "medium";
198
+ }
199
+ }
200
+
201
+ // src/baseline-store.ts
202
+ var STORAGE_PREFIX = "speakable:baseline:";
203
+ function getStorage() {
204
+ try {
205
+ if (typeof localStorage !== "undefined") return localStorage;
206
+ } catch {
207
+ }
208
+ return null;
209
+ }
210
+ function createPanelBaselineStore() {
211
+ const memory = /* @__PURE__ */ new Map();
212
+ function storageKey(component, story) {
213
+ return `${STORAGE_PREFIX}${component}::${story}`;
214
+ }
215
+ return {
216
+ key(component, story) {
217
+ return storageKey(component, story);
218
+ },
219
+ async save(component, story, timeline) {
220
+ const k = storageKey(component, story);
221
+ const json = JSON.stringify(timeline);
222
+ const storage = getStorage();
223
+ if (storage) {
224
+ storage.setItem(k, json);
225
+ } else {
226
+ memory.set(k, json);
227
+ }
228
+ },
229
+ async load(component, story) {
230
+ const k = storageKey(component, story);
231
+ const storage = getStorage();
232
+ const json = storage ? storage.getItem(k) : memory.get(k) ?? null;
233
+ if (!json) return null;
234
+ try {
235
+ return JSON.parse(json);
236
+ } catch {
237
+ return null;
238
+ }
239
+ }
240
+ };
241
+ }
242
+
243
+ // src/manager.tsx
10
244
  import { jsx, jsxs } from "react/jsx-runtime";
11
245
  var READER_TABS = [
12
246
  { key: "nvda", label: "NVDA", color: "#3b82f6" },
13
247
  { key: "jaws", label: "JAWS", color: "#8b5cf6" },
14
248
  { key: "voiceover", label: "VoiceOver", color: "#06b6d4" },
15
249
  { key: "narrator", label: "Narrator", color: "#f59e0b" },
16
- { key: "audit", label: "Audit", color: "#ef4444" }
250
+ { key: "audit", label: "Audit", color: "#ef4444" },
251
+ { key: "timeline", label: "Timeline", color: "#10b981" },
252
+ { key: "diff", label: "Diff", color: "#ec4899" }
17
253
  ];
254
+ var baselineStore = createPanelBaselineStore();
255
+ var SEVERITY_COLORS = {
256
+ critical: "#ef4444",
257
+ high: "#f97316",
258
+ medium: "#f59e0b",
259
+ low: "#6b7280"
260
+ };
261
+ function summarizePayload(event) {
262
+ const p = event.payload;
263
+ switch (p.kind) {
264
+ case "focus_changed":
265
+ return "focus moved here";
266
+ case "announcement":
267
+ return `announce (${p.politeness}): "${p.text}"`;
268
+ case "role_changed":
269
+ return `role ${p.previousRole} \u2192 ${p.newRole}`;
270
+ case "accessible_name_changed":
271
+ return `name "${p.previousName}" \u2192 "${p.newName}"`;
272
+ case "state_changed":
273
+ return `${p.attribute}: ${String(p.previousValue)} \u2192 ${String(p.newValue)}`;
274
+ case "dialog_opened":
275
+ return `dialog opened: ${p.dialogName}${p.isModal ? " (modal)" : ""}`;
276
+ case "dialog_closed":
277
+ return `dialog closed: ${p.dialogName}`;
278
+ case "keyboard_action":
279
+ return `key ${[...p.modifiers || [], p.key].join("+")}`;
280
+ case "warning":
281
+ return `\u26A0 ${p.message}`;
282
+ default:
283
+ return "";
284
+ }
285
+ }
18
286
  function SpeakablePanel() {
19
287
  const [result, setResult] = useState(null);
288
+ const [timeline, setTimeline] = useState(null);
289
+ const [diff, setDiff] = useState(null);
290
+ const [hasBaseline, setHasBaseline] = useState(false);
20
291
  const [activeTab, setActiveTab] = useState("nvda");
21
292
  useEffect(() => {
22
293
  const channel = addons.getChannel();
23
294
  const handleResult = (data) => {
24
295
  setResult(data);
25
296
  };
297
+ const handleTimeline = async (data) => {
298
+ setTimeline(data);
299
+ const baseline = await baselineStore.load(data.component, data.story || "Story");
300
+ if (baseline) {
301
+ setHasBaseline(true);
302
+ const report = diffTimelines(baseline, data);
303
+ setDiff(classifyDiff(report));
304
+ } else {
305
+ setHasBaseline(false);
306
+ setDiff(null);
307
+ }
308
+ };
26
309
  channel.on(EVENT_RESULT, handleResult);
27
- return () => channel.off(EVENT_RESULT, handleResult);
310
+ channel.on(EVENT_TIMELINE, handleTimeline);
311
+ return () => {
312
+ channel.off(EVENT_RESULT, handleResult);
313
+ channel.off(EVENT_TIMELINE, handleTimeline);
314
+ };
28
315
  }, []);
316
+ const handleSetBaseline = async () => {
317
+ if (!timeline) return;
318
+ await baselineStore.save(timeline.component, timeline.story || "Story", timeline);
319
+ setHasBaseline(true);
320
+ const report = diffTimelines(timeline, timeline);
321
+ setDiff(classifyDiff(report));
322
+ };
29
323
  if (!result) {
30
324
  return /* @__PURE__ */ jsxs("div", { style: styles.emptyState, children: [
31
325
  /* @__PURE__ */ jsx("div", { style: styles.emptyIcon, children: "\u{1F50A}" }),
@@ -33,7 +327,9 @@ function SpeakablePanel() {
33
327
  /* @__PURE__ */ jsx("p", { style: styles.emptySubtext, children: "Powered by Speakable" })
34
328
  ] });
35
329
  }
36
- const activeLines = activeTab === "audit" ? [] : result[activeTab];
330
+ const READER_KEYS = ["nvda", "jaws", "voiceover", "narrator"];
331
+ const isReaderTab = READER_KEYS.includes(activeTab);
332
+ const activeLines = isReaderTab ? result[activeTab] : [];
37
333
  const auditFindings = result.audit;
38
334
  return /* @__PURE__ */ jsxs("div", { style: styles.container, children: [
39
335
  /* @__PURE__ */ jsx("div", { style: styles.tabBar, children: READER_TABS.map((tab) => /* @__PURE__ */ jsxs(
@@ -69,7 +365,7 @@ function SpeakablePanel() {
69
365
  " headings"
70
366
  ] })
71
367
  ] }),
72
- /* @__PURE__ */ jsx("div", { style: styles.content, children: activeTab === "audit" ? /* @__PURE__ */ jsx(AuditView, { findings: auditFindings }) : /* @__PURE__ */ jsx(AnnouncementView, { lines: activeLines, readerKey: activeTab }) }),
368
+ /* @__PURE__ */ jsx("div", { style: styles.content, children: activeTab === "audit" ? /* @__PURE__ */ jsx(AuditView, { findings: auditFindings }) : activeTab === "timeline" ? /* @__PURE__ */ jsx(TimelineView, { timeline, hasBaseline, onSetBaseline: handleSetBaseline }) : activeTab === "diff" ? /* @__PURE__ */ jsx(DiffView, { diff, hasBaseline, hasTimeline: !!timeline, onSetBaseline: handleSetBaseline }) : /* @__PURE__ */ jsx(AnnouncementView, { lines: activeLines, readerKey: activeTab }) }),
73
369
  /* @__PURE__ */ jsxs("div", { style: styles.footer, children: [
74
370
  /* @__PURE__ */ jsx("span", { style: styles.footerText, children: "Predictions are heuristic-based. Verify with real screen readers before release." }),
75
371
  /* @__PURE__ */ jsx("a", { href: "https://getspeakable.dev", target: "_blank", rel: "noopener noreferrer", style: styles.footerLink, children: "getspeakable.dev" })
@@ -101,6 +397,86 @@ function AuditView({ findings }) {
101
397
  ] })
102
398
  ] }, i)) });
103
399
  }
400
+ function TimelineView({
401
+ timeline,
402
+ hasBaseline,
403
+ onSetBaseline
404
+ }) {
405
+ if (!timeline) {
406
+ return /* @__PURE__ */ jsxs("p", { style: styles.emptyContent, children: [
407
+ "No interaction timeline for this story. Add",
408
+ " ",
409
+ /* @__PURE__ */ jsx("code", { children: "parameters.speakable.sequence" }),
410
+ " or",
411
+ " ",
412
+ /* @__PURE__ */ jsx("code", { children: "parameters.speakable.pattern" }),
413
+ " to capture one."
414
+ ] });
415
+ }
416
+ return /* @__PURE__ */ jsxs("div", { children: [
417
+ /* @__PURE__ */ jsxs("div", { style: styles.baselineBar, children: [
418
+ /* @__PURE__ */ jsxs("span", { style: styles.stat, children: [
419
+ timeline.events.length,
420
+ " events \xB7 ",
421
+ timeline.duration,
422
+ "ms"
423
+ ] }),
424
+ /* @__PURE__ */ jsx("span", { style: styles.baselineHint, children: hasBaseline ? "\u2713 baseline set" : "No baseline yet" }),
425
+ /* @__PURE__ */ jsx("button", { style: styles.baselineButton, onClick: onSetBaseline, children: "Set baseline" })
426
+ ] }),
427
+ /* @__PURE__ */ jsx("div", { style: styles.lineList, children: timeline.events.map((event, i) => /* @__PURE__ */ jsxs("div", { style: styles.line, children: [
428
+ /* @__PURE__ */ jsxs("span", { style: styles.lineNumber, children: [
429
+ event.timestamp,
430
+ "ms"
431
+ ] }),
432
+ /* @__PURE__ */ jsxs("span", { style: styles.lineText, children: [
433
+ /* @__PURE__ */ jsx("strong", { children: event.type }),
434
+ event.target.role ? ` \xB7 ${event.target.role}` : "",
435
+ event.target.accessibleName ? ` "${event.target.accessibleName}"` : "",
436
+ summarizePayload(event) ? ` \u2014 ${summarizePayload(event)}` : ""
437
+ ] })
438
+ ] }, i)) })
439
+ ] });
440
+ }
441
+ function DiffView({
442
+ diff,
443
+ hasBaseline,
444
+ hasTimeline,
445
+ onSetBaseline
446
+ }) {
447
+ if (!hasBaseline) {
448
+ return /* @__PURE__ */ jsxs("div", { style: styles.emptyState, children: [
449
+ /* @__PURE__ */ jsx("p", { style: styles.emptyText, children: "No baseline to compare against" }),
450
+ /* @__PURE__ */ jsx("p", { style: styles.emptySubtext, children: "Capture a timeline, then set it as the baseline to detect regressions." }),
451
+ hasTimeline && /* @__PURE__ */ jsx("button", { style: styles.baselineButton, onClick: onSetBaseline, children: "Set baseline" })
452
+ ] });
453
+ }
454
+ if (!diff || diff.entries.length === 0) {
455
+ return /* @__PURE__ */ jsxs("div", { style: styles.auditPass, children: [
456
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 24 }, children: "\u2713" }),
457
+ /* @__PURE__ */ jsx("p", { style: { margin: 0, fontWeight: 600, color: "#059669" }, children: "No behavioral changes" }),
458
+ /* @__PURE__ */ jsx("p", { style: { margin: 0, fontSize: 12, color: "#6b7280" }, children: "The current timeline matches the baseline." })
459
+ ] });
460
+ }
461
+ return /* @__PURE__ */ jsx("div", { style: styles.lineList, children: diff.entries.map((entry, i) => /* @__PURE__ */ jsxs(
462
+ "div",
463
+ {
464
+ style: { ...styles.finding, background: "#fff7fb", borderLeftWidth: 3, borderLeftStyle: "solid", borderLeftColor: SEVERITY_COLORS[entry.severity] },
465
+ children: [
466
+ /* @__PURE__ */ jsx("div", { style: { ...styles.findingSeverity, color: SEVERITY_COLORS[entry.severity] }, children: entry.severity.toUpperCase() }),
467
+ /* @__PURE__ */ jsxs("div", { children: [
468
+ /* @__PURE__ */ jsx("p", { style: styles.findingMessage, children: entry.message }),
469
+ /* @__PURE__ */ jsxs("p", { style: styles.findingSelector, children: [
470
+ entry.event.type,
471
+ " \xB7 ",
472
+ entry.event.target.selector
473
+ ] })
474
+ ] })
475
+ ]
476
+ },
477
+ i
478
+ )) });
479
+ }
104
480
  var styles = {
105
481
  container: {
106
482
  display: "flex",
@@ -157,6 +533,29 @@ var styles = {
157
533
  borderBottom: "1px solid #f1f5f9",
158
534
  background: "#f8fafc"
159
535
  },
536
+ baselineBar: {
537
+ display: "flex",
538
+ alignItems: "center",
539
+ gap: 12,
540
+ padding: "8px 16px",
541
+ borderBottom: "1px solid #f1f5f9",
542
+ background: "#f8fafc"
543
+ },
544
+ baselineHint: {
545
+ fontSize: 11,
546
+ color: "#64748b",
547
+ marginLeft: "auto"
548
+ },
549
+ baselineButton: {
550
+ fontSize: 11,
551
+ fontWeight: 600,
552
+ color: "#fff",
553
+ background: "#10b981",
554
+ border: "none",
555
+ borderRadius: 4,
556
+ padding: "4px 10px",
557
+ cursor: "pointer"
558
+ },
160
559
  stat: {
161
560
  fontSize: 11,
162
561
  color: "#64748b",