sela-core 0.1.0-alpha.1
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 +103 -0
- package/bin/sela.js +3 -0
- package/dist/cli/ErrorHandler.d.ts +10 -0
- package/dist/cli/ErrorHandler.d.ts.map +1 -0
- package/dist/cli/ErrorHandler.js +70 -0
- package/dist/cli/commands/bulk.d.ts +3 -0
- package/dist/cli/commands/bulk.d.ts.map +1 -0
- package/dist/cli/commands/bulk.js +158 -0
- package/dist/cli/commands/find.d.ts +3 -0
- package/dist/cli/commands/find.d.ts.map +1 -0
- package/dist/cli/commands/find.js +51 -0
- package/dist/cli/commands/init.d.ts +3 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +166 -0
- package/dist/cli/commands/list.d.ts +3 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +56 -0
- 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/commands/refactor.d.ts +3 -0
- package/dist/cli/commands/refactor.d.ts.map +1 -0
- package/dist/cli/commands/refactor.js +30 -0
- package/dist/cli/commands/showReport.d.ts +3 -0
- package/dist/cli/commands/showReport.d.ts.map +1 -0
- package/dist/cli/commands/showReport.js +82 -0
- package/dist/cli/commands/status.d.ts +3 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +51 -0
- package/dist/cli/commands/sync.d.ts +3 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +131 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +94 -0
- package/dist/cli/ui/DnaTable.d.ts +3 -0
- package/dist/cli/ui/DnaTable.d.ts.map +1 -0
- package/dist/cli/ui/DnaTable.js +77 -0
- package/dist/cli/ui/LocatorPicker.d.ts +8 -0
- package/dist/cli/ui/LocatorPicker.d.ts.map +1 -0
- package/dist/cli/ui/LocatorPicker.js +33 -0
- package/dist/cli/ui/ProgressReporter.d.ts +11 -0
- package/dist/cli/ui/ProgressReporter.d.ts.map +1 -0
- package/dist/cli/ui/ProgressReporter.js +33 -0
- package/dist/cli/ui/RefactorWizard.d.ts +26 -0
- package/dist/cli/ui/RefactorWizard.d.ts.map +1 -0
- package/dist/cli/ui/RefactorWizard.js +295 -0
- package/dist/config/ConfigLoader.d.ts +16 -0
- package/dist/config/ConfigLoader.d.ts.map +1 -0
- package/dist/config/ConfigLoader.js +185 -0
- package/dist/config/DryRunGuard.d.ts +14 -0
- package/dist/config/DryRunGuard.d.ts.map +1 -0
- package/dist/config/DryRunGuard.js +80 -0
- package/dist/config/PathScopeGuard.d.ts +42 -0
- package/dist/config/PathScopeGuard.d.ts.map +1 -0
- package/dist/config/PathScopeGuard.js +192 -0
- package/dist/config/SelaConfig.d.ts +178 -0
- package/dist/config/SelaConfig.d.ts.map +1 -0
- package/dist/config/SelaConfig.js +141 -0
- package/dist/config/defineConfig.d.ts +3 -0
- package/dist/config/defineConfig.d.ts.map +1 -0
- package/dist/config/defineConfig.js +9 -0
- package/dist/engine/FixwrightEngine.d.ts +24 -0
- package/dist/engine/FixwrightEngine.d.ts.map +1 -0
- package/dist/engine/FixwrightEngine.js +403 -0
- package/dist/engine/HealingRegistry.d.ts +40 -0
- package/dist/engine/HealingRegistry.d.ts.map +1 -0
- package/dist/engine/HealingRegistry.js +99 -0
- package/dist/engine/SelaEngine.d.ts +57 -0
- package/dist/engine/SelaEngine.d.ts.map +1 -0
- package/dist/engine/SelaEngine.js +1146 -0
- package/dist/engine/singleton.d.ts +3 -0
- package/dist/engine/singleton.d.ts.map +1 -0
- package/dist/engine/singleton.js +5 -0
- package/dist/errors/SelaError.d.ts +146 -0
- package/dist/errors/SelaError.d.ts.map +1 -0
- package/dist/errors/SelaError.js +168 -0
- package/dist/fixtures/expectProxy.d.ts +12 -0
- package/dist/fixtures/expectProxy.d.ts.map +1 -0
- package/dist/fixtures/expectProxy.js +246 -0
- package/dist/fixtures/index.d.ts +30 -0
- package/dist/fixtures/index.d.ts.map +1 -0
- package/dist/fixtures/index.js +765 -0
- package/dist/fixtures/moduleExpect.d.ts +2 -0
- package/dist/fixtures/moduleExpect.d.ts.map +1 -0
- package/dist/fixtures/moduleExpect.js +54 -0
- package/dist/fixtures/proxyTag.d.ts +12 -0
- package/dist/fixtures/proxyTag.d.ts.map +1 -0
- package/dist/fixtures/proxyTag.js +45 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/reporter/SelaReporter.d.ts +165 -0
- package/dist/reporter/SelaReporter.d.ts.map +1 -0
- package/dist/reporter/SelaReporter.js +517 -0
- package/dist/services/ASTSourceUpdater.d.ts +143 -0
- package/dist/services/ASTSourceUpdater.d.ts.map +1 -0
- package/dist/services/ASTSourceUpdater.js +3514 -0
- 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/ArgumentTypeAnalyzer.d.ts +26 -0
- package/dist/services/ArgumentTypeAnalyzer.d.ts.map +1 -0
- package/dist/services/ArgumentTypeAnalyzer.js +92 -0
- package/dist/services/BlastRadiusAnalyzer.d.ts +15 -0
- package/dist/services/BlastRadiusAnalyzer.d.ts.map +1 -0
- package/dist/services/BlastRadiusAnalyzer.js +107 -0
- package/dist/services/ChainValidator.d.ts +76 -0
- package/dist/services/ChainValidator.d.ts.map +1 -0
- package/dist/services/ChainValidator.js +570 -0
- package/dist/services/CrossFileHealer.d.ts +6 -0
- package/dist/services/CrossFileHealer.d.ts.map +1 -0
- package/dist/services/CrossFileHealer.js +135 -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/DefinitionTracer.d.ts +41 -0
- package/dist/services/DefinitionTracer.d.ts.map +1 -0
- package/dist/services/DefinitionTracer.js +366 -0
- package/dist/services/DnaEditorService.d.ts +31 -0
- package/dist/services/DnaEditorService.d.ts.map +1 -0
- package/dist/services/DnaEditorService.js +217 -0
- package/dist/services/DnaIndexService.d.ts +24 -0
- package/dist/services/DnaIndexService.d.ts.map +1 -0
- package/dist/services/DnaIndexService.js +131 -0
- package/dist/services/HealBudget.d.ts +32 -0
- package/dist/services/HealBudget.d.ts.map +1 -0
- package/dist/services/HealBudget.js +85 -0
- package/dist/services/HealReportService.d.ts +161 -0
- package/dist/services/HealReportService.d.ts.map +1 -0
- package/dist/services/HealReportService.js +1039 -0
- package/dist/services/HealingAdvisory.d.ts +30 -0
- package/dist/services/HealingAdvisory.d.ts.map +1 -0
- package/dist/services/HealingAdvisory.js +51 -0
- package/dist/services/HealingCacheService.d.ts +135 -0
- package/dist/services/HealingCacheService.d.ts.map +1 -0
- package/dist/services/HealingCacheService.js +404 -0
- package/dist/services/HealthReportService.d.ts +10 -0
- package/dist/services/HealthReportService.d.ts.map +1 -0
- package/dist/services/HealthReportService.js +84 -0
- package/dist/services/InitializerUpdater.d.ts +16 -0
- package/dist/services/InitializerUpdater.d.ts.map +1 -0
- package/dist/services/InitializerUpdater.js +73 -0
- package/dist/services/IntentAuditor.d.ts +56 -0
- package/dist/services/IntentAuditor.d.ts.map +1 -0
- package/dist/services/IntentAuditor.js +326 -0
- package/dist/services/InteractiveReview.d.ts +15 -0
- package/dist/services/InteractiveReview.d.ts.map +1 -0
- package/dist/services/InteractiveReview.js +128 -0
- package/dist/services/LLMService.d.ts +121 -0
- package/dist/services/LLMService.d.ts.map +1 -0
- package/dist/services/LLMService.js +475 -0
- package/dist/services/MutationApplier.d.ts +69 -0
- package/dist/services/MutationApplier.d.ts.map +1 -0
- package/dist/services/MutationApplier.js +360 -0
- package/dist/services/PRAutomationService.d.ts +50 -0
- package/dist/services/PRAutomationService.d.ts.map +1 -0
- package/dist/services/PRAutomationService.js +760 -0
- package/dist/services/PendingPromptLedger.d.ts +51 -0
- package/dist/services/PendingPromptLedger.d.ts.map +1 -0
- package/dist/services/PendingPromptLedger.js +205 -0
- package/dist/services/ReportGenerator.d.ts +156 -0
- package/dist/services/ReportGenerator.d.ts.map +1 -0
- package/dist/services/ReportGenerator.js +278 -0
- 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 +112 -0
- package/dist/services/SafetyGuard.d.ts.map +1 -0
- package/dist/services/SafetyGuard.js +87 -0
- package/dist/services/Sanitizer.d.ts +28 -0
- package/dist/services/Sanitizer.d.ts.map +1 -0
- package/dist/services/Sanitizer.js +142 -0
- package/dist/services/SanitizingAnthropic.d.ts +19 -0
- package/dist/services/SanitizingAnthropic.d.ts.map +1 -0
- package/dist/services/SanitizingAnthropic.js +84 -0
- 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.d.ts +11 -0
- package/dist/services/SnapshotService.d.ts.map +1 -0
- package/dist/services/SnapshotService.js +352 -0
- package/dist/services/SourceLinkService.d.ts +26 -0
- package/dist/services/SourceLinkService.d.ts.map +1 -0
- package/dist/services/SourceLinkService.js +165 -0
- package/dist/services/SourceUpdater.d.ts +45 -0
- package/dist/services/SourceUpdater.d.ts.map +1 -0
- package/dist/services/SourceUpdater.js +1091 -0
- package/dist/services/TemplateDiffService.d.ts +25 -0
- package/dist/services/TemplateDiffService.d.ts.map +1 -0
- package/dist/services/TemplateDiffService.js +334 -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/services/WorkspaceSnapshotService.d.ts +71 -0
- package/dist/services/WorkspaceSnapshotService.d.ts.map +1 -0
- package/dist/services/WorkspaceSnapshotService.js +200 -0
- package/dist/services/safety/PipelineContext.d.ts +63 -0
- package/dist/services/safety/PipelineContext.d.ts.map +1 -0
- package/dist/services/safety/PipelineContext.js +34 -0
- package/dist/services/safety/roleClassifier.d.ts +4 -0
- package/dist/services/safety/roleClassifier.d.ts.map +1 -0
- package/dist/services/safety/roleClassifier.js +233 -0
- package/dist/services/safety/rules/ConfidenceTierRule.d.ts +3 -0
- package/dist/services/safety/rules/ConfidenceTierRule.d.ts.map +1 -0
- package/dist/services/safety/rules/ConfidenceTierRule.js +32 -0
- package/dist/services/safety/rules/DangerousSinkRule.d.ts +10 -0
- package/dist/services/safety/rules/DangerousSinkRule.d.ts.map +1 -0
- package/dist/services/safety/rules/DangerousSinkRule.js +53 -0
- package/dist/services/safety/rules/FunctionalRoleRule.d.ts +3 -0
- package/dist/services/safety/rules/FunctionalRoleRule.d.ts.map +1 -0
- package/dist/services/safety/rules/FunctionalRoleRule.js +64 -0
- package/dist/services/safety/rules/HardConfidenceRule.d.ts +3 -0
- package/dist/services/safety/rules/HardConfidenceRule.d.ts.map +1 -0
- package/dist/services/safety/rules/HardConfidenceRule.js +22 -0
- package/dist/services/safety/rules/IntentAuditorRule.d.ts +3 -0
- package/dist/services/safety/rules/IntentAuditorRule.d.ts.map +1 -0
- package/dist/services/safety/rules/IntentAuditorRule.js +97 -0
- package/dist/services/safety/rules/SemanticOppositeRule.d.ts +3 -0
- package/dist/services/safety/rules/SemanticOppositeRule.d.ts.map +1 -0
- package/dist/services/safety/rules/SemanticOppositeRule.js +30 -0
- package/dist/services/safety/rules/StructuralOnlyRule.d.ts +3 -0
- package/dist/services/safety/rules/StructuralOnlyRule.d.ts.map +1 -0
- package/dist/services/safety/rules/StructuralOnlyRule.js +38 -0
- package/dist/services/safety/rules/VisibilityRule.d.ts +3 -0
- package/dist/services/safety/rules/VisibilityRule.d.ts.map +1 -0
- package/dist/services/safety/rules/VisibilityRule.js +53 -0
- package/dist/services/safety/rules/ZeroTrustRule.d.ts +3 -0
- package/dist/services/safety/rules/ZeroTrustRule.d.ts.map +1 -0
- package/dist/services/safety/rules/ZeroTrustRule.js +37 -0
- package/dist/services/safety/rules/index.d.ts +3 -0
- package/dist/services/safety/rules/index.d.ts.map +1 -0
- package/dist/services/safety/rules/index.js +34 -0
- package/dist/services/types.d.ts +59 -0
- package/dist/services/types.d.ts.map +1 -0
- package/dist/services/types.js +2 -0
- package/dist/storage/SnapshotManager.d.ts +5 -0
- package/dist/storage/SnapshotManager.d.ts.map +1 -0
- package/dist/storage/SnapshotManager.js +32 -0
- package/dist/types/index.d.ts +95 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +7 -0
- package/dist/utils/DOMUtils.d.ts +49 -0
- package/dist/utils/DOMUtils.d.ts.map +1 -0
- package/dist/utils/DOMUtils.js +466 -0
- package/dist/utils/FileLock.d.ts +44 -0
- package/dist/utils/FileLock.d.ts.map +1 -0
- package/dist/utils/FileLock.js +189 -0
- package/dist/utils/IsolatedDiff.d.ts +45 -0
- package/dist/utils/IsolatedDiff.d.ts.map +1 -0
- package/dist/utils/IsolatedDiff.js +382 -0
- package/dist/utils/StackUtils.d.ts +11 -0
- package/dist/utils/StackUtils.d.ts.map +1 -0
- package/dist/utils/StackUtils.js +120 -0
- package/dist/utils/SummaryTable.d.ts +17 -0
- package/dist/utils/SummaryTable.d.ts.map +1 -0
- package/dist/utils/SummaryTable.js +37 -0
- package/dist/utils/atomicWrite.d.ts +9 -0
- package/dist/utils/atomicWrite.d.ts.map +1 -0
- package/dist/utils/atomicWrite.js +83 -0
- package/dist/utils/logger.d.ts +59 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +123 -0
- package/dist/vendor/enquirer.d.ts +33 -0
- package/dist/vendor/enquirer.d.ts.map +1 -0
- package/dist/vendor/enquirer.js +11 -0
- package/package.json +71 -0
|
@@ -0,0 +1,1039 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/services/HealReportService.ts
|
|
3
|
+
//
|
|
4
|
+
// Sela Insights — Transparent Healing Report.
|
|
5
|
+
//
|
|
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.
|
|
22
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
25
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
26
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
27
|
+
}
|
|
28
|
+
Object.defineProperty(o, k2, desc);
|
|
29
|
+
}) : (function(o, m, k, k2) {
|
|
30
|
+
if (k2 === undefined) k2 = k;
|
|
31
|
+
o[k2] = m[k];
|
|
32
|
+
}));
|
|
33
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
34
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
35
|
+
}) : function(o, v) {
|
|
36
|
+
o["default"] = v;
|
|
37
|
+
});
|
|
38
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
39
|
+
var ownKeys = function(o) {
|
|
40
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
41
|
+
var ar = [];
|
|
42
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
43
|
+
return ar;
|
|
44
|
+
};
|
|
45
|
+
return ownKeys(o);
|
|
46
|
+
};
|
|
47
|
+
return function (mod) {
|
|
48
|
+
if (mod && mod.__esModule) return mod;
|
|
49
|
+
var result = {};
|
|
50
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
51
|
+
__setModuleDefault(result, mod);
|
|
52
|
+
return result;
|
|
53
|
+
};
|
|
54
|
+
})();
|
|
55
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
56
|
+
exports.sharedHealReport = exports.HealReportService = void 0;
|
|
57
|
+
exports.reportShardDir = reportShardDir;
|
|
58
|
+
exports.clearReportShards = clearReportShards;
|
|
59
|
+
exports.summariseSnapshot = summariseSnapshot;
|
|
60
|
+
const fs = __importStar(require("fs"));
|
|
61
|
+
const path = __importStar(require("path"));
|
|
62
|
+
const atomicWrite_1 = require("../utils/atomicWrite");
|
|
63
|
+
const logger_1 = require("../utils/logger");
|
|
64
|
+
const ReportGenerator_1 = require("./ReportGenerator");
|
|
65
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
66
|
+
// SIDECAR CONFIG
|
|
67
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
68
|
+
const HISTORY_FILE = ".sela-history.json";
|
|
69
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
70
|
+
// PER-WORKER REPORT SHARDS (SELA-5 concurrency safety)
|
|
71
|
+
//
|
|
72
|
+
// Local multi-worker runs (`playwright test --workers=N`) all share one cwd, so
|
|
73
|
+
// having every worker write `sela-report.json` directly is a last-writer-wins
|
|
74
|
+
// race — the final file keeps only one worker's events. Instead each worker
|
|
75
|
+
// writes its events to a private shard file under this dir (mirroring the
|
|
76
|
+
// pending-prompts ledger), and the single main-process reporter fans them in
|
|
77
|
+
// via ReportMergeService at onEnd. CI already shards across machines + merges,
|
|
78
|
+
// so it keeps the direct flushToDisk path.
|
|
79
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
80
|
+
const SHARD_SUBDIR = path.join(".sela", "report-shards");
|
|
81
|
+
/** Directory holding this run's per-worker report shards. */
|
|
82
|
+
function reportShardDir(cwd) {
|
|
83
|
+
return path.join(cwd, SHARD_SUBDIR);
|
|
84
|
+
}
|
|
85
|
+
/** Remove the shard directory and every shard beneath it. Idempotent. */
|
|
86
|
+
function clearReportShards(cwd) {
|
|
87
|
+
const dir = reportShardDir(cwd);
|
|
88
|
+
if (!fs.existsSync(dir))
|
|
89
|
+
return;
|
|
90
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
91
|
+
}
|
|
92
|
+
function shardRandomTag() {
|
|
93
|
+
return Math.random().toString(36).slice(2, 8);
|
|
94
|
+
}
|
|
95
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
96
|
+
// SNAPSHOT SUMMARISER
|
|
97
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
98
|
+
function summariseSnapshot(s) {
|
|
99
|
+
if (!s)
|
|
100
|
+
return null;
|
|
101
|
+
return {
|
|
102
|
+
tagName: s.tagName,
|
|
103
|
+
text: s.text,
|
|
104
|
+
role: s.role,
|
|
105
|
+
classes: s.classes ?? [],
|
|
106
|
+
attributes: s.attributes ?? {},
|
|
107
|
+
ancestry: (s.ancestry ?? []).map((a) => a.fingerprint),
|
|
108
|
+
closestLabel: s.anchors?.closestLabel ?? null,
|
|
109
|
+
rowAnchor: s.anchors?.rowAnchor ?? null,
|
|
110
|
+
isGhost: s.visibility?.isGhost ?? false,
|
|
111
|
+
ghostReason: s.visibility?.ghostReason ?? null,
|
|
112
|
+
isOccluded: s.visibility?.isOccluded ?? false,
|
|
113
|
+
isInShadowDom: s.isInShadowDom ?? false,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
117
|
+
// HealReportService
|
|
118
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
119
|
+
class HealReportService {
|
|
120
|
+
events = [];
|
|
121
|
+
startedAt = new Date().toISOString();
|
|
122
|
+
record(event) {
|
|
123
|
+
this.events.push(event);
|
|
124
|
+
}
|
|
125
|
+
size() {
|
|
126
|
+
return this.events.length;
|
|
127
|
+
}
|
|
128
|
+
clear() {
|
|
129
|
+
this.events = [];
|
|
130
|
+
this.startedAt = new Date().toISOString();
|
|
131
|
+
}
|
|
132
|
+
hasContent() {
|
|
133
|
+
return this.events.length > 0;
|
|
134
|
+
}
|
|
135
|
+
getHealedEvents() {
|
|
136
|
+
return this.events.filter((e) => e.kind === "HEALED");
|
|
137
|
+
}
|
|
138
|
+
getProtectedEvents() {
|
|
139
|
+
return this.events.filter((e) => e.kind === "PROTECTED");
|
|
140
|
+
}
|
|
141
|
+
getFailedEvents() {
|
|
142
|
+
return this.events.filter((e) => e.kind === "FAILED");
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Attach a Playwright failure screenshot (Base64 PNG, no data: prefix)
|
|
146
|
+
* onto the FAILED event(s) matching the supplied identity hints.
|
|
147
|
+
* Returns the number of events updated.
|
|
148
|
+
*/
|
|
149
|
+
attachFailureScreenshot(match, base64Png) {
|
|
150
|
+
if (!base64Png)
|
|
151
|
+
return 0;
|
|
152
|
+
const norm = (p) => (p ?? "").replace(/\\/g, "/").toLowerCase();
|
|
153
|
+
const targetFile = norm(match.sourceFile);
|
|
154
|
+
let touched = 0;
|
|
155
|
+
for (const ev of this.events) {
|
|
156
|
+
if (ev.kind !== "FAILED")
|
|
157
|
+
continue;
|
|
158
|
+
const evFile = norm(ev.sourceFile);
|
|
159
|
+
const fileMatch = !targetFile ||
|
|
160
|
+
!evFile ||
|
|
161
|
+
evFile === targetFile ||
|
|
162
|
+
evFile.endsWith(targetFile) ||
|
|
163
|
+
targetFile.endsWith(evFile);
|
|
164
|
+
const titleMatch = !match.testTitle || ev.testTitle === match.testTitle;
|
|
165
|
+
const idMatch = !match.stableId || ev.stableId === match.stableId;
|
|
166
|
+
if (fileMatch && titleMatch && idMatch) {
|
|
167
|
+
ev.failureScreenshotPng = base64Png;
|
|
168
|
+
touched++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return touched;
|
|
172
|
+
}
|
|
173
|
+
// ── History sidecar ─────────────────────────────────────────────────────
|
|
174
|
+
readHistory(targetDir) {
|
|
175
|
+
const histPath = path.join(targetDir, HISTORY_FILE);
|
|
176
|
+
try {
|
|
177
|
+
const raw = fs.readFileSync(histPath, "utf8");
|
|
178
|
+
const parsed = JSON.parse(raw);
|
|
179
|
+
if (!Array.isArray(parsed))
|
|
180
|
+
return [];
|
|
181
|
+
return parsed;
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
writeHistory(targetDir, runs) {
|
|
188
|
+
const histPath = path.join(targetDir, HISTORY_FILE);
|
|
189
|
+
try {
|
|
190
|
+
fs.writeFileSync(histPath, JSON.stringify(runs, null, 2), "utf8");
|
|
191
|
+
}
|
|
192
|
+
catch (err) {
|
|
193
|
+
logger_1.logger.warn(`Could not write history sidecar: ${err}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// ── Main flush ──────────────────────────────────────────────────────────
|
|
197
|
+
/**
|
|
198
|
+
* Generate and write `sela-report.html` + `sela-report.json` to targetDir.
|
|
199
|
+
* Also updates `.sela-history.json` with the current run's stats.
|
|
200
|
+
* Returns the absolute path of the HTML report, or null when there is
|
|
201
|
+
* nothing to write.
|
|
202
|
+
*/
|
|
203
|
+
flushToDisk(targetDir = process.cwd(), opts = {}) {
|
|
204
|
+
if (this.events.length === 0)
|
|
205
|
+
return null;
|
|
206
|
+
const priorHistory = this.readHistory(targetDir);
|
|
207
|
+
const data = ReportGenerator_1.ReportGenerator.generate(this.events, {
|
|
208
|
+
cwd: targetDir,
|
|
209
|
+
startedAt: this.startedAt,
|
|
210
|
+
historicalRuns: priorHistory,
|
|
211
|
+
runId: opts.runId ??
|
|
212
|
+
process.env["GITHUB_RUN_ID"] ??
|
|
213
|
+
process.env["CI_PIPELINE_ID"] ??
|
|
214
|
+
undefined,
|
|
215
|
+
});
|
|
216
|
+
return this.writeReportData(targetDir, data);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Render and persist a pre-computed {@link ReportData} payload to `targetDir`:
|
|
220
|
+
* • `.sela-history.json` (the rolling window from `data.historical_runs`)
|
|
221
|
+
* • `sela-report.html` (the embedded dashboard)
|
|
222
|
+
* • `sela-report.json` (the raw payload for CI consumers)
|
|
223
|
+
*
|
|
224
|
+
* Extracted from {@link flushToDisk} so the CI merge path
|
|
225
|
+
* (`ReportMergeService`) writes a shard-aggregated report through the exact
|
|
226
|
+
* same rendering / serialisation logic — no duplication.
|
|
227
|
+
*
|
|
228
|
+
* Unlike `flushToDisk`, this writes unconditionally: an empty-event payload
|
|
229
|
+
* still produces a report and a `healed:0` history row. That is the desired
|
|
230
|
+
* CI behaviour — a clean, zero-heal pipeline is a healthy data point that
|
|
231
|
+
* must keep the trend chart's x-axis cadence intact.
|
|
232
|
+
*
|
|
233
|
+
* Returns the absolute path of the written HTML report.
|
|
234
|
+
*/
|
|
235
|
+
writeReportData(targetDir, data) {
|
|
236
|
+
this.writeHistory(targetDir, data.historical_runs);
|
|
237
|
+
const html = renderHtml(data);
|
|
238
|
+
const htmlPath = path.join(targetDir, "sela-report.html");
|
|
239
|
+
fs.writeFileSync(htmlPath, html, "utf8");
|
|
240
|
+
const jsonPath = path.join(targetDir, "sela-report.json");
|
|
241
|
+
fs.writeFileSync(jsonPath, JSON.stringify({
|
|
242
|
+
generatedAt: data.generatedAt,
|
|
243
|
+
startedAt: data.startedAt,
|
|
244
|
+
cwd: data.cwd,
|
|
245
|
+
stats: data.stats,
|
|
246
|
+
roi: data.roi,
|
|
247
|
+
historical_runs: data.historical_runs,
|
|
248
|
+
events: data.events,
|
|
249
|
+
}, null, 2), "utf8");
|
|
250
|
+
return htmlPath;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Write THIS worker's buffered events to a private shard file under
|
|
254
|
+
* `<cwd>/.sela/report-shards/` — one file per worker process (pid +
|
|
255
|
+
* timestamp + random tag), so concurrent workers never contend on a shared
|
|
256
|
+
* report path. The main-process reporter fans them in via
|
|
257
|
+
* {@link ReportMergeService} at onEnd. Returns the shard path, or null when
|
|
258
|
+
* there is nothing to flush.
|
|
259
|
+
*
|
|
260
|
+
* Shard shape is `{ startedAt, events }`, which ReportMergeService.readShard
|
|
261
|
+
* Events accepts (it also tolerates a bare event array).
|
|
262
|
+
*/
|
|
263
|
+
flushShard(cwd = process.cwd()) {
|
|
264
|
+
if (this.events.length === 0)
|
|
265
|
+
return null;
|
|
266
|
+
const dir = reportShardDir(cwd);
|
|
267
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
268
|
+
const file = path.join(dir, `sela-shard-${process.pid}-${Date.now()}-${shardRandomTag()}.json`);
|
|
269
|
+
(0, atomicWrite_1.atomicWriteFile)(file, JSON.stringify({ startedAt: this.startedAt, events: this.events }, null, 2));
|
|
270
|
+
return file;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
exports.HealReportService = HealReportService;
|
|
274
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
275
|
+
// MODULE-LEVEL SINGLETON
|
|
276
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
277
|
+
exports.sharedHealReport = new HealReportService();
|
|
278
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
279
|
+
// HTML RENDERER
|
|
280
|
+
//
|
|
281
|
+
// Uses plain string concatenation throughout — no TypeScript template literals
|
|
282
|
+
// that could misinterpret the embedded CSS/JS content.
|
|
283
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
284
|
+
function renderHtml(data) {
|
|
285
|
+
// Embed the JSON payload. Escape </script> sequences and lone < so the
|
|
286
|
+
// string is safe inside a <script> block regardless of content.
|
|
287
|
+
const payload = JSON.stringify(data)
|
|
288
|
+
.replace(/</g, "\\u003c")
|
|
289
|
+
.replace(/\//g, "\\/");
|
|
290
|
+
return ("<!DOCTYPE html>\n" +
|
|
291
|
+
'<html lang="en">\n' +
|
|
292
|
+
"<head>\n" +
|
|
293
|
+
'<meta charset="UTF-8"/>\n' +
|
|
294
|
+
'<meta name="viewport" content="width=device-width,initial-scale=1.0"/>\n' +
|
|
295
|
+
"<title>Sela Insights \u2014 Healing Report</title>\n" +
|
|
296
|
+
'<link rel="preconnect" href="https://fonts.googleapis.com"/>\n' +
|
|
297
|
+
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>\n' +
|
|
298
|
+
'<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' +
|
|
299
|
+
'<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js"><\/script>\n' +
|
|
300
|
+
"<style>\n" +
|
|
301
|
+
REPORT_CSS +
|
|
302
|
+
"\n</style>\n" +
|
|
303
|
+
"</head>\n" +
|
|
304
|
+
"<body>\n" +
|
|
305
|
+
'<div class="bg-noise"></div>\n' +
|
|
306
|
+
'<div class="bg-grid"></div>\n' +
|
|
307
|
+
buildTopbar() +
|
|
308
|
+
buildOverviewView() +
|
|
309
|
+
buildDeepDiveView() +
|
|
310
|
+
buildFooter() +
|
|
311
|
+
'<div id="toast" class="toast hidden"></div>\n' +
|
|
312
|
+
"<script>window.__SELA_DATA__ = " +
|
|
313
|
+
payload +
|
|
314
|
+
";<\/script>\n" +
|
|
315
|
+
"<script>\n" +
|
|
316
|
+
REPORT_JS +
|
|
317
|
+
"\n<\/script>\n" +
|
|
318
|
+
"</body>\n" +
|
|
319
|
+
"</html>");
|
|
320
|
+
}
|
|
321
|
+
function buildTopbar() {
|
|
322
|
+
return ('<nav class="topbar">\n' +
|
|
323
|
+
' <div class="wrap topbar-inner">\n' +
|
|
324
|
+
' <div class="brand">\n' +
|
|
325
|
+
' <div class="brand-hex"></div>\n' +
|
|
326
|
+
' <span class="brand-name">Sela</span>\n' +
|
|
327
|
+
' <span class="brand-sep">/</span>\n' +
|
|
328
|
+
' <span class="brand-sub">Healing Report</span>\n' +
|
|
329
|
+
" </div>\n" +
|
|
330
|
+
' <div style="display:flex;align-items:center;gap:12px">\n' +
|
|
331
|
+
' <div class="view-toggle">\n' +
|
|
332
|
+
' <button class="vt-btn active" id="btn-overview" onclick="setView(\'overview\')">Overview</button>\n' +
|
|
333
|
+
' <button class="vt-btn" id="btn-deepdive" onclick="setView(\'deepdive\')">Deep Dive</button>\n' +
|
|
334
|
+
" </div>\n" +
|
|
335
|
+
' <div class="topbar-meta">\n' +
|
|
336
|
+
' <div class="topbar-pill live"><span class="dot"></span>Run complete</div>\n' +
|
|
337
|
+
" </div>\n" +
|
|
338
|
+
" </div>\n" +
|
|
339
|
+
" </div>\n" +
|
|
340
|
+
"</nav>\n");
|
|
341
|
+
}
|
|
342
|
+
function buildOverviewView() {
|
|
343
|
+
return ('<div id="view-overview">\n' +
|
|
344
|
+
'<div class="wrap">\n' +
|
|
345
|
+
' <div class="hero">\n' +
|
|
346
|
+
' <div class="exec-row">\n' +
|
|
347
|
+
" <div>\n" +
|
|
348
|
+
' <div class="exec-eyebrow"><span class="ei"></span>Sela Intelligence Run<span class="ei"></span></div>\n' +
|
|
349
|
+
' <h1 class="exec-h1" id="exec-h1"></h1>\n' +
|
|
350
|
+
' <p class="exec-sub" id="exec-sub"></p>\n' +
|
|
351
|
+
" </div>\n" +
|
|
352
|
+
' <div style="text-align:right;font-size:12px;color:var(--text3)">\n' +
|
|
353
|
+
' <div id="meta-gen" style="margin-bottom:3px"></div>\n' +
|
|
354
|
+
' <div id="meta-cwd" style="font-family:var(--mono);font-size:11px;word-break:break-all;max-width:280px"></div>\n' +
|
|
355
|
+
" </div>\n" +
|
|
356
|
+
" </div>\n" +
|
|
357
|
+
' <div class="kpi-row">\n' +
|
|
358
|
+
' <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' +
|
|
359
|
+
' <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' +
|
|
360
|
+
' <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' +
|
|
361
|
+
' <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' +
|
|
362
|
+
" </div>\n" +
|
|
363
|
+
' <div class="charts-row">\n' +
|
|
364
|
+
' <div class="chart-card">\n' +
|
|
365
|
+
' <div class="chart-label">Run Distribution</div>\n' +
|
|
366
|
+
' <div class="donut-wrap">\n' +
|
|
367
|
+
' <canvas id="donut-chart"></canvas>\n' +
|
|
368
|
+
' <div class="donut-center"><div class="donut-total" id="donut-total"></div><div class="donut-total-l">Events</div></div>\n' +
|
|
369
|
+
" </div>\n" +
|
|
370
|
+
' <div class="donut-legend" id="donut-legend"></div>\n' +
|
|
371
|
+
" </div>\n" +
|
|
372
|
+
' <div class="chart-card">\n' +
|
|
373
|
+
' <div class="trend-header">\n' +
|
|
374
|
+
' <div class="chart-label" style="margin-bottom:0">Sela Impact \u2014 Last 14 Runs</div>\n' +
|
|
375
|
+
' <div class="trend-badge" id="trend-badge"></div>\n' +
|
|
376
|
+
" </div>\n" +
|
|
377
|
+
' <div class="trend-canvas-wrap"><canvas id="trend-chart"></canvas></div>\n' +
|
|
378
|
+
' <div style="margin-top:16px">\n' +
|
|
379
|
+
' <div class="chart-label">Recent Runs</div>\n' +
|
|
380
|
+
' <div id="hist-table">\n' +
|
|
381
|
+
' <div class="hist-row hdr">\n' +
|
|
382
|
+
' <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' +
|
|
383
|
+
" </div>\n" +
|
|
384
|
+
" </div>\n" +
|
|
385
|
+
" </div>\n" +
|
|
386
|
+
" </div>\n" +
|
|
387
|
+
" </div>\n" +
|
|
388
|
+
' <div style="margin-bottom:32px;display:flex;gap:10px;flex-wrap:wrap">\n' +
|
|
389
|
+
' <button class="btn btn-primary" onclick="setView(\'deepdive\')">→ Inspect all events</button>\n' +
|
|
390
|
+
' <button class="btn" onclick="copyJson()">⎘ Copy JSON</button>\n' +
|
|
391
|
+
" </div>\n" +
|
|
392
|
+
" </div>\n" +
|
|
393
|
+
"</div>\n" +
|
|
394
|
+
"</div>\n");
|
|
395
|
+
}
|
|
396
|
+
function buildDeepDiveView() {
|
|
397
|
+
return ('<div id="view-deepdive">\n' +
|
|
398
|
+
'<div class="wrap" style="padding-top:24px">\n' +
|
|
399
|
+
' <div class="section-hdr">\n' +
|
|
400
|
+
" <h2>Engineering Deep Dive</h2>\n" +
|
|
401
|
+
' <div class="sh-count" id="dd-count"></div>\n' +
|
|
402
|
+
' <div class="sh-line"></div>\n' +
|
|
403
|
+
' <button class="btn" onclick="setView(\'overview\')" style="font-size:11.5px;padding:4px 10px">← Overview</button>\n' +
|
|
404
|
+
" </div>\n" +
|
|
405
|
+
' <div class="filter-bar">\n' +
|
|
406
|
+
' <div class="fb-chips">\n' +
|
|
407
|
+
' <div class="chip c-all on" data-filter="ALL"><span>All</span></div>\n' +
|
|
408
|
+
' <div class="chip c-heal" data-filter="HEALED"><span class="chip-dot"></span>Healed</div>\n' +
|
|
409
|
+
' <div class="chip c-prot" data-filter="PROTECTED"><span class="chip-dot"></span>Protected \xb7 Bug Caught</div>\n' +
|
|
410
|
+
' <div class="chip c-fail" data-filter="FAILED"><span class="chip-dot"></span>Failed</div>\n' +
|
|
411
|
+
" </div>\n" +
|
|
412
|
+
' <div class="fb-search">\n' +
|
|
413
|
+
' <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' +
|
|
414
|
+
' <input type="text" id="search-input" placeholder="selector, file, test name\u2026"/>\n' +
|
|
415
|
+
" </div>\n" +
|
|
416
|
+
" </div>\n" +
|
|
417
|
+
' <div class="events" id="events-list"></div>\n' +
|
|
418
|
+
' <div class="empty-state hidden" id="empty-state">\n' +
|
|
419
|
+
' <div class="es-icon">\u2205</div>\n' +
|
|
420
|
+
' <div class="es-title">Nothing matches</div>\n' +
|
|
421
|
+
" <div>Try a different filter or clear the search.</div>\n" +
|
|
422
|
+
" </div>\n" +
|
|
423
|
+
"</div>\n" +
|
|
424
|
+
"</div>\n");
|
|
425
|
+
}
|
|
426
|
+
function buildFooter() {
|
|
427
|
+
return ('<footer class="footer">\n' +
|
|
428
|
+
' <div class="wrap footer-inner">\n' +
|
|
429
|
+
' <div>Generated by <strong>sela-core</strong> \xb7 <span id="footer-cwd" style="font-family:var(--mono);font-size:11px"></span></div>\n' +
|
|
430
|
+
" <div>Sela is a transparent pair-programmer. Every decision is yours to keep, roll back, or report.</div>\n" +
|
|
431
|
+
" </div>\n" +
|
|
432
|
+
"</footer>\n");
|
|
433
|
+
}
|
|
434
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
435
|
+
// CSS — stored as a plain string constant (no backtick template literal)
|
|
436
|
+
// so TypeScript has no chance to misparse it.
|
|
437
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
438
|
+
/* eslint-disable prettier/prettier */
|
|
439
|
+
const REPORT_CSS = [
|
|
440
|
+
"*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}",
|
|
441
|
+
":root{",
|
|
442
|
+
" --bg0:#060709;--bg1:#0b0d12;--bg2:#10131a;--bg3:#161923;--bg4:#1c2030;",
|
|
443
|
+
" --line:#1e2336;--line2:#262d42;--line3:#2e3650;",
|
|
444
|
+
" --text:#dde2f0;--text2:#8b93b0;--text3:#5a6180;",
|
|
445
|
+
" --cyan:#00d4ff;--cyan-dim:rgba(0,212,255,.12);--cyan-glow:rgba(0,212,255,.25);",
|
|
446
|
+
" --purple:#a259ff;--purple-dim:rgba(162,89,255,.12);--purple-glow:rgba(162,89,255,.25);",
|
|
447
|
+
" --red:#ff4d6a;--red-dim:rgba(255,77,106,.12);--red-glow:rgba(255,77,106,.25);",
|
|
448
|
+
" --green:#00e5a0;--green-dim:rgba(0,229,160,.1);",
|
|
449
|
+
" --amber:#ffb84d;--amber-dim:rgba(255,184,77,.1);",
|
|
450
|
+
" --radius:10px;--radius-lg:14px;--radius-xl:20px;",
|
|
451
|
+
" --mono:'Geist Mono',ui-monospace,monospace;",
|
|
452
|
+
" --sans:'Geist',system-ui,sans-serif;",
|
|
453
|
+
"}",
|
|
454
|
+
"html{background:var(--bg0);color:var(--text);font-family:var(--sans);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased}",
|
|
455
|
+
"body{min-height:100vh;overflow-x:hidden}",
|
|
456
|
+
"a{color:var(--cyan);text-decoration:none}",
|
|
457
|
+
"button{font-family:var(--sans);cursor:pointer}",
|
|
458
|
+
"input{font-family:var(--sans)}",
|
|
459
|
+
"::-webkit-scrollbar{width:6px;height:6px}",
|
|
460
|
+
"::-webkit-scrollbar-track{background:var(--bg1)}",
|
|
461
|
+
"::-webkit-scrollbar-thumb{background:var(--line3);border-radius:3px}",
|
|
462
|
+
".bg-noise{",
|
|
463
|
+
" position:fixed;inset:0;z-index:-2;",
|
|
464
|
+
" background:",
|
|
465
|
+
" radial-gradient(ellipse 80% 50% at 10% 10%,rgba(0,212,255,.07) 0%,transparent 60%),",
|
|
466
|
+
" radial-gradient(ellipse 60% 40% at 90% 20%,rgba(162,89,255,.08) 0%,transparent 60%),",
|
|
467
|
+
" radial-gradient(ellipse 50% 60% at 50% 90%,rgba(162,89,255,.05) 0%,transparent 60%),",
|
|
468
|
+
" var(--bg0);",
|
|
469
|
+
"}",
|
|
470
|
+
".bg-grid{",
|
|
471
|
+
" position:fixed;inset:0;z-index:-1;pointer-events:none;",
|
|
472
|
+
" background-image:linear-gradient(rgba(255,255,255,.018) 1px,transparent 1px),linear-gradient(90deg,rgba(255,255,255,.018) 1px,transparent 1px);",
|
|
473
|
+
" background-size:40px 40px;",
|
|
474
|
+
" mask-image:radial-gradient(ellipse at 50% 0%,black 0%,transparent 65%);",
|
|
475
|
+
"}",
|
|
476
|
+
".wrap{max-width:1320px;margin:0 auto;padding:0 24px}",
|
|
477
|
+
".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;}",
|
|
478
|
+
".topbar-inner{height:52px;display:flex;align-items:center;justify-content:space-between}",
|
|
479
|
+
".brand{display:flex;align-items:center;gap:10px}",
|
|
480
|
+
".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;}",
|
|
481
|
+
".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;}",
|
|
482
|
+
".brand-name{font-weight:600;font-size:14px;letter-spacing:-.2px}",
|
|
483
|
+
".brand-sep{color:var(--text3);font-size:13px}",
|
|
484
|
+
".brand-sub{font-size:12px;color:var(--text3);letter-spacing:.6px;text-transform:uppercase}",
|
|
485
|
+
".topbar-meta{font-size:12px;color:var(--text3);display:flex;align-items:center;gap:16px}",
|
|
486
|
+
".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);}",
|
|
487
|
+
".topbar-pill .dot{width:5px;height:5px;border-radius:50%}",
|
|
488
|
+
".topbar-pill.live .dot{background:var(--green);box-shadow:0 0 6px var(--green);animation:blink 2s ease-in-out infinite}",
|
|
489
|
+
"@keyframes blink{0%,100%{opacity:1}50%{opacity:.4}}",
|
|
490
|
+
".view-toggle{display:flex;gap:2px;padding:3px;background:var(--bg2);border:1px solid var(--line);border-radius:var(--radius)}",
|
|
491
|
+
".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}",
|
|
492
|
+
".vt-btn.active{background:var(--bg4);color:var(--text);box-shadow:0 1px 3px rgba(0,0,0,.4)}",
|
|
493
|
+
".hero{padding:32px 0 0}",
|
|
494
|
+
".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}",
|
|
495
|
+
"@media(max-width:800px){.exec-row{grid-template-columns:1fr}}",
|
|
496
|
+
".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}",
|
|
497
|
+
".exec-eyebrow .ei{width:14px;height:1px;background:var(--cyan);opacity:.6}",
|
|
498
|
+
".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}",
|
|
499
|
+
".exec-sub{font-size:14px;color:var(--text2);max-width:52ch;line-height:1.6}",
|
|
500
|
+
".kpi-row{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:28px}",
|
|
501
|
+
"@media(max-width:900px){.kpi-row{grid-template-columns:repeat(2,1fr)}}",
|
|
502
|
+
".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}",
|
|
503
|
+
".kpi::before{content:'';position:absolute;inset:0;opacity:.5;background:linear-gradient(180deg,rgba(255,255,255,.03) 0%,transparent 100%);pointer-events:none}",
|
|
504
|
+
".kpi:hover{transform:translateY(-2px);border-color:var(--line3)}",
|
|
505
|
+
".kpi-l{font-size:10.5px;letter-spacing:1.2px;text-transform:uppercase;color:var(--text3);margin-bottom:6px}",
|
|
506
|
+
".kpi-v{font-size:28px;font-weight:700;line-height:1;font-variant-numeric:tabular-nums;margin-bottom:4px}",
|
|
507
|
+
".kpi-f{font-size:11.5px;color:var(--text3)}",
|
|
508
|
+
".kpi-accent{position:absolute;top:0;right:0;width:3px;height:100%;border-radius:0 var(--radius-lg) var(--radius-lg) 0}",
|
|
509
|
+
".kpi.k-time .kpi-v{background:linear-gradient(135deg,var(--cyan),var(--purple));-webkit-background-clip:text;background-clip:text;color:transparent}",
|
|
510
|
+
".kpi.k-time .kpi-accent{background:linear-gradient(180deg,var(--cyan),var(--purple))}",
|
|
511
|
+
".kpi.k-healed .kpi-v{color:var(--cyan)}.kpi.k-healed .kpi-accent{background:var(--cyan)}",
|
|
512
|
+
".kpi.k-prot .kpi-v{color:var(--purple)}.kpi.k-prot .kpi-accent{background:var(--purple)}",
|
|
513
|
+
".kpi.k-fail .kpi-v{color:var(--red)}.kpi.k-fail .kpi-accent{background:var(--red)}",
|
|
514
|
+
".charts-row{display:grid;grid-template-columns:280px 1fr;gap:18px;margin-bottom:32px}",
|
|
515
|
+
"@media(max-width:900px){.charts-row{grid-template-columns:1fr}}",
|
|
516
|
+
".chart-card{background:var(--bg2);border:1px solid var(--line);border-radius:var(--radius-lg);padding:20px}",
|
|
517
|
+
".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}",
|
|
518
|
+
".chart-label::after{content:'';flex:1;height:1px;background:var(--line)}",
|
|
519
|
+
".donut-wrap{position:relative;width:160px;height:160px;margin:0 auto 16px}",
|
|
520
|
+
".donut-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;pointer-events:none}",
|
|
521
|
+
".donut-total{font-size:28px;font-weight:700;line-height:1}",
|
|
522
|
+
".donut-total-l{font-size:10px;letter-spacing:1px;text-transform:uppercase;color:var(--text3);margin-top:2px}",
|
|
523
|
+
".donut-legend{display:flex;flex-direction:column;gap:7px}",
|
|
524
|
+
".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}",
|
|
525
|
+
".dl-row:hover,.dl-row.active{border-color:var(--line3);background:var(--bg3)}",
|
|
526
|
+
".dl-swatch{width:8px;height:8px;border-radius:2px;flex-shrink:0}",
|
|
527
|
+
".dl-name{font-size:12.5px;color:var(--text2);flex:1}",
|
|
528
|
+
".dl-val{font-size:12.5px;font-weight:600;font-variant-numeric:tabular-nums}",
|
|
529
|
+
".dl-pct{font-size:11px;color:var(--text3);margin-left:4px}",
|
|
530
|
+
".trend-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}",
|
|
531
|
+
".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)}",
|
|
532
|
+
".trend-canvas-wrap{position:relative;width:100%;height:200px}",
|
|
533
|
+
".section-hdr{display:flex;align-items:center;gap:10px;margin-bottom:14px}",
|
|
534
|
+
".section-hdr h2{font-size:13px;font-weight:600;letter-spacing:.4px}",
|
|
535
|
+
".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}",
|
|
536
|
+
".section-hdr .sh-line{flex:1;height:1px;background:var(--line)}",
|
|
537
|
+
".filter-bar{display:flex;align-items:center;gap:10px;margin-bottom:16px;flex-wrap:wrap}",
|
|
538
|
+
".fb-chips{display:flex;gap:6px;flex-wrap:wrap}",
|
|
539
|
+
".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}",
|
|
540
|
+
".chip:hover{color:var(--text);border-color:var(--line3)}",
|
|
541
|
+
".chip.c-all.on{border-color:rgba(255,255,255,.3);color:#fff;background:var(--bg3)}",
|
|
542
|
+
".chip.c-heal.on{border-color:var(--cyan);color:var(--cyan);background:var(--cyan-dim)}",
|
|
543
|
+
".chip.c-prot.on{border-color:var(--purple);color:var(--purple);background:var(--purple-dim)}",
|
|
544
|
+
".chip.c-fail.on{border-color:var(--red);color:var(--red);background:var(--red-dim)}",
|
|
545
|
+
".chip .chip-dot{width:5px;height:5px;border-radius:50%}",
|
|
546
|
+
".c-heal .chip-dot,.chip.c-heal.on .chip-dot{background:var(--cyan)}",
|
|
547
|
+
".c-prot .chip-dot,.chip.c-prot.on .chip-dot{background:var(--purple)}",
|
|
548
|
+
".c-fail .chip-dot,.chip.c-fail.on .chip-dot{background:var(--red)}",
|
|
549
|
+
".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}",
|
|
550
|
+
".fb-search input{background:none;border:none;outline:none;color:var(--text);font-size:12.5px;width:200px}",
|
|
551
|
+
".fb-search input::placeholder{color:var(--text3)}",
|
|
552
|
+
".events{display:flex;flex-direction:column;gap:8px}",
|
|
553
|
+
".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}",
|
|
554
|
+
"@keyframes cardIn{0%{opacity:0;transform:translateY(6px)}100%{opacity:1;transform:translateY(0)}}",
|
|
555
|
+
".ecard:hover{border-color:var(--line3)}",
|
|
556
|
+
".ecard.ec-healed{border-left:2px solid var(--cyan)}",
|
|
557
|
+
".ecard.ec-protected{border-left:2px solid var(--purple)}",
|
|
558
|
+
".ecard.ec-failed{border-left:2px solid var(--red)}",
|
|
559
|
+
".ecard.ec-protected .ecard-head{background:linear-gradient(90deg,rgba(162,89,255,.04),transparent)}",
|
|
560
|
+
".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}",
|
|
561
|
+
".ecard-head:hover{background:var(--bg3)}",
|
|
562
|
+
".ecard.open .ecard-head{background:var(--bg3)}",
|
|
563
|
+
".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}",
|
|
564
|
+
".sb-healed{color:var(--cyan);background:var(--cyan-dim)}",
|
|
565
|
+
".sb-protected{color:var(--purple);background:var(--purple-dim);animation:pulseborder 2.5s ease-in-out infinite}",
|
|
566
|
+
"@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)}}",
|
|
567
|
+
".sb-failed{color:var(--red);background:var(--red-dim)}",
|
|
568
|
+
".sb-dot{width:5px;height:5px;border-radius:50%;background:currentColor;box-shadow:0 0 5px currentColor}",
|
|
569
|
+
".ecard-title{min-width:0}",
|
|
570
|
+
".ecard-selector{font-family:var(--mono);font-size:12.5px;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:2px}",
|
|
571
|
+
".ecard-meta{font-size:11.5px;color:var(--text3);display:flex;align-items:center;gap:5px;flex-wrap:wrap}",
|
|
572
|
+
".ecard-meta a{color:var(--text2);font-family:var(--mono);font-size:11px}",
|
|
573
|
+
".ecard-meta a:hover{color:var(--cyan)}",
|
|
574
|
+
".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}",
|
|
575
|
+
".ecard-tag.t-iframe{color:var(--cyan);border-color:rgba(0,212,255,.2)}",
|
|
576
|
+
".ecard-tag.t-shadow{color:var(--purple);border-color:rgba(162,89,255,.2)}",
|
|
577
|
+
".ecard-tag.t-dryrun{color:var(--amber);border-color:rgba(255,184,77,.2)}",
|
|
578
|
+
".conf-badge{font-size:11px;font-weight:600;padding:3px 9px;border-radius:6px;font-variant-numeric:tabular-nums;white-space:nowrap}",
|
|
579
|
+
".conf-high{background:rgba(0,229,160,.12);color:var(--green);border:1px solid rgba(0,229,160,.2)}",
|
|
580
|
+
".conf-mid{background:rgba(255,184,77,.1);color:var(--amber);border:1px solid rgba(255,184,77,.2)}",
|
|
581
|
+
".conf-low{background:rgba(255,77,106,.1);color:var(--red);border:1px solid rgba(255,77,106,.2)}",
|
|
582
|
+
".chev{width:16px;height:16px;color:var(--text3);flex-shrink:0;transition:transform .25s ease}",
|
|
583
|
+
".ecard.open .chev{transform:rotate(90deg)}",
|
|
584
|
+
".ecard-body{display:none;padding:0 16px 18px;border-top:1px solid var(--line)}",
|
|
585
|
+
".ecard.open .ecard-body{display:block;animation:bodyIn .3s ease both}",
|
|
586
|
+
"@keyframes bodyIn{0%{opacity:0;transform:translateY(-3px)}100%{opacity:1;transform:translateY(0)}}",
|
|
587
|
+
".body-section{margin-top:16px}",
|
|
588
|
+
".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}",
|
|
589
|
+
".bs-title .bst-dot{width:5px;height:5px;border-radius:50%;background:var(--purple);box-shadow:0 0 6px var(--purple)}",
|
|
590
|
+
".diff-block{background:var(--bg1);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}",
|
|
591
|
+
".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)}",
|
|
592
|
+
".diff-bar .sep{opacity:.3}",
|
|
593
|
+
".diff-line{display:grid;grid-template-columns:22px 48px 1fr;font-family:var(--mono);font-size:12px;line-height:1.6}",
|
|
594
|
+
".diff-line .gutter{text-align:center;color:var(--text3)}",
|
|
595
|
+
".diff-line .ln{text-align:right;padding-right:10px;color:var(--text3)}",
|
|
596
|
+
".diff-line .code{white-space:pre-wrap;word-break:break-word;padding-right:12px}",
|
|
597
|
+
".diff-line.dl-add{background:rgba(0,229,160,.07)}",
|
|
598
|
+
".diff-line.dl-add .gutter,.diff-line.dl-add .code{color:var(--green)}",
|
|
599
|
+
".diff-line.dl-del{background:rgba(255,77,106,.07)}",
|
|
600
|
+
".diff-line.dl-del .gutter,.diff-line.dl-del .code{color:var(--red)}",
|
|
601
|
+
".diff-line.dl-ctx .code{color:var(--text3)}",
|
|
602
|
+
".diff-line.dl-hunk{background:rgba(0,212,255,.04)}",
|
|
603
|
+
".diff-line.dl-hunk .code{color:rgba(0,212,255,.6);font-size:11px}",
|
|
604
|
+
".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}",
|
|
605
|
+
"@media(max-width:700px){.auditor-wrap{grid-template-columns:1fr}}",
|
|
606
|
+
".gauge-ring{position:relative;width:130px;height:130px;margin:0 auto}",
|
|
607
|
+
".gauge-ring svg{width:100%;height:100%;transform:rotate(-90deg)}",
|
|
608
|
+
".gauge-ring .gt{stroke:var(--line3);fill:none;stroke-width:9}",
|
|
609
|
+
".gauge-ring .gb{fill:none;stroke-width:9;stroke-linecap:round;transition:stroke-dasharray 1.2s cubic-bezier(.4,0,.2,1)}",
|
|
610
|
+
".gauge-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center}",
|
|
611
|
+
".gauge-pct{font-size:26px;font-weight:700;font-variant-numeric:tabular-nums;line-height:1}",
|
|
612
|
+
".gauge-sub{font-size:9.5px;letter-spacing:1px;text-transform:uppercase;color:var(--text3);margin-top:2px}",
|
|
613
|
+
".gb-consistent{stroke:var(--green)}.gb-suspicious{stroke:var(--amber)}.gb-inconsistent{stroke:var(--red)}",
|
|
614
|
+
".pct-consistent{color:var(--green)}.pct-suspicious{color:var(--amber)}.pct-inconsistent{color:var(--red)}",
|
|
615
|
+
".audit-details{display:flex;flex-direction:column;gap:7px}",
|
|
616
|
+
".ad-row{display:flex;gap:8px;font-size:12.5px;align-items:flex-start}",
|
|
617
|
+
".ad-label{color:var(--text3);font-size:11px;letter-spacing:.5px;text-transform:uppercase;min-width:110px;flex-shrink:0;padding-top:1px}",
|
|
618
|
+
".ad-val{color:var(--text)}",
|
|
619
|
+
".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}",
|
|
620
|
+
".verdict-pill{display:inline-block;padding:2px 8px;border-radius:5px;font-size:10.5px;font-weight:700;letter-spacing:.8px;text-transform:uppercase}",
|
|
621
|
+
".vp-consistent{color:var(--green);background:var(--green-dim);border:1px solid rgba(0,229,160,.2)}",
|
|
622
|
+
".vp-suspicious{color:var(--amber);background:var(--amber-dim);border:1px solid rgba(255,184,77,.2)}",
|
|
623
|
+
".vp-inconsistent{color:var(--red);background:var(--red-dim);border:1px solid rgba(255,77,106,.2)}",
|
|
624
|
+
".cot{display:flex;flex-direction:column;gap:7px}",
|
|
625
|
+
".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)}",
|
|
626
|
+
".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}",
|
|
627
|
+
".cot-t{color:var(--text2);font-size:12.5px;line-height:1.5}",
|
|
628
|
+
".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}",
|
|
629
|
+
".dna-table th,.dna-table td{padding:7px 11px;text-align:left;border-bottom:1px solid var(--line)}",
|
|
630
|
+
".dna-table th{background:var(--bg3);font-weight:600;color:var(--text3);font-size:10.5px;letter-spacing:.6px;text-transform:uppercase}",
|
|
631
|
+
".dna-table tr:last-child td{border-bottom:none}",
|
|
632
|
+
".dna-table td.field{color:var(--text3);width:20%}",
|
|
633
|
+
".dna-table td.val{font-family:var(--mono);font-size:11.5px}",
|
|
634
|
+
".dna-table td.icon{width:32px;text-align:center}",
|
|
635
|
+
".dna-table tr.matched td.icon{color:var(--green)}",
|
|
636
|
+
".dna-table tr.changed td.val.new{color:var(--amber)}",
|
|
637
|
+
".dna-table tr.changed td.val.old{color:var(--text3);text-decoration:line-through dotted}",
|
|
638
|
+
".dna-table tr.changed td.icon{color:var(--amber)}",
|
|
639
|
+
".dna-table tr.missing td.icon{color:var(--text3)}",
|
|
640
|
+
".alts{display:flex;flex-direction:column;gap:5px}",
|
|
641
|
+
".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}",
|
|
642
|
+
".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}",
|
|
643
|
+
".alt-copy:hover{color:var(--text);border-color:var(--line3)}",
|
|
644
|
+
".actions{display:flex;gap:8px;flex-wrap:wrap;margin-top:16px;padding-top:14px;border-top:1px dashed var(--line2)}",
|
|
645
|
+
".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}",
|
|
646
|
+
".btn:hover{color:var(--text);border-color:var(--line3);transform:translateY(-1px)}",
|
|
647
|
+
".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)}",
|
|
648
|
+
".btn.btn-primary:hover{box-shadow:0 4px 20px rgba(0,212,255,.2)}",
|
|
649
|
+
".btn.btn-danger{border-color:rgba(255,77,106,.3);color:var(--red)}",
|
|
650
|
+
".btn.btn-danger:hover{background:var(--red-dim)}",
|
|
651
|
+
".btn[data-done='true']{border-color:rgba(0,229,160,.3);color:var(--green);background:var(--green-dim)}",
|
|
652
|
+
".fail-img-wrap{background:var(--bg1);border:1px solid var(--line);border-radius:var(--radius);overflow:hidden}",
|
|
653
|
+
".fail-img-wrap img{display:block;max-width:100%;height:auto}",
|
|
654
|
+
".fail-img-meta{padding:6px 11px;font-size:11px;color:var(--text3);font-family:var(--mono)}",
|
|
655
|
+
".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}",
|
|
656
|
+
".prot-notice strong{color:var(--purple)}",
|
|
657
|
+
".row2{display:grid;grid-template-columns:1fr 1fr;gap:14px}",
|
|
658
|
+
"@media(max-width:700px){.row2{grid-template-columns:1fr}}",
|
|
659
|
+
".muted-block{padding:12px 14px;background:var(--bg1);border:1px dashed var(--line);border-radius:var(--radius);font-size:12px;color:var(--text3)}",
|
|
660
|
+
".hist-row{display:grid;grid-template-columns:1fr repeat(5,80px);gap:0;font-size:12px}",
|
|
661
|
+
".hist-row .hc{padding:6px 10px;border-bottom:1px solid var(--line)}",
|
|
662
|
+
".hist-row.hdr .hc{color:var(--text3);font-size:10.5px;letter-spacing:.6px;text-transform:uppercase;background:var(--bg3)}",
|
|
663
|
+
".hist-row:not(.hdr):hover .hc{background:var(--bg3)}",
|
|
664
|
+
".hist-row .hc:first-child{color:var(--text2)}",
|
|
665
|
+
".hist-saved{color:var(--cyan);font-weight:600}",
|
|
666
|
+
".empty-state{text-align:center;padding:60px 20px;color:var(--text3);border:1px dashed var(--line2);border-radius:var(--radius-xl);margin-top:12px}",
|
|
667
|
+
".es-icon{font-size:36px;margin-bottom:10px}",
|
|
668
|
+
".es-title{font-size:15px;color:var(--text2);margin-bottom:4px}",
|
|
669
|
+
".hidden{display:none!important}",
|
|
670
|
+
".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}",
|
|
671
|
+
"@keyframes toastIn{0%{opacity:0;transform:translate(-50%,8px)}100%{opacity:1;transform:translate(-50%,0)}}",
|
|
672
|
+
".footer{border-top:1px solid var(--line);margin-top:60px;padding:20px 0;font-size:12px;color:var(--text3)}",
|
|
673
|
+
".footer-inner{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:10px}",
|
|
674
|
+
"#view-overview{display:block}",
|
|
675
|
+
"#view-deepdive{display:none}",
|
|
676
|
+
"body.view-deepdive #view-overview{display:none}",
|
|
677
|
+
"body.view-deepdive #view-deepdive{display:block}",
|
|
678
|
+
].join("\n");
|
|
679
|
+
/* eslint-enable prettier/prettier */
|
|
680
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
681
|
+
// FRONT-END JS
|
|
682
|
+
//
|
|
683
|
+
// Stored as a plain Array.join() string — no TypeScript template literal so
|
|
684
|
+
// the browser-side ${...} expressions and backtick strings are never
|
|
685
|
+
// interpreted by the TypeScript compiler.
|
|
686
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
687
|
+
/* eslint-disable prettier/prettier */
|
|
688
|
+
const REPORT_JS = [
|
|
689
|
+
"(function(){",
|
|
690
|
+
"'use strict';",
|
|
691
|
+
"var DATA=window.__SELA_DATA__;",
|
|
692
|
+
"function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/\"/g,'"');}",
|
|
693
|
+
"function fmt(iso){try{return new Date(iso).toLocaleString();}catch(e){return iso;}}",
|
|
694
|
+
"function fmtPct(v){return(typeof v==='number'&&isFinite(v))?v+'%':'n/a';}",
|
|
695
|
+
"function fmtMin(m){var h=Math.floor(m/60),r=m%60;return h>0?h+'h '+r+'m':m+'m';}",
|
|
696
|
+
"function $(id){return document.getElementById(id);}",
|
|
697
|
+
"function qsa(sel,root){return Array.from((root||document).querySelectorAll(sel));}",
|
|
698
|
+
// ── View toggle
|
|
699
|
+
"function setView(v){",
|
|
700
|
+
" document.body.classList.toggle('view-deepdive',v==='deepdive');",
|
|
701
|
+
" $('btn-overview').classList.toggle('active',v==='overview');",
|
|
702
|
+
" $('btn-deepdive').classList.toggle('active',v==='deepdive');",
|
|
703
|
+
"}",
|
|
704
|
+
"window.setView=setView;",
|
|
705
|
+
// ── Toast
|
|
706
|
+
"var toastTimer;",
|
|
707
|
+
"function toast(msg){var t=$('toast');t.textContent=msg;t.classList.remove('hidden');clearTimeout(toastTimer);toastTimer=setTimeout(function(){t.classList.add('hidden');},2600);}",
|
|
708
|
+
"window.copyJson=function(){navigator.clipboard&&navigator.clipboard.writeText(JSON.stringify(DATA,null,2)).then(function(){toast('JSON copied to clipboard');});};",
|
|
709
|
+
// ── Meta + KPIs
|
|
710
|
+
"var s=DATA.stats;",
|
|
711
|
+
"$('meta-gen').textContent='Generated '+fmt(DATA.generatedAt);",
|
|
712
|
+
"$('meta-cwd').textContent=DATA.cwd;",
|
|
713
|
+
"$('footer-cwd').textContent=DATA.cwd;",
|
|
714
|
+
"$('kpi-time').textContent=fmtMin(s.timeSavedMin);",
|
|
715
|
+
"$('kpi-healed').textContent=s.healed;",
|
|
716
|
+
"$('kpi-prot').textContent=s.protectedCnt;",
|
|
717
|
+
"$('kpi-fail').textContent=s.failed;",
|
|
718
|
+
"$('donut-total').textContent=s.total;",
|
|
719
|
+
"$('dd-count').textContent=DATA.events.length+' events';",
|
|
720
|
+
// ── Headline
|
|
721
|
+
"var headline=(function(){",
|
|
722
|
+
" if(s.healed===0&&s.protectedCnt===0&&s.failed===0)return['Sela was idle this run.','No broken selectors were encountered.'];",
|
|
723
|
+
" if(s.protectedCnt>0&&s.healed===0)return['Sela caught '+s.protectedCnt+' masked regression'+(s.protectedCnt!==1?'s':'')+'.',",
|
|
724
|
+
" 'The Intent Auditor blocked auto-fixes that would have silently changed test semantics.'];",
|
|
725
|
+
" 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':'')+'.',",
|
|
726
|
+
" 'Every fix was verified by the Intent Auditor. Two would-be fixes were masked regressions \u2014 drill in below.'];",
|
|
727
|
+
" if(s.healed>0)return['Sela healed '+s.healed+' selector'+(s.healed!==1?'s':'')+' automatically.',",
|
|
728
|
+
" 'Every fix was double-checked by the Intent Auditor before touching source code.'];",
|
|
729
|
+
" return['Sela couldn\u2019t heal '+s.failed+' selector'+(s.failed!==1?'s':'')+'.',",
|
|
730
|
+
" 'These need a human hand \u2014 context, candidates, and direct links are ready below.'];",
|
|
731
|
+
"})();",
|
|
732
|
+
"$('exec-h1').textContent=headline[0];",
|
|
733
|
+
"$('exec-sub').textContent=headline[1];",
|
|
734
|
+
// ── Donut chart
|
|
735
|
+
"var total=s.healed+s.protectedCnt+s.failed||1;",
|
|
736
|
+
"var donutCtx=$('donut-chart').getContext('2d');",
|
|
737
|
+
"new Chart(donutCtx,{type:'doughnut',",
|
|
738
|
+
" data:{labels:['Healed','Protected','Failed'],",
|
|
739
|
+
" datasets:[{data:[s.healed,s.protectedCnt,s.failed],",
|
|
740
|
+
" backgroundColor:['rgba(0,212,255,.8)','rgba(162,89,255,.8)','rgba(255,77,106,.8)'],",
|
|
741
|
+
" borderColor:['#00d4ff','#a259ff','#ff4d6a'],borderWidth:1.5,hoverOffset:6}]},",
|
|
742
|
+
" options:{responsive:true,maintainAspectRatio:false,cutout:'72%',",
|
|
743
|
+
" plugins:{legend:{display:false},tooltip:{callbacks:{label:function(c){return' '+c.label+': '+c.parsed;}}}},",
|
|
744
|
+
" onClick:function(e,els){if(!els.length)return;applyFilter(['HEALED','PROTECTED','FAILED'][els[0].index]);setView('deepdive');}",
|
|
745
|
+
" }",
|
|
746
|
+
"});",
|
|
747
|
+
// ── Donut legend
|
|
748
|
+
"var legendData=[",
|
|
749
|
+
" {filter:'ALL',swatch:'rgba(255,255,255,.3)',name:'All events',val:s.total,pct:null},",
|
|
750
|
+
" {filter:'HEALED',swatch:'var(--cyan)',name:'Healed',val:s.healed,pct:Math.round(s.healed/total*100)+'%'},",
|
|
751
|
+
" {filter:'PROTECTED',swatch:'var(--purple)',name:'Protected',val:s.protectedCnt,pct:Math.round(s.protectedCnt/total*100)+'%'},",
|
|
752
|
+
" {filter:'FAILED',swatch:'var(--red)',name:'Failed',val:s.failed,pct:Math.round(s.failed/total*100)+'%'},",
|
|
753
|
+
"];",
|
|
754
|
+
"var $legend=$('donut-legend');",
|
|
755
|
+
"$legend.innerHTML=legendData.map(function(d,i){",
|
|
756
|
+
" return '<div class=\"dl-row'+(i===0?' active':'')+'\" data-filter=\"'+d.filter+'\">'",
|
|
757
|
+
" +'<div class=\"dl-swatch\" style=\"background:'+d.swatch+'\"></div>'",
|
|
758
|
+
" +'<div class=\"dl-name\">'+d.name+'</div>'",
|
|
759
|
+
" +'<div class=\"dl-val\"'+(d.filter!=='ALL'?' style=\"color:'+d.swatch+'\"':'')+'>'+d.val+'</div>'",
|
|
760
|
+
" +(d.pct?'<div class=\"dl-pct\">'+d.pct+'</div>':'')",
|
|
761
|
+
" +'</div>';",
|
|
762
|
+
"}).join('');",
|
|
763
|
+
"$legend.addEventListener('click',function(e){",
|
|
764
|
+
" var row=e.target.closest('.dl-row');if(!row)return;",
|
|
765
|
+
" qsa('.dl-row',$legend).forEach(function(r){r.classList.remove('active');});",
|
|
766
|
+
" row.classList.add('active');",
|
|
767
|
+
" applyFilter(row.dataset.filter);",
|
|
768
|
+
" setView('deepdive');",
|
|
769
|
+
"});",
|
|
770
|
+
// ── Trend badge
|
|
771
|
+
"var runs=DATA.historical_runs||[];",
|
|
772
|
+
"if(runs.length>1){",
|
|
773
|
+
" var last=runs[runs.length-1],prev=runs[runs.length-2];",
|
|
774
|
+
" if(prev&&prev.time_saved_min>0){",
|
|
775
|
+
" var delta=Math.round((last.time_saved_min-prev.time_saved_min)/prev.time_saved_min*100);",
|
|
776
|
+
" var badge=$('trend-badge');",
|
|
777
|
+
" if(badge){",
|
|
778
|
+
" badge.textContent=(delta>=0?'+':'')+delta+'% vs prev run';",
|
|
779
|
+
" badge.style.color=delta>=0?'var(--green)':'var(--red)';",
|
|
780
|
+
" badge.style.borderColor=delta>=0?'rgba(0,229,160,.25)':'rgba(255,77,106,.25)';",
|
|
781
|
+
" badge.style.background=delta>=0?'var(--green-dim)':'var(--red-dim)';",
|
|
782
|
+
" }",
|
|
783
|
+
" }",
|
|
784
|
+
"}",
|
|
785
|
+
// ── Trend chart
|
|
786
|
+
"var trendLabels=runs.map(function(r){return r.label;});",
|
|
787
|
+
"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;});",
|
|
788
|
+
"var healedSeries=runs.map(function(r){return r.healed;});",
|
|
789
|
+
"var maxHealed=Math.max.apply(null,healedSeries.concat([1]));",
|
|
790
|
+
"var trendCtx=$('trend-chart').getContext('2d');",
|
|
791
|
+
"new Chart(trendCtx,{type:'line',",
|
|
792
|
+
" data:{labels:trendLabels,datasets:[",
|
|
793
|
+
" {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'},",
|
|
794
|
+
" {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'}",
|
|
795
|
+
" ]},",
|
|
796
|
+
" options:{responsive:true,maintainAspectRatio:false,",
|
|
797
|
+
" interaction:{mode:'index',intersect:false},",
|
|
798
|
+
" plugins:{legend:{display:false},",
|
|
799
|
+
" tooltip:{backgroundColor:'rgba(16,19,26,.95)',borderColor:'rgba(30,35,54,.8)',borderWidth:1,titleColor:'#dde2f0',bodyColor:'#8b93b0',padding:10,",
|
|
800
|
+
" callbacks:{label:function(c){return c.datasetIndex===0?' '+c.parsed.y.toFixed(1)+'h saved':' '+c.parsed.y+' healed';}}}},",
|
|
801
|
+
" scales:{",
|
|
802
|
+
" x:{grid:{color:'rgba(30,35,54,.6)',lineWidth:.5},ticks:{color:'#5a6180',font:{size:10},maxRotation:45}},",
|
|
803
|
+
" y:{position:'left',grid:{color:'rgba(30,35,54,.6)',lineWidth:.5},ticks:{color:'#5a6180',font:{size:10},callback:function(v){return v+'h';}}},",
|
|
804
|
+
" y1:{position:'right',grid:{display:false},ticks:{color:'#5a6180',font:{size:10}},max:maxHealed+2}",
|
|
805
|
+
" }",
|
|
806
|
+
" }",
|
|
807
|
+
"});",
|
|
808
|
+
// ── Historical table (last 5)
|
|
809
|
+
"var htbl=$('hist-table');",
|
|
810
|
+
"runs.slice().reverse().slice(0,5).forEach(function(r){",
|
|
811
|
+
" var row=document.createElement('div');row.className='hist-row';",
|
|
812
|
+
" row.innerHTML='<div class=\"hc\">'+esc(r.label)+'</div>'",
|
|
813
|
+
" +'<div class=\"hc\" style=\"color:var(--cyan)\">'+r.healed+'</div>'",
|
|
814
|
+
" +'<div class=\"hc\" style=\"color:var(--purple)\">'+(r.protected||0)+'</div>'",
|
|
815
|
+
" +'<div class=\"hc\" style=\"color:var(--red)\">'+r.failed+'</div>'",
|
|
816
|
+
" +'<div class=\"hc\">'+(r.avg_confidence||0)+'%</div>'",
|
|
817
|
+
" +'<div class=\"hc hist-saved\">'+fmtMin(r.time_saved_min)+'</div>';",
|
|
818
|
+
" htbl.appendChild(row);",
|
|
819
|
+
"});",
|
|
820
|
+
// ── VS Code link
|
|
821
|
+
"function vscodeLink(file,line){",
|
|
822
|
+
" if(!file)return'#';",
|
|
823
|
+
" var abs=(file.startsWith('/')||/^[A-Za-z]:/.test(file))?file:DATA.cwd+'/'+file;",
|
|
824
|
+
" return'vscode://file/'+encodeURI(abs).replace(/#/g,'%23')+(line?':'+line:'');",
|
|
825
|
+
"}",
|
|
826
|
+
// ── Confidence badge
|
|
827
|
+
"function confBadge(pct){",
|
|
828
|
+
" if(typeof pct!=='number'||!isFinite(pct))return'';",
|
|
829
|
+
" var cls=pct>=80?'conf-high':pct>=60?'conf-mid':'conf-low';",
|
|
830
|
+
" return'<span class=\"conf-badge '+cls+'\">'+pct+'%</span>';",
|
|
831
|
+
"}",
|
|
832
|
+
// ── Diff renderer
|
|
833
|
+
"function renderDiff(ev){",
|
|
834
|
+
" if(ev.kind==='PROTECTED')",
|
|
835
|
+
" 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>';",
|
|
836
|
+
" if(ev.kind==='FAILED')",
|
|
837
|
+
" 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>';",
|
|
838
|
+
" var raw=ev.gitUnifiedDiff||'';",
|
|
839
|
+
" var skipPfx=['diff ','index ','--- ','+++','new file','deleted','similarity','rename','Binary'];",
|
|
840
|
+
" var hunkRe=/^@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@/;",
|
|
841
|
+
" var body='',newLn=0,hasDiff=raw.split(/\\r?\\n/).some(function(l){return hunkRe.test(l);});",
|
|
842
|
+
" if(hasDiff){",
|
|
843
|
+
" raw.split(/\\r?\\n/).forEach(function(r){",
|
|
844
|
+
" if(skipPfx.some(function(p){return r.startsWith(p);}))return;",
|
|
845
|
+
" var hm=r.match(hunkRe);",
|
|
846
|
+
' 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;}',
|
|
847
|
+
" if(!r.length||r.startsWith('\\\\'))return;",
|
|
848
|
+
" var h=r[0],t=r.slice(1);",
|
|
849
|
+
" 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++;}",
|
|
850
|
+
' 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>\';}',
|
|
851
|
+
" 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++;}",
|
|
852
|
+
" });",
|
|
853
|
+
" }else{",
|
|
854
|
+
" 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>'",
|
|
855
|
+
" +'<div class=\"diff-line dl-add\"><span class=\"gutter\">+</span><span class=\"ln\">'+(ev.newLineNumber||'')+'</span><span class=\"code\">'+esc(ev.newCodeLine||'')+'</span></div>';",
|
|
856
|
+
" }",
|
|
857
|
+
" 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>';",
|
|
858
|
+
"}",
|
|
859
|
+
// ── Auditor renderer
|
|
860
|
+
"function renderAuditor(a){",
|
|
861
|
+
" if(!a)return'<div class=\"muted-block\">Intent Auditor was not consulted for this event.</div>';",
|
|
862
|
+
" var vl=a.verdict.toLowerCase();",
|
|
863
|
+
" var hasPct=typeof a.confidence==='number'&&isFinite(a.confidence);",
|
|
864
|
+
" var pct=hasPct?Math.max(0,Math.min(100,a.confidence)):0;",
|
|
865
|
+
" var circ=2*Math.PI*50,dash=hasPct?(pct/100)*circ:0;",
|
|
866
|
+
" var adj=[];",
|
|
867
|
+
" if(a.penalty)adj.push('<span style=\"color:var(--red)\">'+a.penalty+'</span>');",
|
|
868
|
+
" if(a.bonus)adj.push('<span style=\"color:var(--green)\">+'+a.bonus+'</span>');",
|
|
869
|
+
" return'<div class=\"auditor-wrap\">'",
|
|
870
|
+
" +'<div class=\"gauge-ring\">'",
|
|
871
|
+
' +\'<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>\'',
|
|
872
|
+
" +'<div class=\"gauge-center\"><div class=\"gauge-pct pct-'+vl+'\">'+(hasPct?pct+'%':'n/a')+'</div><div class=\"gauge-sub\">consistency</div></div>'",
|
|
873
|
+
" +'</div>'",
|
|
874
|
+
" +'<div class=\"audit-details\">'",
|
|
875
|
+
' +\'<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>\'',
|
|
876
|
+
' +(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>\':\'\')',
|
|
877
|
+
" +(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>':'')",
|
|
878
|
+
" +'<div class=\"ad-reason\">'+esc(a.reason)+'</div>'",
|
|
879
|
+
" +'</div></div>';",
|
|
880
|
+
"}",
|
|
881
|
+
// ── CoT renderer
|
|
882
|
+
"function renderCoT(steps){",
|
|
883
|
+
" if(!steps||!steps.length)return'<div class=\"muted-block\">No reasoning trace captured.</div>';",
|
|
884
|
+
" 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>';",
|
|
885
|
+
"}",
|
|
886
|
+
// ── DNA table
|
|
887
|
+
"function renderDna(before,after){",
|
|
888
|
+
" if(!before)return'<div class=\"muted-block\">No DNA baseline available.</div>';",
|
|
889
|
+
" var rows=[];",
|
|
890
|
+
" function row(field,ov,nv){",
|
|
891
|
+
" var os=ov==null?'':Array.isArray(ov)?ov.join(' / '):String(ov);",
|
|
892
|
+
" var ns=nv==null?'':Array.isArray(nv)?nv.join(' / '):String(nv);",
|
|
893
|
+
" var same=os===ns,hasAfter=!!after;",
|
|
894
|
+
" var cls=!hasAfter?'missing':same?'matched':'changed';",
|
|
895
|
+
" var icon=!hasAfter?'\u2014':same?'\u2713':'\u25b3';",
|
|
896
|
+
" var vc;",
|
|
897
|
+
" if(!hasAfter||same)vc='<td class=\"val\">'+esc(os||'\u2014')+'</td>';",
|
|
898
|
+
" else vc='<td class=\"val\"><span class=\"val old\">'+esc(os||'\u2014')+'</span> \u2192 <span class=\"val new\">'+esc(ns||'\u2014')+'</span></td>';",
|
|
899
|
+
" rows.push('<tr class=\"'+cls+'\"><td class=\"field\">'+esc(field)+'</td>'+vc+'<td class=\"icon\">'+icon+'</td></tr>');",
|
|
900
|
+
" }",
|
|
901
|
+
" row('Tag',before.tagName,after&&after.tagName);",
|
|
902
|
+
" row('Text',before.text,after&&after.text);",
|
|
903
|
+
" row('Role',before.role,after&&after.role);",
|
|
904
|
+
" row('Classes',before.classes,after&&after.classes);",
|
|
905
|
+
" row('Ancestry',before.ancestry,after&&after.ancestry);",
|
|
906
|
+
" row('Closest label',before.closestLabel,after&&after.closestLabel);",
|
|
907
|
+
" var ab=Object.entries(before.attributes||{}).map(function(e){return e[0]+'='+e[1];}).join(' \xb7 ');",
|
|
908
|
+
" var aa=after?Object.entries(after.attributes||{}).map(function(e){return e[0]+'='+e[1];}).join(' \xb7 '):null;",
|
|
909
|
+
" row('Attributes',ab,aa);",
|
|
910
|
+
" return'<table class=\"dna-table\"><thead><tr><th>Field</th><th>Value</th><th></th></tr></thead><tbody>'+rows.join('')+'</tbody></table>';",
|
|
911
|
+
"}",
|
|
912
|
+
// ── Screenshot
|
|
913
|
+
"function renderScreenshot(ev){",
|
|
914
|
+
" if(ev.kind!=='FAILED'||!ev.failureScreenshotPng)return'';",
|
|
915
|
+
' return\'<div class="body-section"><div class="bs-title"><span class="bst-dot"></span>Playwright Failure Screenshot</div>\'',
|
|
916
|
+
' +\'<div class="fail-img-wrap"><img src="data:image/png;base64,\'+ev.failureScreenshotPng+\'" alt="Failure screenshot" loading="lazy"/>\'',
|
|
917
|
+
" +'<div class=\"fail-img-meta\">captured at test failure \xb7 base64-embedded</div></div></div>';",
|
|
918
|
+
"}",
|
|
919
|
+
// ── Card body
|
|
920
|
+
"function renderCardBody(ev){",
|
|
921
|
+
" var steps=ev.reasoningSteps||[];",
|
|
922
|
+
" if(ev.kind==='PROTECTED'&&!steps.length){",
|
|
923
|
+
" steps=['AI proposed a replacement selector for the broken locator.'];",
|
|
924
|
+
" if(ev.aiExplanation)steps.push('Reasoning: '+ev.aiExplanation);",
|
|
925
|
+
" if(ev.candidateNewSelector)steps.push('Candidate: '+ev.candidateNewSelector+' \u2014 but semantics changed.');",
|
|
926
|
+
" if(ev.auditor)steps.push('Intent Auditor: '+ev.auditor.verdict+' \u2014 '+ev.auditor.reason.slice(0,160));",
|
|
927
|
+
" steps.push('Sela blocked the auto-fix. Source code is untouched.');",
|
|
928
|
+
" }",
|
|
929
|
+
" if(ev.kind==='FAILED'&&!steps.length){",
|
|
930
|
+
" steps=['Locator failed to resolve at runtime \u2014 Sela attempted to heal it.'];",
|
|
931
|
+
" if(ev.aiExplanation)steps.push('AI: '+ev.aiExplanation);",
|
|
932
|
+
" steps.push('Reason: '+(ev.reason||'unknown'));",
|
|
933
|
+
" steps.push('Open the file in your editor to inspect the original locator.');",
|
|
934
|
+
" }",
|
|
935
|
+
" 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);",
|
|
936
|
+
" var link=vscodeLink(ev.sourceFile,ev.sourceLine);",
|
|
937
|
+
" var actions='';",
|
|
938
|
+
" if(ev.kind==='HEALED'){",
|
|
939
|
+
" actions='<div class=\"actions\">'",
|
|
940
|
+
" +'<button class=\"btn btn-primary\" data-action=\"keep\" data-id=\"'+ev.stableId+'\" onclick=\"handleAction(this,\\'keep\\',\\''+ev.stableId+'\\')\">\u2713 Keep changes</button>'",
|
|
941
|
+
" +'<button class=\"btn btn-danger\" onclick=\"handleAction(this,\\'rollback\\',\\''+ev.stableId+'\\')\">↶ Rollback</button>'",
|
|
942
|
+
" +'<a class=\"btn\" href=\"'+link+'\">⌗ Open in VS Code</a>'",
|
|
943
|
+
' +\'<a class="btn" href="\'+issueUrl+\'" target="_blank" rel="noopener">⚑ Report issue</a>\'',
|
|
944
|
+
" +'</div>';",
|
|
945
|
+
" }else if(ev.kind==='PROTECTED'){",
|
|
946
|
+
" actions='<div class=\"actions\">'",
|
|
947
|
+
" +'<a class=\"btn\" href=\"'+link+'\">\u2192 Open in VS Code</a>'",
|
|
948
|
+
" +'<button class=\"btn btn-primary\" onclick=\"handleAction(this,\\'ack\\',\\''+ev.stableId+'\\')\">✓ Acknowledged \u2014 will investigate</button>'",
|
|
949
|
+
' +\'<a class="btn" href="\'+issueUrl+\'" target="_blank" rel="noopener">⚑ File regression</a>\'',
|
|
950
|
+
" +'</div>';",
|
|
951
|
+
" }else{",
|
|
952
|
+
" actions='<div class=\"actions\">'",
|
|
953
|
+
" +'<a class=\"btn btn-primary\" href=\"'+link+'\">\u2192 Open in VS Code</a>'",
|
|
954
|
+
' +\'<a class="btn" href="\'+issueUrl+\'" target="_blank" rel="noopener">⚑ Report issue</a>\'',
|
|
955
|
+
" +'</div>';",
|
|
956
|
+
" }",
|
|
957
|
+
" var alts=(ev.kind==='HEALED'&&ev.aiAlternatives&&ev.aiAlternatives.length)",
|
|
958
|
+
' ?\'<div class="body-section"><div class="bs-title"><span class="bst-dot"></span>Alternative selectors considered</div><div class="alts">\'',
|
|
959
|
+
" +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('')",
|
|
960
|
+
" +'</div></div>'",
|
|
961
|
+
" :'';",
|
|
962
|
+
' return\'<div class="row2" style="margin-top:16px">\'',
|
|
963
|
+
" +'<div><div class=\"bs-title\"><span class=\"bst-dot\"></span>AI Reasoning \xb7 Chain of Thought</div>'+renderCoT(steps)+'</div>'",
|
|
964
|
+
" +'<div><div class=\"bs-title\"><span class=\"bst-dot\"></span>Intent Auditor</div>'+renderAuditor(ev.auditor||null)+'</div>'",
|
|
965
|
+
" +'</div>'",
|
|
966
|
+
' +\'<div class="body-section"><div class="bs-title"><span class="bst-dot"></span>Source Code Diff</div>\'+renderDiff(ev)+\'</div>\'',
|
|
967
|
+
' +\'<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>\'',
|
|
968
|
+
" +renderScreenshot(ev)",
|
|
969
|
+
" +alts",
|
|
970
|
+
" +actions;",
|
|
971
|
+
"}",
|
|
972
|
+
// ── Event card
|
|
973
|
+
"function renderCard(ev,idx){",
|
|
974
|
+
" var kindCls=ev.kind==='HEALED'?'ec-healed':ev.kind==='PROTECTED'?'ec-protected':'ec-failed';",
|
|
975
|
+
" var badgeCls=ev.kind==='HEALED'?'sb-healed':ev.kind==='PROTECTED'?'sb-protected':'sb-failed';",
|
|
976
|
+
" var badgeTxt=ev.kind==='HEALED'?'Healed':ev.kind==='PROTECTED'?'Protected \xb7 Bug Caught':'Failed';",
|
|
977
|
+
" var sel=ev.kind==='HEALED'?(ev.newSelector||ev.oldSelector):ev.oldSelector;",
|
|
978
|
+
" var link=vscodeLink(ev.sourceFile,ev.sourceLine);",
|
|
979
|
+
" var tags='';",
|
|
980
|
+
" if(ev.inIframe)tags+='<span class=\"ecard-tag t-iframe\">iframe</span>';",
|
|
981
|
+
" if(ev.inShadowDom)tags+='<span class=\"ecard-tag t-shadow\">shadow DOM</span>';",
|
|
982
|
+
" if(ev.dryRun)tags+='<span class=\"ecard-tag t-dryrun\">dry run</span>';",
|
|
983
|
+
" var haystack=(ev.oldSelector+' '+(ev.newSelector||'')+' '+ev.sourceFile+' '+(ev.testTitle||'')+' '+ev.stableId).toLowerCase();",
|
|
984
|
+
" return'<article class=\"ecard '+kindCls+'\" data-kind=\"'+ev.kind+'\" data-haystack=\"'+esc(haystack)+'\" style=\"animation-delay:'+(idx*0.04)+'s\">'",
|
|
985
|
+
" +'<div class=\"ecard-head\" onclick=\"this.parentElement.classList.toggle(\\'open\\')\">'",
|
|
986
|
+
" +'<span class=\"status-badge '+badgeCls+'\"><span class=\"sb-dot\"></span>'+badgeTxt+'</span>'",
|
|
987
|
+
" +'<div class=\"ecard-title\">'",
|
|
988
|
+
" +'<div class=\"ecard-selector\">'+esc(sel)+'</div>'",
|
|
989
|
+
" +'<div class=\"ecard-meta\">'+(ev.testTitle?esc(ev.testTitle)+' \xb7 ':'')",
|
|
990
|
+
" +'<a href=\"'+link+'\">'+esc(ev.sourceFile)+':'+ev.sourceLine+'</a>'+tags+' \xb7 '+fmt(ev.timestamp)+'</div>'",
|
|
991
|
+
" +'</div>'",
|
|
992
|
+
" +confBadge(ev.aiConfidence)",
|
|
993
|
+
' +\'<svg class="chev" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2"><polyline points="5 3 11 8 5 13"/></svg>\'',
|
|
994
|
+
" +'</div>'",
|
|
995
|
+
" +'<div class=\"ecard-body\">'+renderCardBody(ev)+'</div>'",
|
|
996
|
+
" +'</article>';",
|
|
997
|
+
"}",
|
|
998
|
+
// ── Mount events
|
|
999
|
+
"var $el=$('events-list');",
|
|
1000
|
+
"$el.innerHTML=DATA.events.map(renderCard).join('');",
|
|
1001
|
+
"var firstProt=$el.querySelector('.ec-protected');",
|
|
1002
|
+
"if(firstProt)firstProt.classList.add('open');",
|
|
1003
|
+
"else{var f=$el.querySelector('.ecard');if(f)f.classList.add('open');}",
|
|
1004
|
+
// ── Filter + Search
|
|
1005
|
+
"var curFilter='ALL',searchTerm='';",
|
|
1006
|
+
"function applyFilter(f){",
|
|
1007
|
+
" curFilter=f;",
|
|
1008
|
+
" qsa('.chip').forEach(function(c){c.classList.toggle('on',c.dataset.filter===f||(f==='ALL'&&c.dataset.filter==='ALL'));});",
|
|
1009
|
+
" filterCards();",
|
|
1010
|
+
"}",
|
|
1011
|
+
"window.applyFilter=applyFilter;",
|
|
1012
|
+
"function filterCards(){",
|
|
1013
|
+
" var vis=0;",
|
|
1014
|
+
" qsa('.ecard',$el).forEach(function(card){",
|
|
1015
|
+
" var show=(curFilter==='ALL'||card.dataset.kind===curFilter)&&(!searchTerm||card.dataset.haystack.includes(searchTerm));",
|
|
1016
|
+
" card.style.display=show?'':'none';",
|
|
1017
|
+
" if(show)vis++;",
|
|
1018
|
+
" });",
|
|
1019
|
+
" $('empty-state').classList.toggle('hidden',vis>0||DATA.events.length===0);",
|
|
1020
|
+
"}",
|
|
1021
|
+
"var fbChips=document.querySelector('.fb-chips');",
|
|
1022
|
+
"if(fbChips)fbChips.addEventListener('click',function(e){var chip=e.target.closest('.chip');if(chip&&chip.dataset.filter)applyFilter(chip.dataset.filter);});",
|
|
1023
|
+
"$('search-input').addEventListener('input',function(e){searchTerm=e.target.value.toLowerCase().trim();filterCards();});",
|
|
1024
|
+
// ── Actions
|
|
1025
|
+
"window.handleAction=function(btn,action,id){",
|
|
1026
|
+
" if(action==='keep'||action==='ack'){btn.dataset.done='true';btn.textContent=action==='keep'?'\u2713 Approved':'\u2713 Acknowledged';toast('Decision saved for '+id);}",
|
|
1027
|
+
" else if(action==='rollback'){var cmd='npx sela rollback --id '+id;navigator.clipboard&&navigator.clipboard.writeText(cmd).then(function(){toast('Copied: '+cmd);});}",
|
|
1028
|
+
"};",
|
|
1029
|
+
"window.copyText=function(txt,btn){",
|
|
1030
|
+
" navigator.clipboard&&navigator.clipboard.writeText(txt).then(function(){btn.textContent='Copied!';setTimeout(function(){btn.textContent='Copy';},1500);});",
|
|
1031
|
+
"};",
|
|
1032
|
+
// ── Keyboard shortcuts
|
|
1033
|
+
"document.addEventListener('keydown',function(e){",
|
|
1034
|
+
" if(e.key==='/'&&document.activeElement!==$('search-input')){e.preventDefault();$('search-input').focus();setView('deepdive');}",
|
|
1035
|
+
" if(e.key==='Escape')$('search-input').blur();",
|
|
1036
|
+
"});",
|
|
1037
|
+
"})();",
|
|
1038
|
+
].join("\n");
|
|
1039
|
+
/* eslint-enable prettier/prettier */
|