sela-core 1.0.8 → 1.0.9
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/README.md +27 -17
- package/dist/cli/commands/init.js +1 -1
- package/dist/cli/commands/merge.d.ts +13 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +119 -0
- package/dist/cli/index.js +4 -1
- package/dist/config/SelaConfig.d.ts +1 -1
- package/dist/config/SelaConfig.d.ts.map +1 -1
- package/dist/config/SelaConfig.js +1 -1
- package/dist/engine/SelaEngine.d.ts.map +1 -1
- package/dist/engine/SelaEngine.js +54 -2
- package/dist/errors/SelaError.d.ts +8 -0
- package/dist/errors/SelaError.d.ts.map +1 -1
- package/dist/errors/SelaError.js +8 -0
- package/dist/fixtures/expectProxy.d.ts +1 -1
- package/dist/fixtures/expectProxy.js +6 -6
- package/dist/fixtures/index.d.ts +18 -2
- package/dist/fixtures/index.d.ts.map +1 -1
- package/dist/fixtures/index.js +34 -14
- package/dist/fixtures/moduleExpect.js +7 -7
- package/dist/fixtures/proxyTag.js +1 -1
- package/dist/services/ASTSourceUpdater.d.ts +45 -0
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -1
- package/dist/services/ASTSourceUpdater.js +201 -47
- package/dist/services/AnchorResolver.d.ts +157 -0
- package/dist/services/AnchorResolver.d.ts.map +1 -0
- package/dist/services/AnchorResolver.js +289 -0
- package/dist/services/DecisionEngine.d.ts +51 -0
- package/dist/services/DecisionEngine.d.ts.map +1 -0
- package/dist/services/DecisionEngine.js +260 -0
- package/dist/services/HealReportService.d.ts +31 -55
- package/dist/services/HealReportService.d.ts.map +1 -1
- package/dist/services/HealReportService.js +863 -1243
- package/dist/services/HealingAdvisory.d.ts +9 -1
- package/dist/services/HealingAdvisory.d.ts.map +1 -1
- package/dist/services/HealingAdvisory.js +8 -0
- package/dist/services/InitializerUpdater.d.ts.map +1 -1
- package/dist/services/InitializerUpdater.js +10 -0
- package/dist/services/InteractiveReview.d.ts.map +1 -1
- package/dist/services/InteractiveReview.js +4 -1
- package/dist/services/MutationApplier.d.ts +55 -0
- package/dist/services/MutationApplier.d.ts.map +1 -0
- package/dist/services/MutationApplier.js +322 -0
- package/dist/services/PendingPromptLedger.d.ts +7 -0
- package/dist/services/PendingPromptLedger.d.ts.map +1 -1
- package/dist/services/PendingPromptLedger.js +25 -0
- package/dist/services/ReportGenerator.d.ts +116 -30
- package/dist/services/ReportGenerator.d.ts.map +1 -1
- package/dist/services/ReportGenerator.js +150 -63
- package/dist/services/ReportMergeService.d.ts +95 -0
- package/dist/services/ReportMergeService.d.ts.map +1 -0
- package/dist/services/ReportMergeService.js +0 -0
- package/dist/services/SafetyGuard.d.ts +1 -1
- package/dist/services/SafetyGuard.d.ts.map +1 -1
- package/dist/services/SelectorSanitizer.d.ts +52 -0
- package/dist/services/SelectorSanitizer.d.ts.map +1 -0
- package/dist/services/SelectorSanitizer.js +318 -0
- package/dist/services/SnapshotService.js +1 -1
- package/dist/services/SourceUpdater.d.ts.map +1 -1
- package/dist/services/SourceUpdater.js +17 -0
- package/dist/services/TraceBackEngine.d.ts +67 -0
- package/dist/services/TraceBackEngine.d.ts.map +1 -0
- package/dist/services/TraceBackEngine.js +672 -0
- package/dist/utils/DOMUtils.d.ts +18 -2
- package/dist/utils/DOMUtils.d.ts.map +1 -1
- package/dist/utils/DOMUtils.js +335 -49
- package/package.json +1 -1
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// src/services/HealReportService.ts
|
|
3
3
|
//
|
|
4
|
-
// Sela Insights
|
|
5
|
-
// Buffers a HealEvent per heal attempt (HEALED, FAILED, PROTECTED), then renders
|
|
6
|
-
// a single self-contained HTML file (sela-report.html) at commitUpdates() time.
|
|
4
|
+
// Sela Insights — Transparent Healing Report.
|
|
7
5
|
//
|
|
8
|
-
//
|
|
6
|
+
// Responsibilities:
|
|
7
|
+
// • Buffer HealEvents during a Playwright run (record / attachFailureScreenshot).
|
|
8
|
+
// • On flushToDisk():
|
|
9
|
+
// 1. Read `.sela-history.json` sidecar from the project root.
|
|
10
|
+
// 2. Call ReportGenerator.generate() to validate, compute stats/ROI,
|
|
11
|
+
// and merge the current run into the history window.
|
|
12
|
+
// 3. Write the updated history back to `.sela-history.json`.
|
|
13
|
+
// 4. Render and write `sela-report.html` (the full embedded dashboard).
|
|
14
|
+
// 5. Write `sela-report.json` (the raw payload for CI consumers).
|
|
15
|
+
//
|
|
16
|
+
// Template embedding strategy:
|
|
17
|
+
// CSS and JS are stored as plain string constants using Array.join() instead
|
|
18
|
+
// of template literals so TypeScript never misinterprets backticks or ${...}
|
|
19
|
+
// expressions that belong to the browser runtime. renderHtml() assembles the
|
|
20
|
+
// final HTML via simple string concatenation — zero template literals that
|
|
21
|
+
// could interact with the embedded front-end code.
|
|
9
22
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
23
|
if (k2 === undefined) k2 = k;
|
|
11
24
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -44,13 +57,14 @@ exports.sharedHealReport = exports.HealReportService = void 0;
|
|
|
44
57
|
exports.summariseSnapshot = summariseSnapshot;
|
|
45
58
|
const fs = __importStar(require("fs"));
|
|
46
59
|
const path = __importStar(require("path"));
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
//
|
|
60
|
+
const ReportGenerator_1 = require("./ReportGenerator");
|
|
61
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
62
|
+
// SIDECAR CONFIG
|
|
63
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
64
|
+
const HISTORY_FILE = ".sela-history.json";
|
|
65
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
66
|
+
// SNAPSHOT SUMMARISER
|
|
67
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
54
68
|
function summariseSnapshot(s) {
|
|
55
69
|
if (!s)
|
|
56
70
|
return null;
|
|
@@ -69,9 +83,9 @@ function summariseSnapshot(s) {
|
|
|
69
83
|
isInShadowDom: s.isInShadowDom ?? false,
|
|
70
84
|
};
|
|
71
85
|
}
|
|
72
|
-
//
|
|
86
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
73
87
|
// HealReportService
|
|
74
|
-
//
|
|
88
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
75
89
|
class HealReportService {
|
|
76
90
|
events = [];
|
|
77
91
|
startedAt = new Date().toISOString();
|
|
@@ -88,35 +102,19 @@ class HealReportService {
|
|
|
88
102
|
hasContent() {
|
|
89
103
|
return this.events.length > 0;
|
|
90
104
|
}
|
|
91
|
-
/** All HEALED events accumulated in this session. */
|
|
92
105
|
getHealedEvents() {
|
|
93
106
|
return this.events.filter((e) => e.kind === "HEALED");
|
|
94
107
|
}
|
|
95
|
-
/** All PROTECTED events (SafetyGuard blocks) accumulated in this session. */
|
|
96
108
|
getProtectedEvents() {
|
|
97
109
|
return this.events.filter((e) => e.kind === "PROTECTED");
|
|
98
110
|
}
|
|
99
|
-
/** All FAILED events accumulated in this session. */
|
|
100
111
|
getFailedEvents() {
|
|
101
112
|
return this.events.filter((e) => e.kind === "FAILED");
|
|
102
113
|
}
|
|
103
114
|
/**
|
|
104
|
-
*
|
|
105
|
-
* onto the FAILED event(s) matching the supplied identity hints.
|
|
106
|
-
*
|
|
107
|
-
* Playwright-captured screenshot from `TestResult.attachments`.
|
|
108
|
-
*
|
|
109
|
-
* Match policy (all supplied hints must agree):
|
|
110
|
-
* • `sourceFile` - suffix-matched both directions to absorb
|
|
111
|
-
* project-relative vs absolute path mismatches between the engine
|
|
112
|
-
* (relative) and Playwright TestInfo.location.file (absolute).
|
|
113
|
-
* • `testTitle` - exact match.
|
|
114
|
-
* • `stableId` - exact match.
|
|
115
|
-
*
|
|
116
|
-
* Returns the number of events updated. Zero means the caller should
|
|
117
|
-
* keep the screenshot pending until the matching FailedEvent is
|
|
118
|
-
* recorded (worker-crash edge case where the heal pipeline did not
|
|
119
|
-
* run before the Reporter received the failure).
|
|
115
|
+
* Attach a Playwright failure screenshot (Base64 PNG, no data: prefix)
|
|
116
|
+
* onto the FAILED event(s) matching the supplied identity hints.
|
|
117
|
+
* Returns the number of events updated.
|
|
120
118
|
*/
|
|
121
119
|
attachFailureScreenshot(match, base64Png) {
|
|
122
120
|
if (!base64Png)
|
|
@@ -142,1228 +140,850 @@ class HealReportService {
|
|
|
142
140
|
}
|
|
143
141
|
return touched;
|
|
144
142
|
}
|
|
143
|
+
// ── History sidecar ─────────────────────────────────────────────────────
|
|
144
|
+
readHistory(targetDir) {
|
|
145
|
+
const histPath = path.join(targetDir, HISTORY_FILE);
|
|
146
|
+
try {
|
|
147
|
+
const raw = fs.readFileSync(histPath, "utf8");
|
|
148
|
+
const parsed = JSON.parse(raw);
|
|
149
|
+
if (!Array.isArray(parsed))
|
|
150
|
+
return [];
|
|
151
|
+
return parsed;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
writeHistory(targetDir, runs) {
|
|
158
|
+
const histPath = path.join(targetDir, HISTORY_FILE);
|
|
159
|
+
try {
|
|
160
|
+
fs.writeFileSync(histPath, JSON.stringify(runs, null, 2), "utf8");
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
console.warn("[sela] Could not write history sidecar:", err);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// ── Main flush ──────────────────────────────────────────────────────────
|
|
145
167
|
/**
|
|
146
|
-
*
|
|
147
|
-
*
|
|
168
|
+
* Generate and write `sela-report.html` + `sela-report.json` to targetDir.
|
|
169
|
+
* Also updates `.sela-history.json` with the current run's stats.
|
|
170
|
+
* Returns the absolute path of the HTML report, or null when there is
|
|
171
|
+
* nothing to write.
|
|
148
172
|
*/
|
|
149
|
-
flushToDisk(targetDir = process.cwd()) {
|
|
173
|
+
flushToDisk(targetDir = process.cwd(), opts = {}) {
|
|
150
174
|
if (this.events.length === 0)
|
|
151
175
|
return null;
|
|
152
|
-
const
|
|
176
|
+
const priorHistory = this.readHistory(targetDir);
|
|
177
|
+
const data = ReportGenerator_1.ReportGenerator.generate(this.events, {
|
|
178
|
+
cwd: targetDir,
|
|
179
|
+
startedAt: this.startedAt,
|
|
180
|
+
historicalRuns: priorHistory,
|
|
181
|
+
runId: opts.runId ??
|
|
182
|
+
process.env["GITHUB_RUN_ID"] ??
|
|
183
|
+
process.env["CI_PIPELINE_ID"] ??
|
|
184
|
+
undefined,
|
|
185
|
+
});
|
|
186
|
+
return this.writeReportData(targetDir, data);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Render and persist a pre-computed {@link ReportData} payload to `targetDir`:
|
|
190
|
+
* • `.sela-history.json` (the rolling window from `data.historical_runs`)
|
|
191
|
+
* • `sela-report.html` (the embedded dashboard)
|
|
192
|
+
* • `sela-report.json` (the raw payload for CI consumers)
|
|
193
|
+
*
|
|
194
|
+
* Extracted from {@link flushToDisk} so the CI merge path
|
|
195
|
+
* (`ReportMergeService`) writes a shard-aggregated report through the exact
|
|
196
|
+
* same rendering / serialisation logic — no duplication.
|
|
197
|
+
*
|
|
198
|
+
* Unlike `flushToDisk`, this writes unconditionally: an empty-event payload
|
|
199
|
+
* still produces a report and a `healed:0` history row. That is the desired
|
|
200
|
+
* CI behaviour — a clean, zero-heal pipeline is a healthy data point that
|
|
201
|
+
* must keep the trend chart's x-axis cadence intact.
|
|
202
|
+
*
|
|
203
|
+
* Returns the absolute path of the written HTML report.
|
|
204
|
+
*/
|
|
205
|
+
writeReportData(targetDir, data) {
|
|
206
|
+
this.writeHistory(targetDir, data.historical_runs);
|
|
207
|
+
const html = renderHtml(data);
|
|
153
208
|
const htmlPath = path.join(targetDir, "sela-report.html");
|
|
154
|
-
const jsonPath = path.join(targetDir, "sela-report.json");
|
|
155
209
|
fs.writeFileSync(htmlPath, html, "utf8");
|
|
210
|
+
const jsonPath = path.join(targetDir, "sela-report.json");
|
|
156
211
|
fs.writeFileSync(jsonPath, JSON.stringify({
|
|
157
|
-
generatedAt:
|
|
158
|
-
startedAt:
|
|
159
|
-
|
|
212
|
+
generatedAt: data.generatedAt,
|
|
213
|
+
startedAt: data.startedAt,
|
|
214
|
+
cwd: data.cwd,
|
|
215
|
+
stats: data.stats,
|
|
216
|
+
roi: data.roi,
|
|
217
|
+
historical_runs: data.historical_runs,
|
|
218
|
+
events: data.events,
|
|
160
219
|
}, null, 2), "utf8");
|
|
161
220
|
return htmlPath;
|
|
162
221
|
}
|
|
163
|
-
// ─────────────────────────────────────────────────────────────
|
|
164
|
-
// RENDERING
|
|
165
|
-
// ─────────────────────────────────────────────────────────────
|
|
166
|
-
renderHtml() {
|
|
167
|
-
const data = {
|
|
168
|
-
generatedAt: new Date().toISOString(),
|
|
169
|
-
startedAt: this.startedAt,
|
|
170
|
-
cwd: process.cwd(),
|
|
171
|
-
events: this.events,
|
|
172
|
-
stats: this.computeStats(),
|
|
173
|
-
};
|
|
174
|
-
return renderHtmlTemplate(data);
|
|
175
|
-
}
|
|
176
|
-
computeStats() {
|
|
177
|
-
let healed = 0, failed = 0, protectedCnt = 0;
|
|
178
|
-
let iframeCnt = 0, shadowCnt = 0;
|
|
179
|
-
for (const e of this.events) {
|
|
180
|
-
if (e.kind === "HEALED")
|
|
181
|
-
healed++;
|
|
182
|
-
else if (e.kind === "FAILED")
|
|
183
|
-
failed++;
|
|
184
|
-
else
|
|
185
|
-
protectedCnt++;
|
|
186
|
-
if (e.inIframe)
|
|
187
|
-
iframeCnt++;
|
|
188
|
-
if (e.inShadowDom)
|
|
189
|
-
shadowCnt++;
|
|
190
|
-
}
|
|
191
|
-
const timeSavedMin = healed * TIME_PER_HEAL +
|
|
192
|
-
protectedCnt * TIME_PER_PROTECT +
|
|
193
|
-
failed * TIME_PER_FAILED;
|
|
194
|
-
return {
|
|
195
|
-
total: this.events.length,
|
|
196
|
-
healed,
|
|
197
|
-
failed,
|
|
198
|
-
protectedCnt,
|
|
199
|
-
iframeCnt,
|
|
200
|
-
shadowCnt,
|
|
201
|
-
timeSavedMin,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
222
|
}
|
|
205
223
|
exports.HealReportService = HealReportService;
|
|
206
|
-
//
|
|
224
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
207
225
|
// MODULE-LEVEL SINGLETON
|
|
208
|
-
//
|
|
226
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
209
227
|
exports.sharedHealReport = new HealReportService();
|
|
210
|
-
//
|
|
211
|
-
// HTML
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
<
|
|
227
|
-
<
|
|
228
|
-
<
|
|
229
|
-
<
|
|
230
|
-
<
|
|
231
|
-
<
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
</
|
|
235
|
-
|
|
236
|
-
<
|
|
237
|
-
<div class="bg-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
<div class="brand
|
|
257
|
-
<div class="brand-
|
|
258
|
-
</
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
<
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
</div
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
<
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
<
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
<
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
<
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
<div
|
|
310
|
-
|
|
311
|
-
<
|
|
312
|
-
<
|
|
313
|
-
|
|
314
|
-
</
|
|
315
|
-
</
|
|
316
|
-
</
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
a
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
.
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
}
|
|
426
|
-
.exec-
|
|
427
|
-
|
|
428
|
-
.
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
}
|
|
432
|
-
.kpi
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
.kpi
|
|
442
|
-
.kpi-
|
|
443
|
-
.kpi-
|
|
444
|
-
.kpi-
|
|
445
|
-
.kpi.
|
|
446
|
-
.
|
|
447
|
-
.
|
|
448
|
-
.
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
.
|
|
452
|
-
.
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
}
|
|
456
|
-
.
|
|
457
|
-
.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
.
|
|
465
|
-
.
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
.
|
|
471
|
-
.chip
|
|
472
|
-
|
|
473
|
-
.
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
.
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
.
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
}
|
|
496
|
-
.
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
500
|
-
.
|
|
501
|
-
.
|
|
502
|
-
.
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
.
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
.
|
|
518
|
-
|
|
519
|
-
.
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
527
|
-
.
|
|
528
|
-
.
|
|
529
|
-
.
|
|
530
|
-
.
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
.
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
}
|
|
541
|
-
.
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
545
|
-
.
|
|
546
|
-
|
|
547
|
-
}
|
|
548
|
-
.
|
|
549
|
-
.
|
|
550
|
-
|
|
551
|
-
.
|
|
552
|
-
.
|
|
553
|
-
|
|
554
|
-
.
|
|
555
|
-
.
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
.
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
}
|
|
567
|
-
.
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
.
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
.
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
}
|
|
584
|
-
.
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
}
|
|
589
|
-
.
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
}
|
|
593
|
-
.
|
|
594
|
-
.
|
|
595
|
-
.
|
|
596
|
-
.
|
|
597
|
-
.
|
|
598
|
-
.
|
|
599
|
-
.
|
|
600
|
-
.
|
|
601
|
-
.
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
.
|
|
605
|
-
.
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
}
|
|
610
|
-
.
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
.
|
|
628
|
-
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
.
|
|
633
|
-
|
|
634
|
-
.
|
|
635
|
-
|
|
636
|
-
.
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
.
|
|
645
|
-
.
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
.
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
.
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
.
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
.
|
|
692
|
-
.
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
.
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
.
|
|
711
|
-
.
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
.
|
|
719
|
-
.
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
.
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
.
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
.
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
.
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
.
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
.
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
(
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
if
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
}
|
|
889
|
-
|
|
890
|
-
'<div class="
|
|
891
|
-
'<div class
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
'
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
);
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
'<div class="step">' +
|
|
973
|
-
'<span class="step-no">' + (i + 1) + '</span>' +
|
|
974
|
-
'<span class="step-text">' + esc(s) + '</span>' +
|
|
975
|
-
'</div>'
|
|
976
|
-
).join("") + '</div>';
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
function renderAuditor(a) {
|
|
980
|
-
if (!a) return '<div class="muted-block">The Intent Auditor was not consulted for this event (no AI fix attempted, or auditor unavailable).</div>';
|
|
981
|
-
const verdictCls = a.verdict.toLowerCase();
|
|
982
|
-
const hasPct = typeof a.confidence === "number" && isFinite(a.confidence);
|
|
983
|
-
const pct = hasPct ? Math.max(0, Math.min(100, a.confidence)) : 0;
|
|
984
|
-
const radius = 56;
|
|
985
|
-
const circ = 2 * Math.PI * radius;
|
|
986
|
-
const dash = hasPct ? (pct / 100) * circ : 0;
|
|
987
|
-
const adjustments = [];
|
|
988
|
-
if (a.penalty != null && a.penalty !== 0) adjustments.push("ancestry drift " + a.penalty);
|
|
989
|
-
if (a.bonus != null && a.bonus !== 0) adjustments.push("anchor +" + a.bonus);
|
|
990
|
-
const adjLine = adjustments.length
|
|
991
|
-
? '<div class="audit-row"><span class="label">Score adjustments</span><span class="value">raw ' + fmtPct(a.rawConfidence) + ' → ' + fmtPct(hasPct ? pct : undefined) + ' (' + adjustments.join(", ") + ')</span></div>'
|
|
992
|
-
: "";
|
|
993
|
-
return (
|
|
994
|
-
'<div class="auditor-grid">' +
|
|
995
|
-
'<div class="gauge ' + verdictCls + '">' +
|
|
996
|
-
'<svg viewBox="0 0 140 140">' +
|
|
997
|
-
'<circle class="track" cx="70" cy="70" r="' + radius + '"/>' +
|
|
998
|
-
'<circle class="bar" cx="70" cy="70" r="' + radius + '" ' +
|
|
999
|
-
'stroke-dasharray="' + dash.toFixed(2) + ' ' + circ.toFixed(2) + '"/>' +
|
|
1000
|
-
'</svg>' +
|
|
1001
|
-
'<div class="center">' +
|
|
1002
|
-
'<div class="pct">' + (hasPct ? pct + "%" : "n/a") + '</div>' +
|
|
1003
|
-
'<div class="pct-label">Consistency</div>' +
|
|
1004
|
-
'</div>' +
|
|
1005
|
-
'</div>' +
|
|
1006
|
-
'<div class="audit-detail">' +
|
|
1007
|
-
'<div class="audit-row"><span class="label">Verdict</span><span class="value"><span class="verdict-chip ' + verdictCls + '">' + a.verdict + '</span></span></div>' +
|
|
1008
|
-
(a.inversionType ? '<div class="audit-row"><span class="label">Inversion type</span><span class="value">' + esc(a.inversionType) + '</span></div>' : "") +
|
|
1009
|
-
adjLine +
|
|
1010
|
-
'<div class="audit-reason">' + esc(a.reason) + '</div>' +
|
|
1011
|
-
'</div>' +
|
|
1012
|
-
'</div>'
|
|
1013
|
-
);
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
// Parse "git diff --unified=N" output into structured hunks. Skips file
|
|
1017
|
-
// headers ("diff ", "index ", "--- ", "+++ ", rename/similarity lines) and
|
|
1018
|
-
// walks each "@@ -a,b +c,d @@" block, tracking real old/new line numbers
|
|
1019
|
-
// so the rendered diff shows correct line refs even when the AST mutation
|
|
1020
|
-
// landed several lines above the failure site (JumpToDef scenario).
|
|
1021
|
-
function parseUnifiedDiff(raw) {
|
|
1022
|
-
const out = [];
|
|
1023
|
-
if (!raw) return out;
|
|
1024
|
-
const rows = raw.split(/\r?\n/);
|
|
1025
|
-
let cur = null;
|
|
1026
|
-
let oldLn = 0, newLn = 0;
|
|
1027
|
-
for (const r of rows) {
|
|
1028
|
-
if (
|
|
1029
|
-
r.startsWith("diff ") ||
|
|
1030
|
-
r.startsWith("index ") ||
|
|
1031
|
-
r.startsWith("--- ") ||
|
|
1032
|
-
r.startsWith("+++ ") ||
|
|
1033
|
-
r.startsWith("new file mode") ||
|
|
1034
|
-
r.startsWith("deleted file mode") ||
|
|
1035
|
-
r.startsWith("similarity ") ||
|
|
1036
|
-
r.startsWith("rename ") ||
|
|
1037
|
-
r.startsWith("Binary files")
|
|
1038
|
-
) continue;
|
|
1039
|
-
const hunk = r.match(/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/);
|
|
1040
|
-
if (hunk) {
|
|
1041
|
-
if (cur) out.push(cur);
|
|
1042
|
-
cur = { header: r, lines: [] };
|
|
1043
|
-
oldLn = parseInt(hunk[1], 10);
|
|
1044
|
-
newLn = parseInt(hunk[3], 10);
|
|
1045
|
-
continue;
|
|
1046
|
-
}
|
|
1047
|
-
if (!cur) continue;
|
|
1048
|
-
// "" + truly empty trailing lines: ignore
|
|
1049
|
-
if (r.startsWith("\\")) continue;
|
|
1050
|
-
if (r.length === 0) continue;
|
|
1051
|
-
const head = r.charAt(0);
|
|
1052
|
-
const text = r.slice(1);
|
|
1053
|
-
if (head === "+") {
|
|
1054
|
-
cur.lines.push({ type: "add", text, ln: newLn });
|
|
1055
|
-
newLn++;
|
|
1056
|
-
} else if (head === "-") {
|
|
1057
|
-
cur.lines.push({ type: "del", text, ln: oldLn });
|
|
1058
|
-
oldLn++;
|
|
1059
|
-
} else if (head === " ") {
|
|
1060
|
-
cur.lines.push({ type: "ctx", text, ln: newLn });
|
|
1061
|
-
oldLn++; newLn++;
|
|
1062
|
-
}
|
|
1063
|
-
}
|
|
1064
|
-
if (cur) out.push(cur);
|
|
1065
|
-
return out;
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
function renderDiff(ev) {
|
|
1069
|
-
if (ev.kind !== "HEALED") {
|
|
1070
|
-
if (ev.kind === "PROTECTED") {
|
|
1071
|
-
return (
|
|
1072
|
-
'<div class="muted-block">No source code was modified. Sela blocked the AI-proposed change because the Intent Auditor flagged a semantic mismatch - your test file is untouched and the underlying behaviour change is still surfacing in CI.</div>'
|
|
1073
|
-
);
|
|
1074
|
-
}
|
|
1075
|
-
return '<div class="muted-block">No source code was modified - the AI could not propose a confident fix for this selector.</div>';
|
|
1076
|
-
}
|
|
1077
|
-
|
|
1078
|
-
const raw = ev.gitUnifiedDiff || "";
|
|
1079
|
-
const hunks = parseUnifiedDiff(raw);
|
|
1080
|
-
|
|
1081
|
-
let bodyHtml;
|
|
1082
|
-
let sourceTag;
|
|
1083
|
-
if (hunks.length > 0) {
|
|
1084
|
-
sourceTag = "native git diff";
|
|
1085
|
-
bodyHtml = hunks.map(h => {
|
|
1086
|
-
const hdr =
|
|
1087
|
-
'<div class="diff-line ctx">' +
|
|
1088
|
-
'<span class="gutter">@</span><span class="ln"></span><span class="code">' + esc(h.header) + '</span>' +
|
|
1089
|
-
'</div>';
|
|
1090
|
-
const body = h.lines.map(L => {
|
|
1091
|
-
const gutter = L.type === "add" ? "+" : L.type === "del" ? "-" : " ";
|
|
1092
|
-
return '<div class="diff-line ' + L.type + '">' +
|
|
1093
|
-
'<span class="gutter">' + gutter + '</span>' +
|
|
1094
|
-
'<span class="ln">' + L.ln + '</span>' +
|
|
1095
|
-
'<span class="code">' + esc(L.text) + '</span>' +
|
|
1096
|
-
'</div>';
|
|
1097
|
-
}).join("");
|
|
1098
|
-
return hdr + body;
|
|
1099
|
-
}).join("");
|
|
1100
|
-
} else {
|
|
1101
|
-
// Fallback when git diff capture was unavailable (no git, untracked
|
|
1102
|
-
// file, etc.). Render the single before/after pair the engine carried
|
|
1103
|
-
// over from the legacy code path.
|
|
1104
|
-
sourceTag = "fallback (no git diff captured)";
|
|
1105
|
-
const oldLine = ev.oldCodeLine || "";
|
|
1106
|
-
const newLine = ev.newCodeLine || "";
|
|
1107
|
-
bodyHtml =
|
|
1108
|
-
'<div class="diff-line del">' +
|
|
1109
|
-
'<span class="gutter">-</span><span class="ln">' + (ev.newLineNumber || "") + '</span><span class="code">' + esc(oldLine) + '</span>' +
|
|
1110
|
-
'</div>' +
|
|
1111
|
-
'<div class="diff-line add">' +
|
|
1112
|
-
'<span class="gutter">+</span><span class="ln">' + (ev.newLineNumber || "") + '</span><span class="code">' + esc(newLine) + '</span>' +
|
|
1113
|
-
'</div>';
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
return (
|
|
1117
|
-
'<div class="diff">' +
|
|
1118
|
-
'<div class="diff-head">' +
|
|
1119
|
-
'<span>' + esc(ev.sourceFile) + '</span>' +
|
|
1120
|
-
'<span class="muted">·</span>' +
|
|
1121
|
-
'<span>line ' + ev.newLineNumber + '</span>' +
|
|
1122
|
-
(ev.diffStrategy ? '<span class="muted">·</span><span>strategy: ' + esc(ev.diffStrategy) + '</span>' : "") +
|
|
1123
|
-
(ev.blastRadius != null ? '<span class="muted">·</span><span>blast radius: ' + ev.blastRadius + '</span>' : "") +
|
|
1124
|
-
'<span class="muted">·</span><span>' + sourceTag + '</span>' +
|
|
1125
|
-
'</div>' +
|
|
1126
|
-
'<div class="diff-body">' + bodyHtml + '</div>' +
|
|
1127
|
-
'</div>'
|
|
1128
|
-
);
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
function renderFailureContext(ev) {
|
|
1132
|
-
if (ev.kind !== "FAILED" || !ev.failureScreenshotPng) return "";
|
|
1133
|
-
return (
|
|
1134
|
-
'<div class="section">' +
|
|
1135
|
-
'<div class="section-title"><span class="marker"></span>Failure Context · Playwright Screenshot</div>' +
|
|
1136
|
-
'<div class="failure-img">' +
|
|
1137
|
-
'<img src="data:image/png;base64,' + ev.failureScreenshotPng + '" alt="Playwright failure screenshot" loading="lazy" />' +
|
|
1138
|
-
'<div class="img-meta">captured by SelaReporter at test failure · base64-embedded · survives Playwright temp cleanup</div>' +
|
|
1139
|
-
'</div>' +
|
|
1140
|
-
'</div>'
|
|
1141
|
-
);
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
function renderAlts(ev) {
|
|
1145
|
-
if (ev.kind !== "HEALED" || !ev.aiAlternatives || !ev.aiAlternatives.length) return "";
|
|
1146
|
-
return (
|
|
1147
|
-
'<div class="section">' +
|
|
1148
|
-
'<div class="section-title"><span class="marker"></span>Alternative selectors the AI considered</div>' +
|
|
1149
|
-
'<div class="alts">' +
|
|
1150
|
-
ev.aiAlternatives.map(a => '<div class="alt">' + esc(a) + '</div>').join("") +
|
|
1151
|
-
'</div>' +
|
|
1152
|
-
'</div>'
|
|
1153
|
-
);
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
function renderActions(ev) {
|
|
1157
|
-
const issueUrl = "https://github.com/anthropics/sela-core/issues/new?title=" +
|
|
1158
|
-
encodeURIComponent("[Sela Report] " + ev.kind + " - " + ev.stableId) +
|
|
1159
|
-
"&body=" + encodeURIComponent(
|
|
1160
|
-
"Stable ID: " + ev.stableId + "\n" +
|
|
1161
|
-
"File: " + ev.sourceFile + ":" + ev.sourceLine + "\n" +
|
|
1162
|
-
"Kind: " + ev.kind + "\n" +
|
|
1163
|
-
"Generated: " + DATA.generatedAt
|
|
1164
|
-
);
|
|
1165
|
-
|
|
1166
|
-
if (ev.kind === "HEALED") {
|
|
1167
|
-
return (
|
|
1168
|
-
'<div class="actions">' +
|
|
1169
|
-
'<button class="btn primary" data-action="keep" data-id="' + ev.stableId + '">✓ Keep changes</button>' +
|
|
1170
|
-
'<button class="btn danger" data-action="rollback" data-id="' + ev.stableId + '">↶ Rollback</button>' +
|
|
1171
|
-
'<a class="btn ghost" target="_blank" rel="noopener" href="' + issueUrl + '">⚑ Report issue</a>' +
|
|
1172
|
-
'</div>'
|
|
1173
|
-
);
|
|
1174
|
-
}
|
|
1175
|
-
if (ev.kind === "PROTECTED") {
|
|
1176
|
-
return (
|
|
1177
|
-
'<div class="actions">' +
|
|
1178
|
-
'<button class="btn primary" data-action="acknowledge" data-id="' + ev.stableId + '">✓ Acknowledge - I will investigate</button>' +
|
|
1179
|
-
'<a class="btn ghost" target="_blank" rel="noopener" href="' + issueUrl + '">⚑ File regression</a>' +
|
|
1180
|
-
'</div>'
|
|
1181
|
-
);
|
|
1182
|
-
}
|
|
1183
|
-
return (
|
|
1184
|
-
'<div class="actions">' +
|
|
1185
|
-
'<a class="btn primary" href="' + vscodeLink(ev.sourceFile, ev.sourceLine) + '">→ Open in editor</a>' +
|
|
1186
|
-
'<a class="btn ghost" target="_blank" rel="noopener" href="' + issueUrl + '">⚑ Report issue</a>' +
|
|
1187
|
-
'</div>'
|
|
1188
|
-
);
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
function renderCardBody(ev) {
|
|
1192
|
-
const blocks = [];
|
|
1193
|
-
|
|
1194
|
-
// 1. AI Reasoning (CoT)
|
|
1195
|
-
if (ev.kind !== "FAILED" || ev.aiExplanation) {
|
|
1196
|
-
const steps = ev.kind === "HEALED" ? ev.reasoningSteps :
|
|
1197
|
-
ev.kind === "PROTECTED" ? buildProtectedSteps(ev) :
|
|
1198
|
-
buildFailedSteps(ev);
|
|
1199
|
-
blocks.push(
|
|
1200
|
-
'<div class="section">' +
|
|
1201
|
-
'<div class="section-title"><span class="marker"></span>AI Reasoning · Chain of Thought</div>' +
|
|
1202
|
-
renderCoT(steps) +
|
|
1203
|
-
'</div>'
|
|
1204
|
-
);
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
// 2. Intent Auditor deep dive
|
|
1208
|
-
if (ev.auditor !== undefined) {
|
|
1209
|
-
blocks.push(
|
|
1210
|
-
'<div class="section">' +
|
|
1211
|
-
'<div class="section-title"><span class="marker"></span>Intent Auditor · Deep Dive</div>' +
|
|
1212
|
-
renderAuditor(ev.auditor) +
|
|
1213
|
-
'</div>'
|
|
1214
|
-
);
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
// 3. Visual DNA Evidence
|
|
1218
|
-
blocks.push(
|
|
1219
|
-
'<div class="section">' +
|
|
1220
|
-
'<div class="section-title"><span class="marker"></span>Visual DNA Evidence · Old vs New</div>' +
|
|
1221
|
-
renderDnaTable(ev.dnaBefore || null, ev.dnaAfter || null) +
|
|
1222
|
-
'</div>'
|
|
1223
|
-
);
|
|
1224
|
-
|
|
1225
|
-
// 4. Diff
|
|
1226
|
-
blocks.push(
|
|
1227
|
-
'<div class="section">' +
|
|
1228
|
-
'<div class="section-title"><span class="marker"></span>Source Code Diff</div>' +
|
|
1229
|
-
renderDiff(ev) +
|
|
1230
|
-
'</div>'
|
|
1231
|
-
);
|
|
1232
|
-
|
|
1233
|
-
// 5. Failure Context - Playwright screenshot (FAILED only)
|
|
1234
|
-
blocks.push(renderFailureContext(ev));
|
|
1235
|
-
|
|
1236
|
-
// 6. Alternatives
|
|
1237
|
-
blocks.push(renderAlts(ev));
|
|
1238
|
-
|
|
1239
|
-
// 6. Actions
|
|
1240
|
-
blocks.push(renderActions(ev));
|
|
1241
|
-
|
|
1242
|
-
return blocks.join("");
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
function buildProtectedSteps(ev) {
|
|
1246
|
-
const steps = [];
|
|
1247
|
-
steps.push("AI proposed a replacement selector for the broken locator.");
|
|
1248
|
-
if (ev.aiExplanation) steps.push("Reasoning given: " + ev.aiExplanation);
|
|
1249
|
-
if (ev.candidateNewSelector) steps.push("Candidate selector resolved live - but its semantics changed.");
|
|
1250
|
-
if (ev.auditor) steps.push("Intent Auditor verdict: " + ev.auditor.verdict + " · " + ev.auditor.reason);
|
|
1251
|
-
steps.push("Sela blocked the auto-fix. Source code untouched. Investigate this as a real regression.");
|
|
1252
|
-
return steps;
|
|
1253
|
-
}
|
|
1254
|
-
function buildFailedSteps(ev) {
|
|
1255
|
-
const steps = [];
|
|
1256
|
-
steps.push("Locator failed to resolve at runtime - Sela attempted to heal it.");
|
|
1257
|
-
if (ev.aiExplanation) steps.push("AI response: " + ev.aiExplanation);
|
|
1258
|
-
steps.push("No confident replacement was produced. Reason: " + (ev.reason || "unknown"));
|
|
1259
|
-
steps.push("Open the file directly in your editor to inspect the original locator.");
|
|
1260
|
-
return steps;
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
function eventCard(ev) {
|
|
1264
|
-
const link = vscodeLink(ev.sourceFile, ev.sourceLine);
|
|
1265
|
-
const cardCls = ev.kind === "HEALED" ? "healed" : ev.kind === "PROTECTED" ? "protected" : "failed";
|
|
1266
|
-
return (
|
|
1267
|
-
'<article class="card ' + cardCls + '" data-kind="' + ev.kind + '" data-haystack="' + esc((ev.oldSelector + " " + (ev.newSelector || "") + " " + ev.sourceFile + " " + (ev.testTitle || "")).toLowerCase()) + '">' +
|
|
1268
|
-
'<div class="card-head">' +
|
|
1269
|
-
badge(ev.kind) +
|
|
1270
|
-
'<div class="card-title">' +
|
|
1271
|
-
'<div class="selector">' + esc(ev.kind === "HEALED" ? (ev.newSelector || ev.oldSelector) : ev.oldSelector) + '</div>' +
|
|
1272
|
-
'<div class="meta-line">' +
|
|
1273
|
-
(ev.testTitle ? esc(ev.testTitle) + ' · ' : "") +
|
|
1274
|
-
'<a href="' + link + '">' + esc(ev.sourceFile) + ':' + ev.sourceLine + '</a>' +
|
|
1275
|
-
contextTags(ev) +
|
|
1276
|
-
' · ' + fmtTime(ev.timestamp) +
|
|
1277
|
-
'</div>' +
|
|
1278
|
-
'</div>' +
|
|
1279
|
-
'<svg class="chev" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="5 3 11 8 5 13"/></svg>' +
|
|
1280
|
-
'</div>' +
|
|
1281
|
-
'<div class="card-body">' + renderCardBody(ev) + '</div>' +
|
|
1282
|
-
'</article>'
|
|
1283
|
-
);
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
// ── Mount ─────────────────────────────────────────────────────
|
|
1287
|
-
const $events = $("#events");
|
|
1288
|
-
if (DATA.events.length === 0) {
|
|
1289
|
-
$events.innerHTML = '<div class="muted-block">Sela was idle on this run - no broken selectors encountered.</div>';
|
|
1290
|
-
} else {
|
|
1291
|
-
$events.innerHTML = DATA.events.map(eventCard).join("");
|
|
1292
|
-
|
|
1293
|
-
// Auto-open the first PROTECTED card to surface bugs immediately.
|
|
1294
|
-
const firstProtected = $events.querySelector('.card.protected');
|
|
1295
|
-
if (firstProtected) firstProtected.classList.add("open");
|
|
1296
|
-
else {
|
|
1297
|
-
const firstCard = $events.querySelector('.card');
|
|
1298
|
-
if (firstCard) firstCard.classList.add("open");
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
// ── Interactions ──────────────────────────────────────────────
|
|
1303
|
-
function showToast(text) {
|
|
1304
|
-
const t = $("#toast");
|
|
1305
|
-
t.textContent = text;
|
|
1306
|
-
t.classList.remove("hidden");
|
|
1307
|
-
clearTimeout(showToast._t);
|
|
1308
|
-
showToast._t = setTimeout(() => t.classList.add("hidden"), 2400);
|
|
1309
|
-
}
|
|
1310
|
-
|
|
1311
|
-
$events.addEventListener("click", (e) => {
|
|
1312
|
-
const head = e.target.closest(".card-head");
|
|
1313
|
-
if (head) {
|
|
1314
|
-
const card = head.parentElement;
|
|
1315
|
-
card.classList.toggle("open");
|
|
1316
|
-
return;
|
|
1317
|
-
}
|
|
1318
|
-
const btn = e.target.closest("button[data-action]");
|
|
1319
|
-
if (!btn) return;
|
|
1320
|
-
const action = btn.dataset.action;
|
|
1321
|
-
const id = btn.dataset.id;
|
|
1322
|
-
if (action === "keep" || action === "acknowledge") {
|
|
1323
|
-
btn.dataset.state = "approved";
|
|
1324
|
-
btn.textContent = action === "keep" ? "✓ Approved" : "✓ Acknowledged";
|
|
1325
|
-
showToast("Saved locally - Sela noted your decision for " + id);
|
|
1326
|
-
} else if (action === "rollback") {
|
|
1327
|
-
const cmd = "npx sela dna refactor --rollback " + id;
|
|
1328
|
-
navigator.clipboard?.writeText(cmd).catch(() => {});
|
|
1329
|
-
showToast("Rollback command copied: " + cmd);
|
|
1330
|
-
}
|
|
1331
|
-
});
|
|
1332
|
-
|
|
1333
|
-
// ── Filters + Search ──────────────────────────────────────────
|
|
1334
|
-
let activeFilter = "ALL";
|
|
1335
|
-
let searchTerm = "";
|
|
1336
|
-
function applyFilters() {
|
|
1337
|
-
let visible = 0;
|
|
1338
|
-
$$(".card", $events).forEach(card => {
|
|
1339
|
-
const matchKind = activeFilter === "ALL" || card.dataset.kind === activeFilter;
|
|
1340
|
-
const matchSearch = !searchTerm || card.dataset.haystack.includes(searchTerm);
|
|
1341
|
-
const show = matchKind && matchSearch;
|
|
1342
|
-
card.style.display = show ? "" : "none";
|
|
1343
|
-
if (show) visible++;
|
|
1344
|
-
});
|
|
1345
|
-
$("#empty-state").classList.toggle("hidden", visible !== 0 || DATA.events.length === 0);
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
$("#filter-group").addEventListener("click", e => {
|
|
1349
|
-
const chip = e.target.closest(".chip");
|
|
1350
|
-
if (!chip) return;
|
|
1351
|
-
$$(".chip", $("#filter-group")).forEach(c => c.classList.remove("active"));
|
|
1352
|
-
chip.classList.add("active");
|
|
1353
|
-
activeFilter = chip.dataset.filter;
|
|
1354
|
-
applyFilters();
|
|
1355
|
-
});
|
|
1356
|
-
$("#search").addEventListener("input", e => {
|
|
1357
|
-
searchTerm = e.target.value.toLowerCase();
|
|
1358
|
-
applyFilters();
|
|
1359
|
-
});
|
|
1360
|
-
|
|
1361
|
-
// ── Keyboard niceties ─────────────────────────────────────────
|
|
1362
|
-
document.addEventListener("keydown", (e) => {
|
|
1363
|
-
if (e.key === "/" && document.activeElement !== $("#search")) {
|
|
1364
|
-
e.preventDefault();
|
|
1365
|
-
$("#search").focus();
|
|
1366
|
-
}
|
|
1367
|
-
});
|
|
1368
|
-
})();
|
|
1369
|
-
`;
|
|
228
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
229
|
+
// HTML RENDERER
|
|
230
|
+
//
|
|
231
|
+
// Uses plain string concatenation throughout — no TypeScript template literals
|
|
232
|
+
// that could misinterpret the embedded CSS/JS content.
|
|
233
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
234
|
+
function renderHtml(data) {
|
|
235
|
+
// Embed the JSON payload. Escape </script> sequences and lone < so the
|
|
236
|
+
// string is safe inside a <script> block regardless of content.
|
|
237
|
+
const payload = JSON.stringify(data)
|
|
238
|
+
.replace(/</g, "\\u003c")
|
|
239
|
+
.replace(/\//g, "\\/");
|
|
240
|
+
return ("<!DOCTYPE html>\n" +
|
|
241
|
+
'<html lang="en">\n' +
|
|
242
|
+
"<head>\n" +
|
|
243
|
+
'<meta charset="UTF-8"/>\n' +
|
|
244
|
+
'<meta name="viewport" content="width=device-width,initial-scale=1.0"/>\n' +
|
|
245
|
+
"<title>Sela Insights \u2014 Healing Report</title>\n" +
|
|
246
|
+
'<link rel="preconnect" href="https://fonts.googleapis.com"/>\n' +
|
|
247
|
+
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>\n' +
|
|
248
|
+
'<link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500;600&family=Geist:wght@300;400;500;600;700&display=swap" rel="stylesheet"/>\n' +
|
|
249
|
+
'<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js"><\/script>\n' +
|
|
250
|
+
"<style>\n" +
|
|
251
|
+
REPORT_CSS +
|
|
252
|
+
"\n</style>\n" +
|
|
253
|
+
"</head>\n" +
|
|
254
|
+
"<body>\n" +
|
|
255
|
+
'<div class="bg-noise"></div>\n' +
|
|
256
|
+
'<div class="bg-grid"></div>\n' +
|
|
257
|
+
buildTopbar() +
|
|
258
|
+
buildOverviewView() +
|
|
259
|
+
buildDeepDiveView() +
|
|
260
|
+
buildFooter() +
|
|
261
|
+
'<div id="toast" class="toast hidden"></div>\n' +
|
|
262
|
+
"<script>window.__SELA_DATA__ = " +
|
|
263
|
+
payload +
|
|
264
|
+
";<\/script>\n" +
|
|
265
|
+
"<script>\n" +
|
|
266
|
+
REPORT_JS +
|
|
267
|
+
"\n<\/script>\n" +
|
|
268
|
+
"</body>\n" +
|
|
269
|
+
"</html>");
|
|
270
|
+
}
|
|
271
|
+
function buildTopbar() {
|
|
272
|
+
return ('<nav class="topbar">\n' +
|
|
273
|
+
' <div class="wrap topbar-inner">\n' +
|
|
274
|
+
' <div class="brand">\n' +
|
|
275
|
+
' <div class="brand-hex"></div>\n' +
|
|
276
|
+
' <span class="brand-name">Sela</span>\n' +
|
|
277
|
+
' <span class="brand-sep">/</span>\n' +
|
|
278
|
+
' <span class="brand-sub">Healing Report</span>\n' +
|
|
279
|
+
" </div>\n" +
|
|
280
|
+
' <div style="display:flex;align-items:center;gap:12px">\n' +
|
|
281
|
+
' <div class="view-toggle">\n' +
|
|
282
|
+
' <button class="vt-btn active" id="btn-overview" onclick="setView(\'overview\')">Overview</button>\n' +
|
|
283
|
+
' <button class="vt-btn" id="btn-deepdive" onclick="setView(\'deepdive\')">Deep Dive</button>\n' +
|
|
284
|
+
" </div>\n" +
|
|
285
|
+
' <div class="topbar-meta">\n' +
|
|
286
|
+
' <div class="topbar-pill live"><span class="dot"></span>Run complete</div>\n' +
|
|
287
|
+
" </div>\n" +
|
|
288
|
+
" </div>\n" +
|
|
289
|
+
" </div>\n" +
|
|
290
|
+
"</nav>\n");
|
|
291
|
+
}
|
|
292
|
+
function buildOverviewView() {
|
|
293
|
+
return ('<div id="view-overview">\n' +
|
|
294
|
+
'<div class="wrap">\n' +
|
|
295
|
+
' <div class="hero">\n' +
|
|
296
|
+
' <div class="exec-row">\n' +
|
|
297
|
+
" <div>\n" +
|
|
298
|
+
' <div class="exec-eyebrow"><span class="ei"></span>Sela Intelligence Run<span class="ei"></span></div>\n' +
|
|
299
|
+
' <h1 class="exec-h1" id="exec-h1"></h1>\n' +
|
|
300
|
+
' <p class="exec-sub" id="exec-sub"></p>\n' +
|
|
301
|
+
" </div>\n" +
|
|
302
|
+
' <div style="text-align:right;font-size:12px;color:var(--text3)">\n' +
|
|
303
|
+
' <div id="meta-gen" style="margin-bottom:3px"></div>\n' +
|
|
304
|
+
' <div id="meta-cwd" style="font-family:var(--mono);font-size:11px;word-break:break-all;max-width:280px"></div>\n' +
|
|
305
|
+
" </div>\n" +
|
|
306
|
+
" </div>\n" +
|
|
307
|
+
' <div class="kpi-row">\n' +
|
|
308
|
+
' <div class="kpi k-time"><div class="kpi-accent"></div><div class="kpi-l">Time saved</div><div class="kpi-v" id="kpi-time"></div><div class="kpi-f">estimated dev time</div></div>\n' +
|
|
309
|
+
' <div class="kpi k-healed"><div class="kpi-accent"></div><div class="kpi-l">Healed</div><div class="kpi-v" id="kpi-healed"></div><div class="kpi-f">auto-fixed & verified</div></div>\n' +
|
|
310
|
+
' <div class="kpi k-prot"><div class="kpi-accent"></div><div class="kpi-l">Protected</div><div class="kpi-v" id="kpi-prot"></div><div class="kpi-f">regressions blocked</div></div>\n' +
|
|
311
|
+
' <div class="kpi k-fail"><div class="kpi-accent"></div><div class="kpi-l">Failed</div><div class="kpi-v" id="kpi-fail"></div><div class="kpi-f">needs your eyes</div></div>\n' +
|
|
312
|
+
" </div>\n" +
|
|
313
|
+
' <div class="charts-row">\n' +
|
|
314
|
+
' <div class="chart-card">\n' +
|
|
315
|
+
' <div class="chart-label">Run Distribution</div>\n' +
|
|
316
|
+
' <div class="donut-wrap">\n' +
|
|
317
|
+
' <canvas id="donut-chart"></canvas>\n' +
|
|
318
|
+
' <div class="donut-center"><div class="donut-total" id="donut-total"></div><div class="donut-total-l">Events</div></div>\n' +
|
|
319
|
+
" </div>\n" +
|
|
320
|
+
' <div class="donut-legend" id="donut-legend"></div>\n' +
|
|
321
|
+
" </div>\n" +
|
|
322
|
+
' <div class="chart-card">\n' +
|
|
323
|
+
' <div class="trend-header">\n' +
|
|
324
|
+
' <div class="chart-label" style="margin-bottom:0">Sela Impact \u2014 Last 14 Runs</div>\n' +
|
|
325
|
+
' <div class="trend-badge" id="trend-badge"></div>\n' +
|
|
326
|
+
" </div>\n" +
|
|
327
|
+
' <div class="trend-canvas-wrap"><canvas id="trend-chart"></canvas></div>\n' +
|
|
328
|
+
' <div style="margin-top:16px">\n' +
|
|
329
|
+
' <div class="chart-label">Recent Runs</div>\n' +
|
|
330
|
+
' <div id="hist-table">\n' +
|
|
331
|
+
' <div class="hist-row hdr">\n' +
|
|
332
|
+
' <div class="hc">Run</div><div class="hc">Healed</div><div class="hc">Protected</div><div class="hc">Failed</div><div class="hc">Confidence</div><div class="hc">Time Saved</div>\n' +
|
|
333
|
+
" </div>\n" +
|
|
334
|
+
" </div>\n" +
|
|
335
|
+
" </div>\n" +
|
|
336
|
+
" </div>\n" +
|
|
337
|
+
" </div>\n" +
|
|
338
|
+
' <div style="margin-bottom:32px;display:flex;gap:10px;flex-wrap:wrap">\n' +
|
|
339
|
+
' <button class="btn btn-primary" onclick="setView(\'deepdive\')">→ Inspect all events</button>\n' +
|
|
340
|
+
' <button class="btn" onclick="copyJson()">⎘ Copy JSON</button>\n' +
|
|
341
|
+
" </div>\n" +
|
|
342
|
+
" </div>\n" +
|
|
343
|
+
"</div>\n" +
|
|
344
|
+
"</div>\n");
|
|
345
|
+
}
|
|
346
|
+
function buildDeepDiveView() {
|
|
347
|
+
return ('<div id="view-deepdive">\n' +
|
|
348
|
+
'<div class="wrap" style="padding-top:24px">\n' +
|
|
349
|
+
' <div class="section-hdr">\n' +
|
|
350
|
+
" <h2>Engineering Deep Dive</h2>\n" +
|
|
351
|
+
' <div class="sh-count" id="dd-count"></div>\n' +
|
|
352
|
+
' <div class="sh-line"></div>\n' +
|
|
353
|
+
' <button class="btn" onclick="setView(\'overview\')" style="font-size:11.5px;padding:4px 10px">← Overview</button>\n' +
|
|
354
|
+
" </div>\n" +
|
|
355
|
+
' <div class="filter-bar">\n' +
|
|
356
|
+
' <div class="fb-chips">\n' +
|
|
357
|
+
' <div class="chip c-all on" data-filter="ALL"><span>All</span></div>\n' +
|
|
358
|
+
' <div class="chip c-heal" data-filter="HEALED"><span class="chip-dot"></span>Healed</div>\n' +
|
|
359
|
+
' <div class="chip c-prot" data-filter="PROTECTED"><span class="chip-dot"></span>Protected \xb7 Bug Caught</div>\n' +
|
|
360
|
+
' <div class="chip c-fail" data-filter="FAILED"><span class="chip-dot"></span>Failed</div>\n' +
|
|
361
|
+
" </div>\n" +
|
|
362
|
+
' <div class="fb-search">\n' +
|
|
363
|
+
' <svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8"><circle cx="7" cy="7" r="5"/><path d="M11 11l3 3"/><\/svg>\n' +
|
|
364
|
+
' <input type="text" id="search-input" placeholder="selector, file, test name\u2026"/>\n' +
|
|
365
|
+
" </div>\n" +
|
|
366
|
+
" </div>\n" +
|
|
367
|
+
' <div class="events" id="events-list"></div>\n' +
|
|
368
|
+
' <div class="empty-state hidden" id="empty-state">\n' +
|
|
369
|
+
' <div class="es-icon">\u2205</div>\n' +
|
|
370
|
+
' <div class="es-title">Nothing matches</div>\n' +
|
|
371
|
+
" <div>Try a different filter or clear the search.</div>\n" +
|
|
372
|
+
" </div>\n" +
|
|
373
|
+
"</div>\n" +
|
|
374
|
+
"</div>\n");
|
|
375
|
+
}
|
|
376
|
+
function buildFooter() {
|
|
377
|
+
return ('<footer class="footer">\n' +
|
|
378
|
+
' <div class="wrap footer-inner">\n' +
|
|
379
|
+
' <div>Generated by <strong>sela-core</strong> \xb7 <span id="footer-cwd" style="font-family:var(--mono);font-size:11px"></span></div>\n' +
|
|
380
|
+
" <div>Sela is a transparent pair-programmer. Every decision is yours to keep, roll back, or report.</div>\n" +
|
|
381
|
+
" </div>\n" +
|
|
382
|
+
"</footer>\n");
|
|
383
|
+
}
|
|
384
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
385
|
+
// CSS — stored as a plain string constant (no backtick template literal)
|
|
386
|
+
// so TypeScript has no chance to misparse it.
|
|
387
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
388
|
+
/* eslint-disable prettier/prettier */
|
|
389
|
+
const REPORT_CSS = [
|
|
390
|
+
"*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}",
|
|
391
|
+
":root{",
|
|
392
|
+
" --bg0:#060709;--bg1:#0b0d12;--bg2:#10131a;--bg3:#161923;--bg4:#1c2030;",
|
|
393
|
+
" --line:#1e2336;--line2:#262d42;--line3:#2e3650;",
|
|
394
|
+
" --text:#dde2f0;--text2:#8b93b0;--text3:#5a6180;",
|
|
395
|
+
" --cyan:#00d4ff;--cyan-dim:rgba(0,212,255,.12);--cyan-glow:rgba(0,212,255,.25);",
|
|
396
|
+
" --purple:#a259ff;--purple-dim:rgba(162,89,255,.12);--purple-glow:rgba(162,89,255,.25);",
|
|
397
|
+
" --red:#ff4d6a;--red-dim:rgba(255,77,106,.12);--red-glow:rgba(255,77,106,.25);",
|
|
398
|
+
" --green:#00e5a0;--green-dim:rgba(0,229,160,.1);",
|
|
399
|
+
" --amber:#ffb84d;--amber-dim:rgba(255,184,77,.1);",
|
|
400
|
+
" --radius:10px;--radius-lg:14px;--radius-xl:20px;",
|
|
401
|
+
" --mono:'Geist Mono',ui-monospace,monospace;",
|
|
402
|
+
" --sans:'Geist',system-ui,sans-serif;",
|
|
403
|
+
"}",
|
|
404
|
+
"html{background:var(--bg0);color:var(--text);font-family:var(--sans);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased}",
|
|
405
|
+
"body{min-height:100vh;overflow-x:hidden}",
|
|
406
|
+
"a{color:var(--cyan);text-decoration:none}",
|
|
407
|
+
"button{font-family:var(--sans);cursor:pointer}",
|
|
408
|
+
"input{font-family:var(--sans)}",
|
|
409
|
+
"::-webkit-scrollbar{width:6px;height:6px}",
|
|
410
|
+
"::-webkit-scrollbar-track{background:var(--bg1)}",
|
|
411
|
+
"::-webkit-scrollbar-thumb{background:var(--line3);border-radius:3px}",
|
|
412
|
+
".bg-noise{",
|
|
413
|
+
" position:fixed;inset:0;z-index:-2;",
|
|
414
|
+
" background:",
|
|
415
|
+
" radial-gradient(ellipse 80% 50% at 10% 10%,rgba(0,212,255,.07) 0%,transparent 60%),",
|
|
416
|
+
" radial-gradient(ellipse 60% 40% at 90% 20%,rgba(162,89,255,.08) 0%,transparent 60%),",
|
|
417
|
+
" radial-gradient(ellipse 50% 60% at 50% 90%,rgba(162,89,255,.05) 0%,transparent 60%),",
|
|
418
|
+
" var(--bg0);",
|
|
419
|
+
"}",
|
|
420
|
+
".bg-grid{",
|
|
421
|
+
" position:fixed;inset:0;z-index:-1;pointer-events:none;",
|
|
422
|
+
" background-image:linear-gradient(rgba(255,255,255,.018) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.018) 1px,transparent 1px);",
|
|
423
|
+
" background-size:40px 40px;",
|
|
424
|
+
" mask-image:radial-gradient(ellipse at 50% 0%,black 0%,transparent 65%);",
|
|
425
|
+
"}",
|
|
426
|
+
".wrap{max-width:1320px;margin:0 auto;padding:0 24px}",
|
|
427
|
+
".topbar{position:sticky;top:0;z-index:100;background:rgba(6,7,9,.85);backdrop-filter:blur(16px) saturate(1.4);border-bottom:1px solid var(--line);height:52px;}",
|
|
428
|
+
".topbar-inner{height:52px;display:flex;align-items:center;justify-content:space-between}",
|
|
429
|
+
".brand{display:flex;align-items:center;gap:10px}",
|
|
430
|
+
".brand-hex{width:28px;height:28px;background:linear-gradient(135deg,rgba(0,212,255,.2),rgba(162,89,255,.2));border:1px solid rgba(0,212,255,.3);clip-path:polygon(50% 0%,100% 25%,100% 75%,50% 100%,0% 75%,0% 25%);position:relative;}",
|
|
431
|
+
".brand-hex::after{content:'';position:absolute;inset:4px;background:linear-gradient(135deg,var(--cyan),var(--purple));clip-path:polygon(50% 0%,100% 25%,100% 75%,50% 100%,0% 75%,0% 25%);opacity:.7;}",
|
|
432
|
+
".brand-name{font-weight:600;font-size:14px;letter-spacing:-.2px}",
|
|
433
|
+
".brand-sep{color:var(--text3);font-size:13px}",
|
|
434
|
+
".brand-sub{font-size:12px;color:var(--text3);letter-spacing:.6px;text-transform:uppercase}",
|
|
435
|
+
".topbar-meta{font-size:12px;color:var(--text3);display:flex;align-items:center;gap:16px}",
|
|
436
|
+
".topbar-pill{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border-radius:999px;font-size:11px;border:1px solid var(--line2);color:var(--text3);}",
|
|
437
|
+
".topbar-pill .dot{width:5px;height:5px;border-radius:50%}",
|
|
438
|
+
".topbar-pill.live .dot{background:var(--green);box-shadow:0 0 6px var(--green);animation:blink 2s ease-in-out infinite}",
|
|
439
|
+
"@keyframes blink{0%,100%{opacity:1}50%{opacity:.4}}",
|
|
440
|
+
".view-toggle{display:flex;gap:2px;padding:3px;background:var(--bg2);border:1px solid var(--line);border-radius:var(--radius)}",
|
|
441
|
+
".vt-btn{padding:5px 14px;border-radius:7px;font-size:12px;font-weight:500;border:none;background:transparent;color:var(--text3);transition:all .2s ease}",
|
|
442
|
+
".vt-btn.active{background:var(--bg4);color:var(--text);box-shadow:0 1px 3px rgba(0,0,0,.4)}",
|
|
443
|
+
".hero{padding:32px 0 0}",
|
|
444
|
+
".exec-row{display:grid;grid-template-columns:1fr auto;gap:32px;align-items:start;padding-bottom:28px;border-bottom:1px solid var(--line);margin-bottom:28px}",
|
|
445
|
+
"@media(max-width:800px){.exec-row{grid-template-columns:1fr}}",
|
|
446
|
+
".exec-eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;letter-spacing:1.8px;text-transform:uppercase;color:var(--cyan);margin-bottom:10px}",
|
|
447
|
+
".exec-eyebrow .ei{width:14px;height:1px;background:var(--cyan);opacity:.6}",
|
|
448
|
+
".exec-h1{font-size:32px;font-weight:700;line-height:1.1;letter-spacing:-.6px;background:linear-gradient(135deg,#fff 0%,rgba(221,226,240,.7) 100%);-webkit-background-clip:text;background-clip:text;color:transparent;margin-bottom:8px}",
|
|
449
|
+
".exec-sub{font-size:14px;color:var(--text2);max-width:52ch;line-height:1.6}",
|
|
450
|
+
".kpi-row{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:28px}",
|
|
451
|
+
"@media(max-width:900px){.kpi-row{grid-template-columns:repeat(2,1fr)}}",
|
|
452
|
+
".kpi{background:var(--bg2);border:1px solid var(--line);border-radius:var(--radius-lg);padding:16px 18px;position:relative;overflow:hidden;transition:border-color .25s ease,transform .2s ease}",
|
|
453
|
+
".kpi::before{content:'';position:absolute;inset:0;opacity:.5;background:linear-gradient(180deg,rgba(255,255,255,.03) 0%,transparent 100%);pointer-events:none}",
|
|
454
|
+
".kpi:hover{transform:translateY(-2px);border-color:var(--line3)}",
|
|
455
|
+
".kpi-l{font-size:10.5px;letter-spacing:1.2px;text-transform:uppercase;color:var(--text3);margin-bottom:6px}",
|
|
456
|
+
".kpi-v{font-size:28px;font-weight:700;line-height:1;font-variant-numeric:tabular-nums;margin-bottom:4px}",
|
|
457
|
+
".kpi-f{font-size:11.5px;color:var(--text3)}",
|
|
458
|
+
".kpi-accent{position:absolute;top:0;right:0;width:3px;height:100%;border-radius:0 var(--radius-lg) var(--radius-lg) 0}",
|
|
459
|
+
".kpi.k-time .kpi-v{background:linear-gradient(135deg,var(--cyan),var(--purple));-webkit-background-clip:text;background-clip:text;color:transparent}",
|
|
460
|
+
".kpi.k-time .kpi-accent{background:linear-gradient(180deg,var(--cyan),var(--purple))}",
|
|
461
|
+
".kpi.k-healed .kpi-v{color:var(--cyan)}.kpi.k-healed .kpi-accent{background:var(--cyan)}",
|
|
462
|
+
".kpi.k-prot .kpi-v{color:var(--purple)}.kpi.k-prot .kpi-accent{background:var(--purple)}",
|
|
463
|
+
".kpi.k-fail .kpi-v{color:var(--red)}.kpi.k-fail .kpi-accent{background:var(--red)}",
|
|
464
|
+
".charts-row{display:grid;grid-template-columns:280px 1fr;gap:18px;margin-bottom:32px}",
|
|
465
|
+
"@media(max-width:900px){.charts-row{grid-template-columns:1fr}}",
|
|
466
|
+
".chart-card{background:var(--bg2);border:1px solid var(--line);border-radius:var(--radius-lg);padding:20px}",
|
|
467
|
+
".chart-label{font-size:10.5px;letter-spacing:1.2px;text-transform:uppercase;color:var(--text3);margin-bottom:14px;display:flex;align-items:center;gap:8px}",
|
|
468
|
+
".chart-label::after{content:'';flex:1;height:1px;background:var(--line)}",
|
|
469
|
+
".donut-wrap{position:relative;width:160px;height:160px;margin:0 auto 16px}",
|
|
470
|
+
".donut-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none}",
|
|
471
|
+
".donut-total{font-size:28px;font-weight:700;line-height:1}",
|
|
472
|
+
".donut-total-l{font-size:10px;letter-spacing:1px;text-transform:uppercase;color:var(--text3);margin-top:2px}",
|
|
473
|
+
".donut-legend{display:flex;flex-direction:column;gap:7px}",
|
|
474
|
+
".dl-row{display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:7px;border:1px solid transparent;cursor:pointer;transition:all .2s ease}",
|
|
475
|
+
".dl-row:hover,.dl-row.active{border-color:var(--line3);background:var(--bg3)}",
|
|
476
|
+
".dl-swatch{width:8px;height:8px;border-radius:2px;flex-shrink:0}",
|
|
477
|
+
".dl-name{font-size:12.5px;color:var(--text2);flex:1}",
|
|
478
|
+
".dl-val{font-size:12.5px;font-weight:600;font-variant-numeric:tabular-nums}",
|
|
479
|
+
".dl-pct{font-size:11px;color:var(--text3);margin-left:4px}",
|
|
480
|
+
".trend-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}",
|
|
481
|
+
".trend-badge{font-size:11px;font-weight:600;padding:3px 9px;border-radius:999px;background:var(--green-dim);color:var(--green);border:1px solid rgba(0,229,160,.25)}",
|
|
482
|
+
".trend-canvas-wrap{position:relative;width:100%;height:200px}",
|
|
483
|
+
".section-hdr{display:flex;align-items:center;gap:10px;margin-bottom:14px}",
|
|
484
|
+
".section-hdr h2{font-size:13px;font-weight:600;letter-spacing:.4px}",
|
|
485
|
+
".section-hdr .sh-count{font-size:11px;padding:2px 8px;border-radius:999px;background:var(--bg3);color:var(--text3);border:1px solid var(--line);font-variant-numeric:tabular-nums}",
|
|
486
|
+
".section-hdr .sh-line{flex:1;height:1px;background:var(--line)}",
|
|
487
|
+
".filter-bar{display:flex;align-items:center;gap:10px;margin-bottom:16px;flex-wrap:wrap}",
|
|
488
|
+
".fb-chips{display:flex;gap:6px;flex-wrap:wrap}",
|
|
489
|
+
".chip{display:inline-flex;align-items:center;gap:5px;padding:5px 12px;border-radius:999px;font-size:12px;font-weight:500;border:1px solid var(--line2);background:var(--bg2);color:var(--text3);cursor:pointer;transition:all .2s ease}",
|
|
490
|
+
".chip:hover{color:var(--text);border-color:var(--line3)}",
|
|
491
|
+
".chip.c-all.on{border-color:rgba(255,255,255,.3);color:#fff;background:var(--bg3)}",
|
|
492
|
+
".chip.c-heal.on{border-color:var(--cyan);color:var(--cyan);background:var(--cyan-dim)}",
|
|
493
|
+
".chip.c-prot.on{border-color:var(--purple);color:var(--purple);background:var(--purple-dim)}",
|
|
494
|
+
".chip.c-fail.on{border-color:var(--red);color:var(--red);background:var(--red-dim)}",
|
|
495
|
+
".chip .chip-dot{width:5px;height:5px;border-radius:50%}",
|
|
496
|
+
".c-heal .chip-dot,.chip.c-heal.on .chip-dot{background:var(--cyan)}",
|
|
497
|
+
".c-prot .chip-dot,.chip.c-prot.on .chip-dot{background:var(--purple)}",
|
|
498
|
+
".c-fail .chip-dot,.chip.c-fail.on .chip-dot{background:var(--red)}",
|
|
499
|
+
".fb-search{margin-left:auto;display:flex;align-items:center;background:var(--bg2);border:1px solid var(--line2);border-radius:var(--radius);padding:0 12px;gap:7px;height:34px}",
|
|
500
|
+
".fb-search input{background:none;border:none;outline:none;color:var(--text);font-size:12.5px;width:200px}",
|
|
501
|
+
".fb-search input::placeholder{color:var(--text3)}",
|
|
502
|
+
".events{display:flex;flex-direction:column;gap:8px}",
|
|
503
|
+
".ecard{background:var(--bg2);border:1px solid var(--line);border-radius:var(--radius-lg);overflow:hidden;transition:border-color .2s ease;animation:cardIn .4s cubic-bezier(.18,.89,.32,1.15) both}",
|
|
504
|
+
"@keyframes cardIn{0%{opacity:0;transform:translateY(6px)}100%{opacity:1;transform:translateY(0)}}",
|
|
505
|
+
".ecard:hover{border-color:var(--line3)}",
|
|
506
|
+
".ecard.ec-healed{border-left:2px solid var(--cyan)}",
|
|
507
|
+
".ecard.ec-protected{border-left:2px solid var(--purple)}",
|
|
508
|
+
".ecard.ec-failed{border-left:2px solid var(--red)}",
|
|
509
|
+
".ecard.ec-protected .ecard-head{background:linear-gradient(90deg,rgba(162,89,255,.04),transparent)}",
|
|
510
|
+
".ecard-head{display:grid;grid-template-columns:auto 1fr auto auto;align-items:center;gap:12px;padding:13px 16px;cursor:pointer;user-select:none;transition:background .15s ease}",
|
|
511
|
+
".ecard-head:hover{background:var(--bg3)}",
|
|
512
|
+
".ecard.open .ecard-head{background:var(--bg3)}",
|
|
513
|
+
".status-badge{display:inline-flex;align-items:center;gap:5px;padding:4px 9px;border-radius:999px;font-size:10.5px;font-weight:600;letter-spacing:.8px;text-transform:uppercase;white-space:nowrap;border:1px solid currentColor}",
|
|
514
|
+
".sb-healed{color:var(--cyan);background:var(--cyan-dim)}",
|
|
515
|
+
".sb-protected{color:var(--purple);background:var(--purple-dim);animation:pulseborder 2.5s ease-in-out infinite}",
|
|
516
|
+
"@keyframes pulseborder{0%,100%{box-shadow:0 0 0 0 rgba(162,89,255,0)}50%{box-shadow:0 0 0 4px rgba(162,89,255,.15)}}",
|
|
517
|
+
".sb-failed{color:var(--red);background:var(--red-dim)}",
|
|
518
|
+
".sb-dot{width:5px;height:5px;border-radius:50%;background:currentColor;box-shadow:0 0 5px currentColor}",
|
|
519
|
+
".ecard-title{min-width:0}",
|
|
520
|
+
".ecard-selector{font-family:var(--mono);font-size:12.5px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:2px}",
|
|
521
|
+
".ecard-meta{font-size:11.5px;color:var(--text3);display:flex;align-items:center;gap:5px;flex-wrap:wrap}",
|
|
522
|
+
".ecard-meta a{color:var(--text2);font-family:var(--mono);font-size:11px}",
|
|
523
|
+
".ecard-meta a:hover{color:var(--cyan)}",
|
|
524
|
+
".ecard-tag{display:inline-block;font-size:10px;padding:1px 6px;border-radius:4px;background:var(--bg4);border:1px solid var(--line2);font-family:var(--mono);letter-spacing:.3px}",
|
|
525
|
+
".ecard-tag.t-iframe{color:var(--cyan);border-color:rgba(0,212,255,.2)}",
|
|
526
|
+
".ecard-tag.t-shadow{color:var(--purple);border-color:rgba(162,89,255,.2)}",
|
|
527
|
+
".ecard-tag.t-dryrun{color:var(--amber);border-color:rgba(255,184,77,.2)}",
|
|
528
|
+
".conf-badge{font-size:11px;font-weight:600;padding:3px 9px;border-radius:6px;font-variant-numeric:tabular-nums;white-space:nowrap}",
|
|
529
|
+
".conf-high{background:rgba(0,229,160,.12);color:var(--green);border:1px solid rgba(0,229,160,.2)}",
|
|
530
|
+
".conf-mid{background:rgba(255,184,77,.1);color:var(--amber);border:1px solid rgba(255,184,77,.2)}",
|
|
531
|
+
".conf-low{background:rgba(255,77,106,.1);color:var(--red);border:1px solid rgba(255,77,106,.2)}",
|
|
532
|
+
".chev{width:16px;height:16px;color:var(--text3);flex-shrink:0;transition:transform .25s ease}",
|
|
533
|
+
".ecard.open .chev{transform:rotate(90deg)}",
|
|
534
|
+
".ecard-body{display:none;padding:0 16px 18px;border-top:1px solid var(--line)}",
|
|
535
|
+
".ecard.open .ecard-body{display:block;animation:bodyIn .3s ease both}",
|
|
536
|
+
"@keyframes bodyIn{0%{opacity:0;transform:translateY(-3px)}100%{opacity:1;transform:translateY(0)}}",
|
|
537
|
+
".body-section{margin-top:16px}",
|
|
538
|
+
".bs-title{font-size:10.5px;letter-spacing:1.4px;text-transform:uppercase;color:var(--text3);margin-bottom:10px;display:flex;align-items:center;gap:8px}",
|
|
539
|
+
".bs-title .bst-dot{width:5px;height:5px;border-radius:50%;background:var(--purple);box-shadow:0 0 6px var(--purple)}",
|
|
540
|
+
".diff-block{background:var(--bg1);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}",
|
|
541
|
+
".diff-bar{display:flex;align-items:center;gap:8px;padding:7px 12px;background:var(--bg3);border-bottom:1px solid var(--line);font-size:11px;color:var(--text3);font-family:var(--mono)}",
|
|
542
|
+
".diff-bar .sep{opacity:.3}",
|
|
543
|
+
".diff-line{display:grid;grid-template-columns:22px 48px 1fr;font-family:var(--mono);font-size:12px;line-height:1.6}",
|
|
544
|
+
".diff-line .gutter{text-align:center;color:var(--text3)}",
|
|
545
|
+
".diff-line .ln{text-align:right;padding-right:10px;color:var(--text3)}",
|
|
546
|
+
".diff-line .code{white-space:pre-wrap;word-break:break-word;padding-right:12px}",
|
|
547
|
+
".diff-line.dl-add{background:rgba(0,229,160,.07)}",
|
|
548
|
+
".diff-line.dl-add .gutter,.diff-line.dl-add .code{color:var(--green)}",
|
|
549
|
+
".diff-line.dl-del{background:rgba(255,77,106,.07)}",
|
|
550
|
+
".diff-line.dl-del .gutter,.diff-line.dl-del .code{color:var(--red)}",
|
|
551
|
+
".diff-line.dl-ctx .code{color:var(--text3)}",
|
|
552
|
+
".diff-line.dl-hunk{background:rgba(0,212,255,.04)}",
|
|
553
|
+
".diff-line.dl-hunk .code{color:rgba(0,212,255,.6);font-size:11px}",
|
|
554
|
+
".auditor-wrap{display:grid;grid-template-columns:160px 1fr;gap:16px;background:var(--bg1);border:1px solid var(--line);border-radius:var(--radius);padding:16px}",
|
|
555
|
+
"@media(max-width:700px){.auditor-wrap{grid-template-columns:1fr}}",
|
|
556
|
+
".gauge-ring{position:relative;width:130px;height:130px;margin:0 auto}",
|
|
557
|
+
".gauge-ring svg{width:100%;height:100%;transform:rotate(-90deg)}",
|
|
558
|
+
".gauge-ring .gt{stroke:var(--line3);fill:none;stroke-width:9}",
|
|
559
|
+
".gauge-ring .gb{fill:none;stroke-width:9;stroke-linecap:round;transition:stroke-dasharray 1.2s cubic-bezier(.4,0,.2,1)}",
|
|
560
|
+
".gauge-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center}",
|
|
561
|
+
".gauge-pct{font-size:26px;font-weight:700;font-variant-numeric:tabular-nums;line-height:1}",
|
|
562
|
+
".gauge-sub{font-size:9.5px;letter-spacing:1px;text-transform:uppercase;color:var(--text3);margin-top:2px}",
|
|
563
|
+
".gb-consistent{stroke:var(--green)}.gb-suspicious{stroke:var(--amber)}.gb-inconsistent{stroke:var(--red)}",
|
|
564
|
+
".pct-consistent{color:var(--green)}.pct-suspicious{color:var(--amber)}.pct-inconsistent{color:var(--red)}",
|
|
565
|
+
".audit-details{display:flex;flex-direction:column;gap:7px}",
|
|
566
|
+
".ad-row{display:flex;gap:8px;font-size:12.5px;align-items:flex-start}",
|
|
567
|
+
".ad-label{color:var(--text3);font-size:11px;letter-spacing:.5px;text-transform:uppercase;min-width:110px;flex-shrink:0;padding-top:1px}",
|
|
568
|
+
".ad-val{color:var(--text)}",
|
|
569
|
+
".ad-reason{margin-top:6px;padding:10px 12px;border-left:2px solid var(--purple);background:rgba(162,89,255,.06);font-size:12.5px;color:var(--text);line-height:1.55;border-radius:0 var(--radius) var(--radius) 0}",
|
|
570
|
+
".verdict-pill{display:inline-block;padding:2px 8px;border-radius:5px;font-size:10.5px;font-weight:700;letter-spacing:.8px;text-transform:uppercase}",
|
|
571
|
+
".vp-consistent{color:var(--green);background:var(--green-dim);border:1px solid rgba(0,229,160,.2)}",
|
|
572
|
+
".vp-suspicious{color:var(--amber);background:var(--amber-dim);border:1px solid rgba(255,184,77,.2)}",
|
|
573
|
+
".vp-inconsistent{color:var(--red);background:var(--red-dim);border:1px solid rgba(255,77,106,.2)}",
|
|
574
|
+
".cot{display:flex;flex-direction:column;gap:7px}",
|
|
575
|
+
".cot-step{display:flex;gap:10px;align-items:flex-start;padding:9px 12px;background:var(--bg1);border:1px solid var(--line);border-radius:var(--radius)}",
|
|
576
|
+
".cot-n{flex-shrink:0;font-family:var(--mono);font-size:10.5px;color:var(--cyan);width:20px;height:20px;border-radius:50%;background:rgba(0,212,255,.1);border:1px solid rgba(0,212,255,.2);display:inline-flex;align-items:center;justify-content:center}",
|
|
577
|
+
".cot-t{color:var(--text2);font-size:12.5px;line-height:1.5}",
|
|
578
|
+
".dna-table{width:100%;border-collapse:separate;border-spacing:0;font-size:12px;background:var(--bg1);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}",
|
|
579
|
+
".dna-table th,.dna-table td{padding:7px 11px;text-align:left;border-bottom:1px solid var(--line)}",
|
|
580
|
+
".dna-table th{background:var(--bg3);font-weight:600;color:var(--text3);font-size:10.5px;letter-spacing:.6px;text-transform:uppercase}",
|
|
581
|
+
".dna-table tr:last-child td{border-bottom:none}",
|
|
582
|
+
".dna-table td.field{color:var(--text3);width:20%}",
|
|
583
|
+
".dna-table td.val{font-family:var(--mono);font-size:11.5px}",
|
|
584
|
+
".dna-table td.icon{width:32px;text-align:center}",
|
|
585
|
+
".dna-table tr.matched td.icon{color:var(--green)}",
|
|
586
|
+
".dna-table tr.changed td.val.new{color:var(--amber)}",
|
|
587
|
+
".dna-table tr.changed td.val.old{color:var(--text3);text-decoration:line-through dotted}",
|
|
588
|
+
".dna-table tr.changed td.icon{color:var(--amber)}",
|
|
589
|
+
".dna-table tr.missing td.icon{color:var(--text3)}",
|
|
590
|
+
".alts{display:flex;flex-direction:column;gap:5px}",
|
|
591
|
+
".alt-row{font-family:var(--mono);font-size:12px;color:var(--text2);padding:7px 11px;background:var(--bg1);border:1px solid var(--line);border-radius:var(--radius);display:flex;align-items:center;justify-content:space-between;gap:8px}",
|
|
592
|
+
".alt-copy{font-size:10px;padding:2px 8px;border-radius:5px;border:1px solid var(--line2);background:var(--bg3);color:var(--text3);cursor:pointer;transition:all .15s ease;white-space:nowrap}",
|
|
593
|
+
".alt-copy:hover{color:var(--text);border-color:var(--line3)}",
|
|
594
|
+
".actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:16px;padding-top:14px;border-top:1px dashed var(--line2)}",
|
|
595
|
+
".btn{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border-radius:var(--radius);font-size:12.5px;font-weight:500;border:1px solid var(--line2);background:var(--bg3);color:var(--text2);cursor:pointer;transition:all .2s ease}",
|
|
596
|
+
".btn:hover{color:var(--text);border-color:var(--line3);transform:translateY(-1px)}",
|
|
597
|
+
".btn.btn-primary{background:linear-gradient(135deg,rgba(0,212,255,.15),rgba(162,89,255,.15));border-color:rgba(0,212,255,.3);color:var(--cyan)}",
|
|
598
|
+
".btn.btn-primary:hover{box-shadow:0 4px 20px rgba(0,212,255,.2)}",
|
|
599
|
+
".btn.btn-danger{border-color:rgba(255,77,106,.3);color:var(--red)}",
|
|
600
|
+
".btn.btn-danger:hover{background:var(--red-dim)}",
|
|
601
|
+
".btn[data-done='true']{border-color:rgba(0,229,160,.3);color:var(--green);background:var(--green-dim)}",
|
|
602
|
+
".fail-img-wrap{background:var(--bg1);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}",
|
|
603
|
+
".fail-img-wrap img{display:block;max-width:100%;height:auto}",
|
|
604
|
+
".fail-img-meta{padding:6px 11px;font-size:11px;color:var(--text3);font-family:var(--mono)}",
|
|
605
|
+
".prot-notice{padding:12px 14px;background:rgba(162,89,255,.07);border:1px solid rgba(162,89,255,.2);border-radius:var(--radius);font-size:12.5px;color:var(--text2);line-height:1.55}",
|
|
606
|
+
".prot-notice strong{color:var(--purple)}",
|
|
607
|
+
".row2{display:grid;grid-template-columns:1fr 1fr;gap:14px}",
|
|
608
|
+
"@media(max-width:700px){.row2{grid-template-columns:1fr}}",
|
|
609
|
+
".muted-block{padding:12px 14px;background:var(--bg1);border:1px dashed var(--line);border-radius:var(--radius);font-size:12px;color:var(--text3)}",
|
|
610
|
+
".hist-row{display:grid;grid-template-columns:1fr repeat(5,80px);gap:0;font-size:12px}",
|
|
611
|
+
".hist-row .hc{padding:6px 10px;border-bottom:1px solid var(--line)}",
|
|
612
|
+
".hist-row.hdr .hc{color:var(--text3);font-size:10.5px;letter-spacing:.6px;text-transform:uppercase;background:var(--bg3)}",
|
|
613
|
+
".hist-row:not(.hdr):hover .hc{background:var(--bg3)}",
|
|
614
|
+
".hist-row .hc:first-child{color:var(--text2)}",
|
|
615
|
+
".hist-saved{color:var(--cyan);font-weight:600}",
|
|
616
|
+
".empty-state{text-align:center;padding:60px 20px;color:var(--text3);border:1px dashed var(--line2);border-radius:var(--radius-xl);margin-top:12px}",
|
|
617
|
+
".es-icon{font-size:36px;margin-bottom:10px}",
|
|
618
|
+
".es-title{font-size:15px;color:var(--text2);margin-bottom:4px}",
|
|
619
|
+
".hidden{display:none!important}",
|
|
620
|
+
".toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:var(--bg4);border:1px solid var(--line3);color:var(--text);padding:10px 18px;border-radius:999px;font-size:12.5px;box-shadow:0 8px 30px rgba(0,0,0,.5);z-index:200;animation:toastIn .3s ease}",
|
|
621
|
+
"@keyframes toastIn{0%{opacity:0;transform:translate(-50%,8px)}100%{opacity:1;transform:translate(-50%,0)}}",
|
|
622
|
+
".footer{border-top:1px solid var(--line);margin-top:60px;padding:20px 0;font-size:12px;color:var(--text3)}",
|
|
623
|
+
".footer-inner{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:10px}",
|
|
624
|
+
"#view-overview{display:block}",
|
|
625
|
+
"#view-deepdive{display:none}",
|
|
626
|
+
"body.view-deepdive #view-overview{display:none}",
|
|
627
|
+
"body.view-deepdive #view-deepdive{display:block}",
|
|
628
|
+
].join("\n");
|
|
629
|
+
/* eslint-enable prettier/prettier */
|
|
630
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
631
|
+
// FRONT-END JS
|
|
632
|
+
//
|
|
633
|
+
// Stored as a plain Array.join() string — no TypeScript template literal so
|
|
634
|
+
// the browser-side ${...} expressions and backtick strings are never
|
|
635
|
+
// interpreted by the TypeScript compiler.
|
|
636
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
637
|
+
/* eslint-disable prettier/prettier */
|
|
638
|
+
const REPORT_JS = [
|
|
639
|
+
"(function(){",
|
|
640
|
+
"'use strict';",
|
|
641
|
+
"var DATA=window.__SELA_DATA__;",
|
|
642
|
+
"function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/\"/g,'"');}",
|
|
643
|
+
"function fmt(iso){try{return new Date(iso).toLocaleString();}catch(e){return iso;}}",
|
|
644
|
+
"function fmtPct(v){return(typeof v==='number'&&isFinite(v))?v+'%':'n/a';}",
|
|
645
|
+
"function fmtMin(m){var h=Math.floor(m/60),r=m%60;return h>0?h+'h '+r+'m':m+'m';}",
|
|
646
|
+
"function $(id){return document.getElementById(id);}",
|
|
647
|
+
"function qsa(sel,root){return Array.from((root||document).querySelectorAll(sel));}",
|
|
648
|
+
// ── View toggle
|
|
649
|
+
"function setView(v){",
|
|
650
|
+
" document.body.classList.toggle('view-deepdive',v==='deepdive');",
|
|
651
|
+
" $('btn-overview').classList.toggle('active',v==='overview');",
|
|
652
|
+
" $('btn-deepdive').classList.toggle('active',v==='deepdive');",
|
|
653
|
+
"}",
|
|
654
|
+
"window.setView=setView;",
|
|
655
|
+
// ── Toast
|
|
656
|
+
"var toastTimer;",
|
|
657
|
+
"function toast(msg){var t=$('toast');t.textContent=msg;t.classList.remove('hidden');clearTimeout(toastTimer);toastTimer=setTimeout(function(){t.classList.add('hidden');},2600);}",
|
|
658
|
+
"window.copyJson=function(){navigator.clipboard&&navigator.clipboard.writeText(JSON.stringify(DATA,null,2)).then(function(){toast('JSON copied to clipboard');});};",
|
|
659
|
+
// ── Meta + KPIs
|
|
660
|
+
"var s=DATA.stats;",
|
|
661
|
+
"$('meta-gen').textContent='Generated '+fmt(DATA.generatedAt);",
|
|
662
|
+
"$('meta-cwd').textContent=DATA.cwd;",
|
|
663
|
+
"$('footer-cwd').textContent=DATA.cwd;",
|
|
664
|
+
"$('kpi-time').textContent=fmtMin(s.timeSavedMin);",
|
|
665
|
+
"$('kpi-healed').textContent=s.healed;",
|
|
666
|
+
"$('kpi-prot').textContent=s.protectedCnt;",
|
|
667
|
+
"$('kpi-fail').textContent=s.failed;",
|
|
668
|
+
"$('donut-total').textContent=s.total;",
|
|
669
|
+
"$('dd-count').textContent=DATA.events.length+' events';",
|
|
670
|
+
// ── Headline
|
|
671
|
+
"var headline=(function(){",
|
|
672
|
+
" if(s.healed===0&&s.protectedCnt===0&&s.failed===0)return['Sela was idle this run.','No broken selectors were encountered.'];",
|
|
673
|
+
" if(s.protectedCnt>0&&s.healed===0)return['Sela caught '+s.protectedCnt+' masked regression'+(s.protectedCnt!==1?'s':'')+'.',",
|
|
674
|
+
" 'The Intent Auditor blocked auto-fixes that would have silently changed test semantics.'];",
|
|
675
|
+
" if(s.healed>0&&s.protectedCnt>0)return['Sela healed '+s.healed+' selector'+(s.healed!==1?'s':'')+' and caught '+s.protectedCnt+' regression'+(s.protectedCnt!==1?'s':'')+'.',",
|
|
676
|
+
" 'Every fix was verified by the Intent Auditor. Two would-be fixes were masked regressions \u2014 drill in below.'];",
|
|
677
|
+
" if(s.healed>0)return['Sela healed '+s.healed+' selector'+(s.healed!==1?'s':'')+' automatically.',",
|
|
678
|
+
" 'Every fix was double-checked by the Intent Auditor before touching source code.'];",
|
|
679
|
+
" return['Sela couldn\u2019t heal '+s.failed+' selector'+(s.failed!==1?'s':'')+'.',",
|
|
680
|
+
" 'These need a human hand \u2014 context, candidates, and direct links are ready below.'];",
|
|
681
|
+
"})();",
|
|
682
|
+
"$('exec-h1').textContent=headline[0];",
|
|
683
|
+
"$('exec-sub').textContent=headline[1];",
|
|
684
|
+
// ── Donut chart
|
|
685
|
+
"var total=s.healed+s.protectedCnt+s.failed||1;",
|
|
686
|
+
"var donutCtx=$('donut-chart').getContext('2d');",
|
|
687
|
+
"new Chart(donutCtx,{type:'doughnut',",
|
|
688
|
+
" data:{labels:['Healed','Protected','Failed'],",
|
|
689
|
+
" datasets:[{data:[s.healed,s.protectedCnt,s.failed],",
|
|
690
|
+
" backgroundColor:['rgba(0,212,255,.8)','rgba(162,89,255,.8)','rgba(255,77,106,.8)'],",
|
|
691
|
+
" borderColor:['#00d4ff','#a259ff','#ff4d6a'],borderWidth:1.5,hoverOffset:6}]},",
|
|
692
|
+
" options:{responsive:true,maintainAspectRatio:false,cutout:'72%',",
|
|
693
|
+
" plugins:{legend:{display:false},tooltip:{callbacks:{label:function(c){return' '+c.label+': '+c.parsed;}}}},",
|
|
694
|
+
" onClick:function(e,els){if(!els.length)return;applyFilter(['HEALED','PROTECTED','FAILED'][els[0].index]);setView('deepdive');}",
|
|
695
|
+
" }",
|
|
696
|
+
"});",
|
|
697
|
+
// ── Donut legend
|
|
698
|
+
"var legendData=[",
|
|
699
|
+
" {filter:'ALL',swatch:'rgba(255,255,255,.3)',name:'All events',val:s.total,pct:null},",
|
|
700
|
+
" {filter:'HEALED',swatch:'var(--cyan)',name:'Healed',val:s.healed,pct:Math.round(s.healed/total*100)+'%'},",
|
|
701
|
+
" {filter:'PROTECTED',swatch:'var(--purple)',name:'Protected',val:s.protectedCnt,pct:Math.round(s.protectedCnt/total*100)+'%'},",
|
|
702
|
+
" {filter:'FAILED',swatch:'var(--red)',name:'Failed',val:s.failed,pct:Math.round(s.failed/total*100)+'%'},",
|
|
703
|
+
"];",
|
|
704
|
+
"var $legend=$('donut-legend');",
|
|
705
|
+
"$legend.innerHTML=legendData.map(function(d,i){",
|
|
706
|
+
" return '<div class=\"dl-row'+(i===0?' active':'')+'\" data-filter=\"'+d.filter+'\">'",
|
|
707
|
+
" +'<div class=\"dl-swatch\" style=\"background:'+d.swatch+'\"></div>'",
|
|
708
|
+
" +'<div class=\"dl-name\">'+d.name+'</div>'",
|
|
709
|
+
" +'<div class=\"dl-val\"'+(d.filter!=='ALL'?' style=\"color:'+d.swatch+'\"':'')+'>'+d.val+'</div>'",
|
|
710
|
+
" +(d.pct?'<div class=\"dl-pct\">'+d.pct+'</div>':'')",
|
|
711
|
+
" +'</div>';",
|
|
712
|
+
"}).join('');",
|
|
713
|
+
"$legend.addEventListener('click',function(e){",
|
|
714
|
+
" var row=e.target.closest('.dl-row');if(!row)return;",
|
|
715
|
+
" qsa('.dl-row',$legend).forEach(function(r){r.classList.remove('active');});",
|
|
716
|
+
" row.classList.add('active');",
|
|
717
|
+
" applyFilter(row.dataset.filter);",
|
|
718
|
+
" setView('deepdive');",
|
|
719
|
+
"});",
|
|
720
|
+
// ── Trend badge
|
|
721
|
+
"var runs=DATA.historical_runs||[];",
|
|
722
|
+
"if(runs.length>1){",
|
|
723
|
+
" var last=runs[runs.length-1],prev=runs[runs.length-2];",
|
|
724
|
+
" if(prev&&prev.time_saved_min>0){",
|
|
725
|
+
" var delta=Math.round((last.time_saved_min-prev.time_saved_min)/prev.time_saved_min*100);",
|
|
726
|
+
" var badge=$('trend-badge');",
|
|
727
|
+
" if(badge){",
|
|
728
|
+
" badge.textContent=(delta>=0?'+':'')+delta+'% vs prev run';",
|
|
729
|
+
" badge.style.color=delta>=0?'var(--green)':'var(--red)';",
|
|
730
|
+
" badge.style.borderColor=delta>=0?'rgba(0,229,160,.25)':'rgba(255,77,106,.25)';",
|
|
731
|
+
" badge.style.background=delta>=0?'var(--green-dim)':'var(--red-dim)';",
|
|
732
|
+
" }",
|
|
733
|
+
" }",
|
|
734
|
+
"}",
|
|
735
|
+
// ── Trend chart
|
|
736
|
+
"var trendLabels=runs.map(function(r){return r.label;});",
|
|
737
|
+
"var cumSaved=runs.map(function(_,i){return Math.round(runs.slice(0,i+1).reduce(function(a,b){return a+(b.time_saved_min/60);},0)*10)/10;});",
|
|
738
|
+
"var healedSeries=runs.map(function(r){return r.healed;});",
|
|
739
|
+
"var maxHealed=Math.max.apply(null,healedSeries.concat([1]));",
|
|
740
|
+
"var trendCtx=$('trend-chart').getContext('2d');",
|
|
741
|
+
"new Chart(trendCtx,{type:'line',",
|
|
742
|
+
" data:{labels:trendLabels,datasets:[",
|
|
743
|
+
" {label:'Cumulative hours saved',data:cumSaved,borderColor:'#00d4ff',backgroundColor:'rgba(0,212,255,.06)',fill:true,tension:.35,borderWidth:1.5,pointRadius:2.5,pointBackgroundColor:'#00d4ff',yAxisID:'y'},",
|
|
744
|
+
" {label:'Healed per run',data:healedSeries,borderColor:'rgba(162,89,255,.7)',backgroundColor:'transparent',tension:.35,borderWidth:1,borderDash:[4,3],pointRadius:2,pointBackgroundColor:'#a259ff',yAxisID:'y1'}",
|
|
745
|
+
" ]},",
|
|
746
|
+
" options:{responsive:true,maintainAspectRatio:false,",
|
|
747
|
+
" interaction:{mode:'index',intersect:false},",
|
|
748
|
+
" plugins:{legend:{display:false},",
|
|
749
|
+
" tooltip:{backgroundColor:'rgba(16,19,26,.95)',borderColor:'rgba(30,35,54,.8)',borderWidth:1,titleColor:'#dde2f0',bodyColor:'#8b93b0',padding:10,",
|
|
750
|
+
" callbacks:{label:function(c){return c.datasetIndex===0?' '+c.parsed.y.toFixed(1)+'h saved':' '+c.parsed.y+' healed';}}}},",
|
|
751
|
+
" scales:{",
|
|
752
|
+
" x:{grid:{color:'rgba(30,35,54,.6)',lineWidth:.5},ticks:{color:'#5a6180',font:{size:10},maxRotation:45}},",
|
|
753
|
+
" y:{position:'left',grid:{color:'rgba(30,35,54,.6)',lineWidth:.5},ticks:{color:'#5a6180',font:{size:10},callback:function(v){return v+'h';}}},",
|
|
754
|
+
" y1:{position:'right',grid:{display:false},ticks:{color:'#5a6180',font:{size:10}},max:maxHealed+2}",
|
|
755
|
+
" }",
|
|
756
|
+
" }",
|
|
757
|
+
"});",
|
|
758
|
+
// ── Historical table (last 5)
|
|
759
|
+
"var htbl=$('hist-table');",
|
|
760
|
+
"runs.slice().reverse().slice(0,5).forEach(function(r){",
|
|
761
|
+
" var row=document.createElement('div');row.className='hist-row';",
|
|
762
|
+
" row.innerHTML='<div class=\"hc\">'+esc(r.label)+'</div>'",
|
|
763
|
+
" +'<div class=\"hc\" style=\"color:var(--cyan)\">'+r.healed+'</div>'",
|
|
764
|
+
" +'<div class=\"hc\" style=\"color:var(--purple)\">'+(r.protected||0)+'</div>'",
|
|
765
|
+
" +'<div class=\"hc\" style=\"color:var(--red)\">'+r.failed+'</div>'",
|
|
766
|
+
" +'<div class=\"hc\">'+(r.avg_confidence||0)+'%</div>'",
|
|
767
|
+
" +'<div class=\"hc hist-saved\">'+fmtMin(r.time_saved_min)+'</div>';",
|
|
768
|
+
" htbl.appendChild(row);",
|
|
769
|
+
"});",
|
|
770
|
+
// ── VS Code link
|
|
771
|
+
"function vscodeLink(file,line){",
|
|
772
|
+
" if(!file)return'#';",
|
|
773
|
+
" var abs=(file.startsWith('/')||/^[A-Za-z]:/.test(file))?file:DATA.cwd+'/'+file;",
|
|
774
|
+
" return'vscode://file/'+encodeURI(abs).replace(/#/g,'%23')+(line?':'+line:'');",
|
|
775
|
+
"}",
|
|
776
|
+
// ── Confidence badge
|
|
777
|
+
"function confBadge(pct){",
|
|
778
|
+
" if(typeof pct!=='number'||!isFinite(pct))return'';",
|
|
779
|
+
" var cls=pct>=80?'conf-high':pct>=60?'conf-mid':'conf-low';",
|
|
780
|
+
" return'<span class=\"conf-badge '+cls+'\">'+pct+'%</span>';",
|
|
781
|
+
"}",
|
|
782
|
+
// ── Diff renderer
|
|
783
|
+
"function renderDiff(ev){",
|
|
784
|
+
" if(ev.kind==='PROTECTED')",
|
|
785
|
+
" return'<div class=\"prot-notice\"><strong>Source code untouched.</strong> Sela blocked this auto-fix \u2014 the Intent Auditor detected a semantic mismatch'+(ev.auditor&&ev.auditor.inversionType?' ('+esc(ev.auditor.inversionType)+')':'')+'. This is a real regression surfacing in CI. Investigate <a href=\"'+vscodeLink(ev.sourceFile,ev.sourceLine)+'\">'+esc(ev.sourceFile)+':'+ev.sourceLine+'</a> manually.</div>';",
|
|
786
|
+
" if(ev.kind==='FAILED')",
|
|
787
|
+
" return'<div class=\"muted-block\">No source code was modified. The AI could not propose a confident fix \u2014 all candidates resolved to 0 elements. Open the file directly to investigate.</div>';",
|
|
788
|
+
" var raw=ev.gitUnifiedDiff||'';",
|
|
789
|
+
" var skipPfx=['diff ','index ','--- ','+++','new file','deleted','similarity','rename','Binary'];",
|
|
790
|
+
" var hunkRe=/^@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/;",
|
|
791
|
+
" var body='',newLn=0,hasDiff=raw.split(/\\r?\\n/).some(function(l){return hunkRe.test(l);});",
|
|
792
|
+
" if(hasDiff){",
|
|
793
|
+
" raw.split(/\\r?\\n/).forEach(function(r){",
|
|
794
|
+
" if(skipPfx.some(function(p){return r.startsWith(p);}))return;",
|
|
795
|
+
" var hm=r.match(hunkRe);",
|
|
796
|
+
' if(hm){newLn=parseInt(hm[2]);body+=\'<div class="diff-line dl-hunk"><span class="gutter">@</span><span class="ln"></span><span class="code">\'+esc(r)+\'</span></div>\';return;}',
|
|
797
|
+
" if(!r.length||r.startsWith('\\\\'))return;",
|
|
798
|
+
" var h=r[0],t=r.slice(1);",
|
|
799
|
+
" if(h==='+'){body+='<div class=\"diff-line dl-add\"><span class=\"gutter\">+</span><span class=\"ln\">'+newLn+'</span><span class=\"code\">'+esc(t)+'</span></div>';newLn++;}",
|
|
800
|
+
' else if(h===\'-\'){body+=\'<div class="diff-line dl-del"><span class="gutter">-</span><span class="ln"></span><span class="code">\'+esc(t)+\'</span></div>\';}',
|
|
801
|
+
" else if(h===' '){body+='<div class=\"diff-line dl-ctx\"><span class=\"gutter\"> </span><span class=\"ln\">'+newLn+'</span><span class=\"code\">'+esc(t)+'</span></div>';newLn++;}",
|
|
802
|
+
" });",
|
|
803
|
+
" }else{",
|
|
804
|
+
" body='<div class=\"diff-line dl-del\"><span class=\"gutter\">-</span><span class=\"ln\">'+(ev.newLineNumber||'')+'</span><span class=\"code\">'+esc(ev.oldCodeLine||'')+'</span></div>'",
|
|
805
|
+
" +'<div class=\"diff-line dl-add\"><span class=\"gutter\">+</span><span class=\"ln\">'+(ev.newLineNumber||'')+'</span><span class=\"code\">'+esc(ev.newCodeLine||'')+'</span></div>';",
|
|
806
|
+
" }",
|
|
807
|
+
" return'<div class=\"diff-block\"><div class=\"diff-bar\"><span>'+esc(ev.sourceFile)+'</span><span class=\"sep\">\xb7</span><span>line '+ev.newLineNumber+'</span>'+(ev.diffStrategy?'<span class=\"sep\">\xb7</span><span>'+esc(ev.diffStrategy)+'</span>':'')+'<span class=\"sep\">\xb7</span><span>'+(hasDiff?'git diff':'fallback')+'</span></div><div>'+body+'</div></div>';",
|
|
808
|
+
"}",
|
|
809
|
+
// ── Auditor renderer
|
|
810
|
+
"function renderAuditor(a){",
|
|
811
|
+
" if(!a)return'<div class=\"muted-block\">Intent Auditor was not consulted for this event.</div>';",
|
|
812
|
+
" var vl=a.verdict.toLowerCase();",
|
|
813
|
+
" var hasPct=typeof a.confidence==='number'&&isFinite(a.confidence);",
|
|
814
|
+
" var pct=hasPct?Math.max(0,Math.min(100,a.confidence)):0;",
|
|
815
|
+
" var circ=2*Math.PI*50,dash=hasPct?(pct/100)*circ:0;",
|
|
816
|
+
" var adj=[];",
|
|
817
|
+
" if(a.penalty)adj.push('<span style=\"color:var(--red)\">'+a.penalty+'</span>');",
|
|
818
|
+
" if(a.bonus)adj.push('<span style=\"color:var(--green)\">+'+a.bonus+'</span>');",
|
|
819
|
+
" return'<div class=\"auditor-wrap\">'",
|
|
820
|
+
" +'<div class=\"gauge-ring\">'",
|
|
821
|
+
' +\'<svg viewBox="0 0 120 120"><circle class="gt" cx="60" cy="60" r="50"/><circle class="gb gb-\'+vl+\'" cx="60" cy="60" r="50" stroke-dasharray="\'+dash.toFixed(1)+\' \'+circ.toFixed(1)+\'"/></svg>\'',
|
|
822
|
+
" +'<div class=\"gauge-center\"><div class=\"gauge-pct pct-'+vl+'\">'+(hasPct?pct+'%':'n/a')+'</div><div class=\"gauge-sub\">consistency</div></div>'",
|
|
823
|
+
" +'</div>'",
|
|
824
|
+
" +'<div class=\"audit-details\">'",
|
|
825
|
+
' +\'<div class="ad-row"><span class="ad-label">Verdict</span><span class="ad-val"><span class="verdict-pill vp-\'+vl+\'">\'+esc(a.verdict)+\'</span></span></div>\'',
|
|
826
|
+
' +(a.inversionType?\'<div class="ad-row"><span class="ad-label">Inversion</span><span class="ad-val" style="color:var(--red)">\'+esc(a.inversionType)+\'</span></div>\':\'\')',
|
|
827
|
+
" +(adj.length?'<div class=\"ad-row\"><span class=\"ad-label\">Score delta</span><span class=\"ad-val\">'+adj.join(' ')+' (raw: '+fmtPct(a.rawConfidence)+')</span></div>':'')",
|
|
828
|
+
" +'<div class=\"ad-reason\">'+esc(a.reason)+'</div>'",
|
|
829
|
+
" +'</div></div>';",
|
|
830
|
+
"}",
|
|
831
|
+
// ── CoT renderer
|
|
832
|
+
"function renderCoT(steps){",
|
|
833
|
+
" if(!steps||!steps.length)return'<div class=\"muted-block\">No reasoning trace captured.</div>';",
|
|
834
|
+
" return'<div class=\"cot\">'+steps.map(function(s,i){return'<div class=\"cot-step\"><span class=\"cot-n\">'+(i+1)+'</span><span class=\"cot-t\">'+esc(s)+'</span></div>';}).join('')+'</div>';",
|
|
835
|
+
"}",
|
|
836
|
+
// ── DNA table
|
|
837
|
+
"function renderDna(before,after){",
|
|
838
|
+
" if(!before)return'<div class=\"muted-block\">No DNA baseline available.</div>';",
|
|
839
|
+
" var rows=[];",
|
|
840
|
+
" function row(field,ov,nv){",
|
|
841
|
+
" var os=ov==null?'':Array.isArray(ov)?ov.join(' / '):String(ov);",
|
|
842
|
+
" var ns=nv==null?'':Array.isArray(nv)?nv.join(' / '):String(nv);",
|
|
843
|
+
" var same=os===ns,hasAfter=!!after;",
|
|
844
|
+
" var cls=!hasAfter?'missing':same?'matched':'changed';",
|
|
845
|
+
" var icon=!hasAfter?'\u2014':same?'\u2713':'\u25b3';",
|
|
846
|
+
" var vc;",
|
|
847
|
+
" if(!hasAfter||same)vc='<td class=\"val\">'+esc(os||'\u2014')+'</td>';",
|
|
848
|
+
" else vc='<td class=\"val\"><span class=\"val old\">'+esc(os||'\u2014')+'</span> \u2192 <span class=\"val new\">'+esc(ns||'\u2014')+'</span></td>';",
|
|
849
|
+
" rows.push('<tr class=\"'+cls+'\"><td class=\"field\">'+esc(field)+'</td>'+vc+'<td class=\"icon\">'+icon+'</td></tr>');",
|
|
850
|
+
" }",
|
|
851
|
+
" row('Tag',before.tagName,after&&after.tagName);",
|
|
852
|
+
" row('Text',before.text,after&&after.text);",
|
|
853
|
+
" row('Role',before.role,after&&after.role);",
|
|
854
|
+
" row('Classes',before.classes,after&&after.classes);",
|
|
855
|
+
" row('Ancestry',before.ancestry,after&&after.ancestry);",
|
|
856
|
+
" row('Closest label',before.closestLabel,after&&after.closestLabel);",
|
|
857
|
+
" var ab=Object.entries(before.attributes||{}).map(function(e){return e[0]+'='+e[1];}).join(' \xb7 ');",
|
|
858
|
+
" var aa=after?Object.entries(after.attributes||{}).map(function(e){return e[0]+'='+e[1];}).join(' \xb7 '):null;",
|
|
859
|
+
" row('Attributes',ab,aa);",
|
|
860
|
+
" return'<table class=\"dna-table\"><thead><tr><th>Field</th><th>Value</th><th></th></tr></thead><tbody>'+rows.join('')+'</tbody></table>';",
|
|
861
|
+
"}",
|
|
862
|
+
// ── Screenshot
|
|
863
|
+
"function renderScreenshot(ev){",
|
|
864
|
+
" if(ev.kind!=='FAILED'||!ev.failureScreenshotPng)return'';",
|
|
865
|
+
' return\'<div class="body-section"><div class="bs-title"><span class="bst-dot"></span>Playwright Failure Screenshot</div>\'',
|
|
866
|
+
' +\'<div class="fail-img-wrap"><img src="data:image/png;base64,\'+ev.failureScreenshotPng+\'" alt="Failure screenshot" loading="lazy"/>\'',
|
|
867
|
+
" +'<div class=\"fail-img-meta\">captured at test failure \xb7 base64-embedded</div></div></div>';",
|
|
868
|
+
"}",
|
|
869
|
+
// ── Card body
|
|
870
|
+
"function renderCardBody(ev){",
|
|
871
|
+
" var steps=ev.reasoningSteps||[];",
|
|
872
|
+
" if(ev.kind==='PROTECTED'&&!steps.length){",
|
|
873
|
+
" steps=['AI proposed a replacement selector for the broken locator.'];",
|
|
874
|
+
" if(ev.aiExplanation)steps.push('Reasoning: '+ev.aiExplanation);",
|
|
875
|
+
" if(ev.candidateNewSelector)steps.push('Candidate: '+ev.candidateNewSelector+' \u2014 but semantics changed.');",
|
|
876
|
+
" if(ev.auditor)steps.push('Intent Auditor: '+ev.auditor.verdict+' \u2014 '+ev.auditor.reason.slice(0,160));",
|
|
877
|
+
" steps.push('Sela blocked the auto-fix. Source code is untouched.');",
|
|
878
|
+
" }",
|
|
879
|
+
" if(ev.kind==='FAILED'&&!steps.length){",
|
|
880
|
+
" steps=['Locator failed to resolve at runtime \u2014 Sela attempted to heal it.'];",
|
|
881
|
+
" if(ev.aiExplanation)steps.push('AI: '+ev.aiExplanation);",
|
|
882
|
+
" steps.push('Reason: '+(ev.reason||'unknown'));",
|
|
883
|
+
" steps.push('Open the file in your editor to inspect the original locator.');",
|
|
884
|
+
" }",
|
|
885
|
+
" var issueUrl='https://github.com/sela-core/sela/issues/new?title='+encodeURIComponent('[Report] '+ev.kind+' '+ev.stableId)+'&body='+encodeURIComponent('Stable ID: '+ev.stableId+'\\nFile: '+ev.sourceFile+':'+ev.sourceLine);",
|
|
886
|
+
" var link=vscodeLink(ev.sourceFile,ev.sourceLine);",
|
|
887
|
+
" var actions='';",
|
|
888
|
+
" if(ev.kind==='HEALED'){",
|
|
889
|
+
" actions='<div class=\"actions\">'",
|
|
890
|
+
" +'<button class=\"btn btn-primary\" data-action=\"keep\" data-id=\"'+ev.stableId+'\" onclick=\"handleAction(this,\\'keep\\',\\''+ev.stableId+'\\')\">\u2713 Keep changes</button>'",
|
|
891
|
+
" +'<button class=\"btn btn-danger\" onclick=\"handleAction(this,\\'rollback\\',\\''+ev.stableId+'\\')\">↶ Rollback</button>'",
|
|
892
|
+
" +'<a class=\"btn\" href=\"'+link+'\">⌗ Open in VS Code</a>'",
|
|
893
|
+
' +\'<a class="btn" href="\'+issueUrl+\'" target="_blank" rel="noopener">⚑ Report issue</a>\'',
|
|
894
|
+
" +'</div>';",
|
|
895
|
+
" }else if(ev.kind==='PROTECTED'){",
|
|
896
|
+
" actions='<div class=\"actions\">'",
|
|
897
|
+
" +'<a class=\"btn\" href=\"'+link+'\">\u2192 Open in VS Code</a>'",
|
|
898
|
+
" +'<button class=\"btn btn-primary\" onclick=\"handleAction(this,\\'ack\\',\\''+ev.stableId+'\\')\">✓ Acknowledged \u2014 will investigate</button>'",
|
|
899
|
+
' +\'<a class="btn" href="\'+issueUrl+\'" target="_blank" rel="noopener">⚑ File regression</a>\'',
|
|
900
|
+
" +'</div>';",
|
|
901
|
+
" }else{",
|
|
902
|
+
" actions='<div class=\"actions\">'",
|
|
903
|
+
" +'<a class=\"btn btn-primary\" href=\"'+link+'\">\u2192 Open in VS Code</a>'",
|
|
904
|
+
' +\'<a class="btn" href="\'+issueUrl+\'" target="_blank" rel="noopener">⚑ Report issue</a>\'',
|
|
905
|
+
" +'</div>';",
|
|
906
|
+
" }",
|
|
907
|
+
" var alts=(ev.kind==='HEALED'&&ev.aiAlternatives&&ev.aiAlternatives.length)",
|
|
908
|
+
' ?\'<div class="body-section"><div class="bs-title"><span class="bst-dot"></span>Alternative selectors considered</div><div class="alts">\'',
|
|
909
|
+
" +ev.aiAlternatives.map(function(a){return'<div class=\"alt-row\"><span>'+esc(a)+'</span><button class=\"alt-copy\" onclick=\"copyText(\\''+esc(a)+'\\',this)\">Copy</button></div>';}).join('')",
|
|
910
|
+
" +'</div></div>'",
|
|
911
|
+
" :'';",
|
|
912
|
+
' return\'<div class="row2" style="margin-top:16px">\'',
|
|
913
|
+
" +'<div><div class=\"bs-title\"><span class=\"bst-dot\"></span>AI Reasoning \xb7 Chain of Thought</div>'+renderCoT(steps)+'</div>'",
|
|
914
|
+
" +'<div><div class=\"bs-title\"><span class=\"bst-dot\"></span>Intent Auditor</div>'+renderAuditor(ev.auditor||null)+'</div>'",
|
|
915
|
+
" +'</div>'",
|
|
916
|
+
' +\'<div class="body-section"><div class="bs-title"><span class="bst-dot"></span>Source Code Diff</div>\'+renderDiff(ev)+\'</div>\'',
|
|
917
|
+
' +\'<div class="body-section"><div class="bs-title"><span class="bst-dot"></span>Visual DNA Evidence</div>\'+renderDna(ev.dnaBefore||null,ev.dnaAfter||null)+\'</div>\'',
|
|
918
|
+
" +renderScreenshot(ev)",
|
|
919
|
+
" +alts",
|
|
920
|
+
" +actions;",
|
|
921
|
+
"}",
|
|
922
|
+
// ── Event card
|
|
923
|
+
"function renderCard(ev,idx){",
|
|
924
|
+
" var kindCls=ev.kind==='HEALED'?'ec-healed':ev.kind==='PROTECTED'?'ec-protected':'ec-failed';",
|
|
925
|
+
" var badgeCls=ev.kind==='HEALED'?'sb-healed':ev.kind==='PROTECTED'?'sb-protected':'sb-failed';",
|
|
926
|
+
" var badgeTxt=ev.kind==='HEALED'?'Healed':ev.kind==='PROTECTED'?'Protected \xb7 Bug Caught':'Failed';",
|
|
927
|
+
" var sel=ev.kind==='HEALED'?(ev.newSelector||ev.oldSelector):ev.oldSelector;",
|
|
928
|
+
" var link=vscodeLink(ev.sourceFile,ev.sourceLine);",
|
|
929
|
+
" var tags='';",
|
|
930
|
+
" if(ev.inIframe)tags+='<span class=\"ecard-tag t-iframe\">iframe</span>';",
|
|
931
|
+
" if(ev.inShadowDom)tags+='<span class=\"ecard-tag t-shadow\">shadow DOM</span>';",
|
|
932
|
+
" if(ev.dryRun)tags+='<span class=\"ecard-tag t-dryrun\">dry run</span>';",
|
|
933
|
+
" var haystack=(ev.oldSelector+' '+(ev.newSelector||'')+' '+ev.sourceFile+' '+(ev.testTitle||'')+' '+ev.stableId).toLowerCase();",
|
|
934
|
+
" return'<article class=\"ecard '+kindCls+'\" data-kind=\"'+ev.kind+'\" data-haystack=\"'+esc(haystack)+'\" style=\"animation-delay:'+(idx*0.04)+'s\">'",
|
|
935
|
+
" +'<div class=\"ecard-head\" onclick=\"this.parentElement.classList.toggle(\\'open\\')\">'",
|
|
936
|
+
" +'<span class=\"status-badge '+badgeCls+'\"><span class=\"sb-dot\"></span>'+badgeTxt+'</span>'",
|
|
937
|
+
" +'<div class=\"ecard-title\">'",
|
|
938
|
+
" +'<div class=\"ecard-selector\">'+esc(sel)+'</div>'",
|
|
939
|
+
" +'<div class=\"ecard-meta\">'+(ev.testTitle?esc(ev.testTitle)+' \xb7 ':'')",
|
|
940
|
+
" +'<a href=\"'+link+'\">'+esc(ev.sourceFile)+':'+ev.sourceLine+'</a>'+tags+' \xb7 '+fmt(ev.timestamp)+'</div>'",
|
|
941
|
+
" +'</div>'",
|
|
942
|
+
" +confBadge(ev.aiConfidence)",
|
|
943
|
+
' +\'<svg class="chev" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="5 3 11 8 5 13"/></svg>\'',
|
|
944
|
+
" +'</div>'",
|
|
945
|
+
" +'<div class=\"ecard-body\">'+renderCardBody(ev)+'</div>'",
|
|
946
|
+
" +'</article>';",
|
|
947
|
+
"}",
|
|
948
|
+
// ── Mount events
|
|
949
|
+
"var $el=$('events-list');",
|
|
950
|
+
"$el.innerHTML=DATA.events.map(renderCard).join('');",
|
|
951
|
+
"var firstProt=$el.querySelector('.ec-protected');",
|
|
952
|
+
"if(firstProt)firstProt.classList.add('open');",
|
|
953
|
+
"else{var f=$el.querySelector('.ecard');if(f)f.classList.add('open');}",
|
|
954
|
+
// ── Filter + Search
|
|
955
|
+
"var curFilter='ALL',searchTerm='';",
|
|
956
|
+
"function applyFilter(f){",
|
|
957
|
+
" curFilter=f;",
|
|
958
|
+
" qsa('.chip').forEach(function(c){c.classList.toggle('on',c.dataset.filter===f||(f==='ALL'&&c.dataset.filter==='ALL'));});",
|
|
959
|
+
" filterCards();",
|
|
960
|
+
"}",
|
|
961
|
+
"window.applyFilter=applyFilter;",
|
|
962
|
+
"function filterCards(){",
|
|
963
|
+
" var vis=0;",
|
|
964
|
+
" qsa('.ecard',$el).forEach(function(card){",
|
|
965
|
+
" var show=(curFilter==='ALL'||card.dataset.kind===curFilter)&&(!searchTerm||card.dataset.haystack.includes(searchTerm));",
|
|
966
|
+
" card.style.display=show?'':'none';",
|
|
967
|
+
" if(show)vis++;",
|
|
968
|
+
" });",
|
|
969
|
+
" $('empty-state').classList.toggle('hidden',vis>0||DATA.events.length===0);",
|
|
970
|
+
"}",
|
|
971
|
+
"var fbChips=document.querySelector('.fb-chips');",
|
|
972
|
+
"if(fbChips)fbChips.addEventListener('click',function(e){var chip=e.target.closest('.chip');if(chip&&chip.dataset.filter)applyFilter(chip.dataset.filter);});",
|
|
973
|
+
"$('search-input').addEventListener('input',function(e){searchTerm=e.target.value.toLowerCase().trim();filterCards();});",
|
|
974
|
+
// ── Actions
|
|
975
|
+
"window.handleAction=function(btn,action,id){",
|
|
976
|
+
" if(action==='keep'||action==='ack'){btn.dataset.done='true';btn.textContent=action==='keep'?'\u2713 Approved':'\u2713 Acknowledged';toast('Decision saved for '+id);}",
|
|
977
|
+
" else if(action==='rollback'){var cmd='npx sela rollback --id '+id;navigator.clipboard&&navigator.clipboard.writeText(cmd).then(function(){toast('Copied: '+cmd);});}",
|
|
978
|
+
"};",
|
|
979
|
+
"window.copyText=function(txt,btn){",
|
|
980
|
+
" navigator.clipboard&&navigator.clipboard.writeText(txt).then(function(){btn.textContent='Copied!';setTimeout(function(){btn.textContent='Copy';},1500);});",
|
|
981
|
+
"};",
|
|
982
|
+
// ── Keyboard shortcuts
|
|
983
|
+
"document.addEventListener('keydown',function(e){",
|
|
984
|
+
" if(e.key==='/'&&document.activeElement!==$('search-input')){e.preventDefault();$('search-input').focus();setView('deepdive');}",
|
|
985
|
+
" if(e.key==='Escape')$('search-input').blur();",
|
|
986
|
+
"});",
|
|
987
|
+
"})();",
|
|
988
|
+
].join("\n");
|
|
989
|
+
/* eslint-enable prettier/prettier */
|