sela-core 0.1.0-alpha.40 → 0.1.0-alpha.41

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.
@@ -1 +1 @@
1
- {"version":3,"file":"bootImpl.d.ts","sourceRoot":"","sources":["../../src/boot/bootImpl.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IAClD,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;IAC9B,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACtC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,MAAM,CAAC;CACnB;AAKD,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAMlE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAUzD;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,CAKzF;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAkC7C"}
1
+ {"version":3,"file":"bootImpl.d.ts","sourceRoot":"","sources":["../../src/boot/bootImpl.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,QAAQ;IACvB,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IAClD,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC;IAC9B,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACtC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,EAAE,MAAM,CAAC,UAAU,CAAC;IACvB,GAAG,EAAE,MAAM,MAAM,CAAC;CACnB;AAKD,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAMlE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAUzD;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,GAAG,MAAM,CAKzF;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,CAuC7C"}
@@ -39,6 +39,10 @@ function bootImpl(deps) {
39
39
  const manifestPath = deps.env['SELA_REGRESSION_MANIFEST']
40
40
  ? deps.env['SELA_REGRESSION_MANIFEST']
41
41
  : `${deps.cwd()}/.sela/sela-regression-manifest.json`;
42
+ // Expose the resolved path so worker processes (and the fixture's
43
+ // regressionInjector) can discover the manifest without requiring
44
+ // SELA_REGRESSION_MANIFEST to be set explicitly by the user.
45
+ deps.env['SELA_REGRESSION_MANIFEST'] = manifestPath;
42
46
  let manifest;
43
47
  try {
44
48
  manifest = JSON.parse(deps.readFileSync(manifestPath, 'utf-8'));
@@ -1 +1 @@
1
- {"version":3,"file":"ConfigLoader.d.ts","sourceRoot":"","sources":["../../src/config/ConfigLoader.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAiB,MAAM,cAAc,CAAC;AAiNzE,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA+B;IACtD,OAAO,CAAC,MAAM,CAAC,MAAM,CAAuB;IAK5C,OAAO,CAAC,MAAM,CAAC,aAAa,CAA2B;IAEvD;;;;;;;;OAQG;IACH,MAAM,CAAC,WAAW,IAAI,cAAc;IAqCpC;;;;OAIG;IACH,MAAM,CAAC,YAAY,IAAI,UAAU;IAKjC,OAAO,CAAC,MAAM,CAAC,aAAa;IAsB5B,yEAAyE;IACzE,MAAM,CAAC,SAAS,IAAI,MAAM,GAAG,IAAI;IAKjC,uDAAuD;IACvD,MAAM,CAAC,KAAK,IAAI,IAAI;CAKrB"}
1
+ {"version":3,"file":"ConfigLoader.d.ts","sourceRoot":"","sources":["../../src/config/ConfigLoader.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAiB,MAAM,cAAc,CAAC;AAqOzE,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAA+B;IACtD,OAAO,CAAC,MAAM,CAAC,MAAM,CAAuB;IAK5C,OAAO,CAAC,MAAM,CAAC,aAAa,CAA2B;IAEvD;;;;;;;;OAQG;IACH,MAAM,CAAC,WAAW,IAAI,cAAc;IAqCpC;;;;OAIG;IACH,MAAM,CAAC,YAAY,IAAI,UAAU;IAKjC,OAAO,CAAC,MAAM,CAAC,aAAa;IAsB5B,yEAAyE;IACzE,MAAM,CAAC,SAAS,IAAI,MAAM,GAAG,IAAI;IAKjC,uDAAuD;IACvD,MAAM,CAAC,KAAK,IAAI,IAAI;CAKrB"}
@@ -170,23 +170,43 @@ function loadRawConfig(configPath) {
170
170
  }
171
171
  // Every attempt failed. Two cases:
172
172
  //
173
- // 1. The only candidate was a .ts file and the error looks like a missing
174
- // TypeScript loader (e.g. "Cannot find module" for a .ts import, or
175
- // "Unknown file extension .ts"). The CLI runs in plain Node.js no tsx/
176
- // ts-node so the config simply cannot be loaded here. Gracefully fall
177
- // back to defaults rather than crashing a CLI command that doesn't need
178
- // the config (e.g. `sela shadow-regression`).
173
+ // 1. The config cannot be loaded due to a missing or incompatible TypeScript
174
+ // loader. This covers several scenarios that all stem from the same root
175
+ // cause the Node.js process has no way to execute .ts (or compiled ESM
176
+ // .js) in the current environment:
177
+ // ERR_UNKNOWN_FILE_EXTENSION / "Unknown file extension .ts" plain
178
+ // Node.js sees the .ts extension with no registered loader.
179
+ // • "Cannot use import statement outside a module" — the config (or its
180
+ // compiled .js sibling) uses ESM `import` syntax but Node.js is
181
+ // running in CJS mode and cannot load it with require(). Happens when
182
+ // a compiled sela.config.js uses top-level `import`, or when Node 20
183
+ // --experimental-strip-types strips .ts annotations but the project
184
+ // is CJS.
185
+ // • ERR_REQUIRE_ESM / "require() of ES Module" — Node.js detected the
186
+ // file is an ES module and refused the synchronous require() call.
187
+ // All of these are environment/loader mismatches, not config content bugs.
188
+ // Gracefully fall back to defaults so the CLI command (e.g.
189
+ // `sela shadow-regression`) still runs rather than crashing.
179
190
  //
180
191
  // 2. Genuine config error (syntax, key validation, etc.) → hard throw so CI
181
192
  // surfaces the problem instead of silently running on stale defaults.
182
193
  const msg = lastError?.message ?? "";
183
- const isNoTsLoader = configPath.endsWith(".ts") &&
184
- (msg.includes("Cannot find module") ||
194
+ const code = lastError?.code ?? "";
195
+ const isLoaderEnvError = configPath.endsWith(".ts") &&
196
+ (code === "ERR_UNKNOWN_FILE_EXTENSION" ||
197
+ code === "ERR_REQUIRE_ESM" ||
198
+ msg.includes("Cannot find module") ||
185
199
  msg.includes("Unknown file extension") ||
186
- msg.includes("ERR_UNKNOWN_FILE_EXTENSION"));
187
- if (isNoTsLoader) {
188
- logger_1.logger.warn(`[Sela] sela.config.ts found but no TypeScript loader is active — running on defaults. ` +
189
- `If you need config values in the CLI, add a compiled sela.config.js alongside it.`);
200
+ msg.includes("ERR_UNKNOWN_FILE_EXTENSION") ||
201
+ msg.includes("Cannot use import statement outside a module") ||
202
+ msg.includes("require() of ES Module") ||
203
+ msg.includes("ERR_REQUIRE_ESM"));
204
+ if (isLoaderEnvError) {
205
+ logger_1.logger.warn(`[Sela] sela.config.ts found but cannot be loaded in this Node.js context ` +
206
+ `(no TypeScript loader active, or the compiled .js output uses ESM syntax ` +
207
+ `incompatible with require()). Running on defaults. ` +
208
+ `To use config values in the CLI, compile sela.config.ts to CJS ` +
209
+ `(e.g. tsc --module commonjs) or set SELA_CONFIG_PATH to the compiled file.`);
190
210
  return {};
191
211
  }
192
212
  throw new Error(`[Sela] Failed to load ${path.basename(configPath)}: ${msg || "unknown error"}. ` +
@@ -1 +1 @@
1
- {"version":3,"file":"SelaEngine.d.ts","sourceRoot":"","sources":["../../src/engine/SelaEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAcxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAe,QAAQ,EAAkB,MAAM,yBAAyB,CAAC;AA6GhF,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,mBAAmB,CAAmB;IAC9C,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,eAAe,CAAkC;IAKzD,OAAO,CAAC,qBAAqB,CAAkC;IAK/D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;;IAyCnD,YAAY,CAChB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAqEnB,IAAI,CACR,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,MAAM,EACpB,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,QAAmB,GAC5B,OAAO,CAAC,MAAM,CAAC;IAylClB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,aAAa;IAerB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,oBAAoB;IAoB5B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,2BAA2B;IA4BnC,OAAO,CAAC,yBAAyB;IAgDjC,OAAO,CAAC,kBAAkB;IA8D1B,OAAO,CAAC,oBAAoB;IAyD5B,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAIrD,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5E,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK/B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;YAsJtB,aAAa;IAsB3B,OAAO,CAAC,0BAA0B;IA+BlC,OAAO,CAAC,WAAW;IA6Bb,wBAAwB,CAC5B,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM;IAkErB,OAAO,CAAC,MAAM,CAAC,oBAAoB;CAWpC;AAED,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"SelaEngine.d.ts","sourceRoot":"","sources":["../../src/engine/SelaEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAcxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAe,QAAQ,EAAkB,MAAM,yBAAyB,CAAC;AA6GhF,qBAAa,UAAU;IACrB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,eAAe,CAAkB;IACzC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,oBAAoB,CAAuB;IACnD,OAAO,CAAC,mBAAmB,CAAmB;IAC9C,OAAO,CAAC,YAAY,CAAsB;IAC1C,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,SAAS,CAA6C;IAC9D,OAAO,CAAC,cAAc,CAAoC;IAC1D,OAAO,CAAC,eAAe,CAAkC;IAKzD,OAAO,CAAC,qBAAqB,CAAkC;IAK/D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;;IAyCnD,YAAY,CAChB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAqEnB,IAAI,CACR,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,MAAM,EACpB,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE,QAAmB,GAC5B,OAAO,CAAC,MAAM,CAAC;IAkmClB,OAAO,CAAC,aAAa;IAYrB,OAAO,CAAC,aAAa;IAerB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,oBAAoB;IAoB5B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,2BAA2B;IA4BnC,OAAO,CAAC,yBAAyB;IAgDjC,OAAO,CAAC,kBAAkB;IA8D1B,OAAO,CAAC,oBAAoB;IAyD5B,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAIrD,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5E,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAK/B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;YAsJtB,aAAa;IAsB3B,OAAO,CAAC,0BAA0B;IA+BlC,OAAO,CAAC,WAAW;IA6Bb,wBAAwB,CAC5B,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM;IAkErB,OAAO,CAAC,MAAM,CAAC,oBAAoB;CAWpC;AAED,OAAO,EAAE,CAAC"}
@@ -522,20 +522,30 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
522
522
  // attached to the HealedEvent below and, on success, stored in the
523
523
  // DNA record so subsequent heals can show the previous baseline.
524
524
  const _vsKey = (0, VisualSnapshotService_1.snapshotKey)(filePath, stableId);
525
- const _vsLiveDest = (0, VisualSnapshotService_1.liveSnapshotPath)(process.cwd(), _vsKey);
525
+ const _vsCandDest = (0, VisualSnapshotService_1.candidateSnapshotPath)(process.cwd(), _vsKey);
526
526
  let _vsCapture = { elementFound: false, capturedAt: null };
527
527
  let _vsLiveRelPath = null;
528
528
  let _vsDnaRelPath = null;
529
529
  let _vsDnaCapturedAt = null;
530
530
  try {
531
- _vsCapture = await (0, VisualSnapshotService_1.captureElementScreenshot)(page, elementOnlySelector, successfulPath, _vsLiveDest);
531
+ _vsCapture = await (0, VisualSnapshotService_1.captureElementScreenshot)(page, elementOnlySelector, successfulPath, _vsCandDest);
532
532
  if (_vsCapture.elementFound) {
533
- _vsLiveRelPath = path.relative(process.cwd(), _vsLiveDest).replace(/\\/g, "/");
534
- }
535
- const _dnaAbsPath = (0, VisualSnapshotService_1.dnaSnapshotPath)(process.cwd(), _vsKey);
536
- if (fs.existsSync(_dnaAbsPath)) {
537
- _vsDnaRelPath = path.relative(process.cwd(), _dnaAbsPath).replace(/\\/g, "/");
538
- _vsDnaCapturedAt = lastKnownState?.visualSnapshot?.capturedAt ?? null;
533
+ const _baselineAbsPath = (0, VisualSnapshotService_1.baselineSnapshotPath)(process.cwd(), _vsKey);
534
+ if (isCiEnvironment()) {
535
+ // CI: keep candidate alongside baseline so the report shows before/after.
536
+ _vsLiveRelPath = path.relative(process.cwd(), _vsCandDest).replace(/\\/g, "/");
537
+ if (fs.existsSync(_baselineAbsPath)) {
538
+ _vsDnaRelPath = path.relative(process.cwd(), _baselineAbsPath).replace(/\\/g, "/");
539
+ _vsDnaCapturedAt = lastKnownState?.visualSnapshot?.capturedAt ?? null;
540
+ }
541
+ }
542
+ else {
543
+ // Local: immediately overwrite baseline → only 1 file ever exists.
544
+ (0, VisualSnapshotService_1.promoteCandidateToBaseline)(process.cwd(), _vsKey);
545
+ _vsLiveRelPath = path.relative(process.cwd(), _baselineAbsPath).replace(/\\/g, "/");
546
+ // Before-image is gone (overwritten); suppress the before slot.
547
+ _vsDnaRelPath = null;
548
+ }
539
549
  }
540
550
  }
541
551
  catch {
@@ -961,7 +971,7 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
961
971
  visualSnapshot: _vsCapture.elementFound && _vsCapture.capturedAt
962
972
  ? {
963
973
  path: path
964
- .relative(process.cwd(), (0, VisualSnapshotService_1.dnaSnapshotPath)(process.cwd(), _vsKey))
974
+ .relative(process.cwd(), (0, VisualSnapshotService_1.baselineSnapshotPath)(process.cwd(), _vsKey))
965
975
  .replace(/\\/g, "/"),
966
976
  capturedAt: _vsCapture.capturedAt,
967
977
  }
@@ -1527,15 +1537,15 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
1527
1537
  this.dnaBuffer.set(stableId, enriched);
1528
1538
  logger_1.logger.debug(`DNA v2 captured for: ${stableId}`);
1529
1539
  // ── Visual baseline capture (guarded) ─────────────────────
1530
- // Capture a PNG baseline ONLY if the file is missing (first run)
1531
- // or the DOM snapshot changed (manual edit / UI shift). Skipping
1532
- // on unchanged steady-state runs avoids screenshot overhead in CI.
1533
- if (sourceFile) {
1540
+ // Local only: capture a PNG baseline when the file is missing or the
1541
+ // DOM snapshot changed. CI is read-only baselines are committed
1542
+ // from local dev and must not be regenerated during CI test runs.
1543
+ if (sourceFile && !isCiEnvironment()) {
1534
1544
  try {
1535
1545
  const vsKey = (0, VisualSnapshotService_1.snapshotKey)(sourceFile, stableId);
1536
- const dnaDest = (0, VisualSnapshotService_1.dnaSnapshotPath)(process.cwd(), vsKey);
1537
- const dnaStat = fs.existsSync(dnaDest) ? fs.statSync(dnaDest) : null;
1538
- const pngMissing = !dnaStat || dnaStat.size === 0;
1546
+ const baseDest = (0, VisualSnapshotService_1.baselineSnapshotPath)(process.cwd(), vsKey);
1547
+ const baseStat = fs.existsSync(baseDest) ? fs.statSync(baseDest) : null;
1548
+ const pngMissing = !baseStat || baseStat.size === 0;
1539
1549
  let needsCapture = pngMissing;
1540
1550
  if (!pngMissing) {
1541
1551
  // load() reads from disk — commitUpdates() hasn't run yet,
@@ -1547,10 +1557,10 @@ If the text has NOT changed, omit the "contentChange" field entirely.`;
1547
1557
  SelaEngine._snapshotFingerprint(enriched);
1548
1558
  }
1549
1559
  if (needsCapture) {
1550
- const liveDest = (0, VisualSnapshotService_1.liveSnapshotPath)(process.cwd(), vsKey);
1551
- const result = await (0, VisualSnapshotService_1.captureElementScreenshot)(page, selector, [], liveDest);
1560
+ const candDest = (0, VisualSnapshotService_1.candidateSnapshotPath)(process.cwd(), vsKey);
1561
+ const result = await (0, VisualSnapshotService_1.captureElementScreenshot)(page, selector, [], candDest);
1552
1562
  if (result.elementFound) {
1553
- (0, VisualSnapshotService_1.promoteLiveToBaseline)(process.cwd(), vsKey);
1563
+ (0, VisualSnapshotService_1.promoteCandidateToBaseline)(process.cwd(), vsKey);
1554
1564
  logger_1.logger.debug(`Visual baseline ${pngMissing ? "created" : "refreshed"}: ${vsKey}`);
1555
1565
  }
1556
1566
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fixtures/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAK/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAqCpD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAI/D;AAgoBD,wBAAgB,uBAAuB,CACrC,eAAe,EAAE,GAAG,EACpB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,IAAI,EACb,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAChC,WAAW,EAAE,MAAM,GAClB,GAAG,CAuCL;AAaD,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,OAAO,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,IAAI,EACb,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAChC,mBAAmB,CAAC,EAAE,MAAM,GAC3B,GAAG,CAiPL;AAQD,eAAO,MAAM,IAAI;YAEL,UAAU,CAAC,OAAO,mBAAmB,CAAC;;IAG9C;;;;;;;;;;;;;OAaG;0BACmB,IAAI;EAmI5B,CAAC;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fixtures/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAK/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAuCpD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,GAAG,IAAI,CAI/D;AAgoBD,wBAAgB,uBAAuB,CACrC,eAAe,EAAE,GAAG,EACpB,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,IAAI,EACb,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAChC,WAAW,EAAE,MAAM,GAClB,GAAG,CAuCL;AAaD,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,OAAO,EACnB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,IAAI,EACb,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,EAChC,mBAAmB,CAAC,EAAE,MAAM,GAC3B,GAAG,CAiPL;AAQD,eAAO,MAAM,IAAI;YAEL,UAAU,CAAC,OAAO,mBAAmB,CAAC;;IAG9C;;;;;;;;;;;;;OAaG;0BACmB,IAAI;EAmI5B,CAAC;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
@@ -58,11 +58,12 @@ function regressionInjector() {
58
58
  if (_regressionInjector !== undefined)
59
59
  return _regressionInjector;
60
60
  const active = process.env["SELA_REGRESSION_ACTIVE"];
61
- const manifestPath = process.env["SELA_REGRESSION_MANIFEST"];
62
- if (active?.trim().toLowerCase() !== "true" || !manifestPath) {
61
+ if (active?.trim().toLowerCase() !== "true") {
63
62
  _regressionInjector = null;
64
63
  return null;
65
64
  }
65
+ const manifestPath = process.env["SELA_REGRESSION_MANIFEST"] ??
66
+ path.join(process.cwd(), ".sela", RegressionManifestService_1.REGRESSION_MANIFEST_FILE);
66
67
  try {
67
68
  _regressionInjector = RegressionManifestService_1.RegressionInjector.fromFile(manifestPath);
68
69
  logger_1.logger.debug(`Shadow-regression injector loaded from ${manifestPath}`);
@@ -1266,6 +1266,7 @@ const REPORT_JS = [
1266
1266
  "}",
1267
1267
  // ── Shared-locator runtime bypass (blast-radius protection)
1268
1268
  "var WARN_TRIANGLE='<svg class=\"warn-tri\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 1.5L15 14H1L8 1.5z\"/><line x1=\"8\" y1=\"6\" x2=\"8\" y2=\"9.5\"/><circle cx=\"8\" cy=\"11.6\" r=\"0.7\" fill=\"currentColor\" stroke=\"none\"/></svg>';",
1269
+ "var LIGHTBULB='<svg class=\"lightbulb-icon\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 1a3.5 3.5 0 0 0-3.5 3.5c0 1.5.5 2.5 1 3.5v1.5h5v-1.5c.5-1 1-2 1-3.5A3.5 3.5 0 0 0 8 1z\"/><line x1=\"5\" y1=\"10.5\" x2=\"11\" y2=\"10.5\"/><path d=\"M6 12.5h4\"/><path d=\"M6.5 14h3\"/></svg>';",
1269
1270
  "function renderBypassBody(ev){",
1270
1271
  " var link=vscodeLink(ev.sourceFile,ev.sourceLine);",
1271
1272
  " var files=(ev.affectedFiles&&ev.affectedFiles.length)?'<div class=\"bb-files\">Affected: '+ev.affectedFiles.map(esc).join(', ')+'</div>':'';",
@@ -1285,7 +1286,7 @@ const REPORT_JS = [
1285
1286
  " var bypassBrainInner=''",
1286
1287
  " +'<div><div class=\"brain-section-title\">Chain of Thought</div>'+renderCoT(steps)+'</div>'",
1287
1288
  " +'<div><div class=\"brain-section-title\">Intent Audit</div>'+renderAuditor(ev.auditor||null)+'</div>';",
1288
- " var bypassBrain='<details class=\"brain-details\"><summary class=\"brain-summary\"><span class=\"brain-arrow\">▶</span>🧠 AI Reasoning \xb7 Audit</summary><div class=\"brain-body\">'+bypassBrainInner+'</div></details>';",
1289
+ " var bypassBrain='<details class=\"brain-details\"><summary class=\"brain-summary\"><span class=\"brain-arrow\">▶</span>'+LIGHTBULB+' AI Reasoning \xb7 Audit</summary><div class=\"brain-body\">'+bypassBrainInner+'</div></details>';",
1289
1290
  " var actions='<div class=\"actions\">'",
1290
1291
  " +'<a class=\"btn btn-primary\" href=\"'+link+'\">→ Open shared declaration in VS Code</a>'",
1291
1292
  " +'</div>';",
@@ -1344,7 +1345,7 @@ const REPORT_JS = [
1344
1345
  " var _snip=(_d.tagName||'element')+(_d.role?' role=\"'+_d.role+'\"':'')+(_d.text?' \\''+String(_d.text).slice(0,60)+'\\'':'');",
1345
1346
  " brainInner+='<div><div class=\"brain-section-title\">Target DOM Snapshot</div><div class=\"muted-block\" style=\"font-family:var(--mono);font-size:12px\">'+esc(_snip)+'</div></div>';",
1346
1347
  " }",
1347
- " var brain='<details class=\"brain-details\"><summary class=\"brain-summary\"><span class=\"brain-arrow\">▶</span>🧠 AI Reasoning & Audit</summary><div class=\"brain-body\">'+brainInner+'</div></details>';",
1348
+ " var brain='<details class=\"brain-details\"><summary class=\"brain-summary\"><span class=\"brain-arrow\">▶</span>'+LIGHTBULB+' AI Reasoning & Audit</summary><div class=\"brain-body\">'+brainInner+'</div></details>';",
1348
1349
  ' return\'<div class="body-section"><div class="bs-title"><span class="bst-dot"></span>Source Code Diff</div>\'+renderDiff(ev)+\'</div>\'',
1349
1350
  " +brain",
1350
1351
  " +renderFixRegression(ev)",
@@ -310,7 +310,7 @@ function buildDefaultBody(heals, branchInfo, reportLink, decision) {
310
310
  }
311
311
  lines.push("```");
312
312
  if (h.aiExplanation) {
313
- lines.push(`> 🧠 ${h.aiExplanation}`);
313
+ lines.push(`> 💡 ${h.aiExplanation}`);
314
314
  }
315
315
  lines.push(`> AI Confidence: **${fmtPct(h.aiConfidence)}** · Auditor: **${fmtPct(h.auditor?.confidence)}**`);
316
316
  lines.push("");
@@ -483,12 +483,12 @@ async function runCleanRoom(args) {
483
483
  continue;
484
484
  const key = (0, VisualSnapshotService_1.snapshotKey)(heal.sourceFile, heal.stableId);
485
485
  _vsActiveKeys.add(key);
486
- const promoted = (0, VisualSnapshotService_1.promoteLiveToBaseline)(ctx.cwd, key);
486
+ const promoted = (0, VisualSnapshotService_1.promoteCandidateToBaseline)(ctx.cwd, key);
487
487
  if (promoted.ok) {
488
- const relDna = path
489
- .join(".sela", "dna-snapshots", `${key}-dna.png`)
488
+ const relBase = path
489
+ .join(".sela", "dna-snapshots", `${key}-baseline.png`)
490
490
  .replace(/\\/g, "/");
491
- (0, shell_1.sh)(`git add ${(0, shell_1.shellQuote)(relDna)}`, ctx.cwd);
491
+ (0, shell_1.sh)(`git add ${(0, shell_1.shellQuote)(relBase)}`, ctx.cwd);
492
492
  }
493
493
  }
494
494
  const gcCount = (0, VisualSnapshotService_1.gcOrphanSnapshots)(ctx.cwd, _vsActiveKeys);
@@ -496,14 +496,14 @@ async function runCleanRoom(args) {
496
496
  (0, shell_1.sh)(`git add -u ${(0, shell_1.shellQuote)(path.join(".sela", "dna-snapshots"))}`, ctx.cwd);
497
497
  }
498
498
  // Stage first-run pass baselines and freshened post-edit baselines.
499
- // These are *-dna.png files written by captureSuccessfulElement()
499
+ // These are *-baseline.png files written by captureSuccessfulElement()
500
500
  // that are not part of the heal loop above (no heal event = no loop
501
501
  // entry). git add on already-staged files is a no-op, so double-
502
502
  // staging healed PNGs here is harmless.
503
503
  const dnaDir = path.join(ctx.cwd, ".sela", "dna-snapshots");
504
504
  if (fs.existsSync(dnaDir)) {
505
505
  for (const f of fs.readdirSync(dnaDir)) {
506
- if (!f.endsWith("-dna.png"))
506
+ if (!f.endsWith("-baseline.png"))
507
507
  continue;
508
508
  (0, shell_1.sh)(`git add ${(0, shell_1.shellQuote)(path.join(".sela", "dna-snapshots", f))}`, ctx.cwd);
509
509
  }
@@ -2,10 +2,15 @@ import type { Page } from "@playwright/test";
2
2
  /** Deterministic 16-hex key — same truncation as HealingRegistry.fingerprint(). */
3
3
  export declare function snapshotKey(sourceFile: string, stableId: string): string;
4
4
  export declare function dnaSnapshotDir(cwd: string): string;
5
- /** Canonical on-disk baseline promoted from the previous heal cycle. */
6
- export declare function dnaSnapshotPath(cwd: string, key: string): string;
7
- /** Temporary live capture from the current heal cycle (not committed to git). */
8
- export declare function liveSnapshotPath(cwd: string, key: string): string;
5
+ /** Canonical on-disk baseline committed to git; one file per element, ever. */
6
+ export declare function baselineSnapshotPath(cwd: string, key: string): string;
7
+ /**
8
+ * Candidate screenshot captured during a heal cycle.
9
+ * - Local: promoted immediately to baseline then deleted (0 surplus files).
10
+ * - CI: kept alongside the untouched baseline so the report shows before/after.
11
+ * PRAutomationService promotes and deletes it when the heal PR is committed.
12
+ */
13
+ export declare function candidateSnapshotPath(cwd: string, key: string): string;
9
14
  export interface CaptureResult {
10
15
  elementFound: boolean;
11
16
  capturedAt: string | null;
@@ -19,28 +24,29 @@ export interface CaptureResult {
19
24
  */
20
25
  export declare function captureElementScreenshot(page: Page, selector: string, framePath: string[], destPath: string): Promise<CaptureResult>;
21
26
  /**
22
- * Atomic promotion of the live capture to the canonical DNA baseline path.
27
+ * Atomic promotion of the candidate capture to the canonical baseline path.
23
28
  *
24
29
  * Steps:
25
- * 1. Backup existing dna to dna.bak (so rollback can restore it).
26
- * 2. Copy livedna.
30
+ * 1. Backup existing baseline to baseline.bak (so rollback can restore it).
31
+ * 2. Copy candidatebaseline.
32
+ * 3. Delete candidate — baseline is now the single source of truth.
27
33
  *
28
- * Returns {ok:false} when the live file is absent (capture failed or
34
+ * Returns {ok:false} when the candidate file is absent (capture failed or
29
35
  * elementFound was false).
30
36
  */
31
- export declare function promoteLiveToBaseline(cwd: string, key: string): {
37
+ export declare function promoteCandidateToBaseline(cwd: string, key: string): {
32
38
  ok: boolean;
33
39
  };
34
40
  /**
35
41
  * Undo a promotion by restoring the .bak file (if present) or deleting the
36
- * newly promoted dna file. Called from runCleanRoom()'s catch block.
42
+ * newly promoted baseline file. Called from runCleanRoom()'s catch block.
37
43
  */
38
44
  export declare function rollbackPromotion(cwd: string, key: string): void;
39
45
  /** Clean up the .bak sidecar after a successful git commit. */
40
46
  export declare function cleanupBackup(cwd: string, key: string): void;
41
47
  /**
42
- * Delete *-dna.png files not referenced by any key in activeKeys.
43
- * Leaves *-live.png files and .bak files untouched — those are transient.
48
+ * Delete *-baseline.png and *-candidate.png files not referenced by any key
49
+ * in activeKeys. Leaves .bak files untouched.
44
50
  *
45
51
  * Returns the number of files deleted (0 when the dir doesn't exist yet).
46
52
  */
@@ -1 +1 @@
1
- {"version":3,"file":"VisualSnapshotService.d.ts","sourceRoot":"","sources":["../../src/services/VisualSnapshotService.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAS7C,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKxE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,iFAAiF;AACjF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjE;AAwCD,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EAAE,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,CAAC,CAoCxB;AAMD;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV;IAAE,EAAE,EAAE,OAAO,CAAA;CAAE,CAgCjB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAahE;AAED,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAO5D;AAMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,MAAM,CAyBR"}
1
+ {"version":3,"file":"VisualSnapshotService.d.ts","sourceRoot":"","sources":["../../src/services/VisualSnapshotService.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAS7C,mFAAmF;AACnF,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAKxE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEtE;AAwCD,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EAAE,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,aAAa,CAAC,CAoCxB;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GACV;IAAE,EAAE,EAAE,OAAO,CAAA;CAAE,CAkCjB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAahE;AAED,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAO5D;AAMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACtB,MAAM,CA4BR"}
@@ -52,10 +52,10 @@ var __importStar = (this && this.__importStar) || (function () {
52
52
  Object.defineProperty(exports, "__esModule", { value: true });
53
53
  exports.snapshotKey = snapshotKey;
54
54
  exports.dnaSnapshotDir = dnaSnapshotDir;
55
- exports.dnaSnapshotPath = dnaSnapshotPath;
56
- exports.liveSnapshotPath = liveSnapshotPath;
55
+ exports.baselineSnapshotPath = baselineSnapshotPath;
56
+ exports.candidateSnapshotPath = candidateSnapshotPath;
57
57
  exports.captureElementScreenshot = captureElementScreenshot;
58
- exports.promoteLiveToBaseline = promoteLiveToBaseline;
58
+ exports.promoteCandidateToBaseline = promoteCandidateToBaseline;
59
59
  exports.rollbackPromotion = rollbackPromotion;
60
60
  exports.cleanupBackup = cleanupBackup;
61
61
  exports.gcOrphanSnapshots = gcOrphanSnapshots;
@@ -77,13 +77,18 @@ function snapshotKey(sourceFile, stableId) {
77
77
  function dnaSnapshotDir(cwd) {
78
78
  return path.join(cwd, SNAPSHOT_SUBDIR);
79
79
  }
80
- /** Canonical on-disk baseline promoted from the previous heal cycle. */
81
- function dnaSnapshotPath(cwd, key) {
82
- return path.join(dnaSnapshotDir(cwd), `${key}-dna.png`);
80
+ /** Canonical on-disk baseline committed to git; one file per element, ever. */
81
+ function baselineSnapshotPath(cwd, key) {
82
+ return path.join(dnaSnapshotDir(cwd), `${key}-baseline.png`);
83
83
  }
84
- /** Temporary live capture from the current heal cycle (not committed to git). */
85
- function liveSnapshotPath(cwd, key) {
86
- return path.join(dnaSnapshotDir(cwd), `${key}-live.png`);
84
+ /**
85
+ * Candidate screenshot captured during a heal cycle.
86
+ * - Local: promoted immediately to baseline then deleted (0 surplus files).
87
+ * - CI: kept alongside the untouched baseline so the report shows before/after.
88
+ * PRAutomationService promotes and deletes it when the heal PR is committed.
89
+ */
90
+ function candidateSnapshotPath(cwd, key) {
91
+ return path.join(dnaSnapshotDir(cwd), `${key}-candidate.png`);
87
92
  }
88
93
  function ensureSnapshotDir(cwd) {
89
94
  const dir = dnaSnapshotDir(cwd);
@@ -159,56 +164,62 @@ async function captureElementScreenshot(page, selector, framePath, destPath) {
159
164
  // PROMOTION (live → dna baseline)
160
165
  // ─────────────────────────────────────────────────────────────────────────────
161
166
  /**
162
- * Atomic promotion of the live capture to the canonical DNA baseline path.
167
+ * Atomic promotion of the candidate capture to the canonical baseline path.
163
168
  *
164
169
  * Steps:
165
- * 1. Backup existing dna to dna.bak (so rollback can restore it).
166
- * 2. Copy livedna.
170
+ * 1. Backup existing baseline to baseline.bak (so rollback can restore it).
171
+ * 2. Copy candidatebaseline.
172
+ * 3. Delete candidate — baseline is now the single source of truth.
167
173
  *
168
- * Returns {ok:false} when the live file is absent (capture failed or
174
+ * Returns {ok:false} when the candidate file is absent (capture failed or
169
175
  * elementFound was false).
170
176
  */
171
- function promoteLiveToBaseline(cwd, key) {
172
- const livePath = liveSnapshotPath(cwd, key);
173
- const dnaPath = dnaSnapshotPath(cwd, key);
174
- const bakPath = `${dnaPath}.bak`;
177
+ function promoteCandidateToBaseline(cwd, key) {
178
+ const candPath = candidateSnapshotPath(cwd, key);
179
+ const basePath = baselineSnapshotPath(cwd, key);
180
+ const bakPath = `${basePath}.bak`;
175
181
  try {
176
- if (!fs.existsSync(livePath)) {
177
- logger_1.logger.debug(`VisualSnapshot: promote skipped — live not found: ${path.basename(livePath)}`);
182
+ if (!fs.existsSync(candPath)) {
183
+ logger_1.logger.debug(`VisualSnapshot: promote skipped — candidate not found: ${path.basename(candPath)}`);
178
184
  return { ok: false };
179
185
  }
180
186
  ensureSnapshotDir(cwd);
181
187
  // Backup existing baseline for rollback.
182
- if (fs.existsSync(dnaPath)) {
188
+ if (fs.existsSync(basePath)) {
183
189
  try {
184
- fs.copyFileSync(dnaPath, bakPath);
190
+ fs.copyFileSync(basePath, bakPath);
185
191
  }
186
192
  catch {
187
193
  // Backup failure is non-fatal; rollback may not be possible.
188
194
  }
189
195
  }
190
- fs.copyFileSync(livePath, dnaPath);
196
+ fs.copyFileSync(candPath, basePath);
197
+ // Remove candidate — baseline is now the single source of truth.
198
+ try {
199
+ fs.unlinkSync(candPath);
200
+ }
201
+ catch { /* non-fatal */ }
191
202
  return { ok: true };
192
203
  }
193
204
  catch (err) {
194
- logger_1.logger.warn(`VisualSnapshot: promoteLiveToBaseline failed: ${err?.message ?? err}`);
205
+ logger_1.logger.warn(`VisualSnapshot: promoteCandidateToBaseline failed: ${err?.message ?? err}`);
195
206
  return { ok: false };
196
207
  }
197
208
  }
198
209
  /**
199
210
  * Undo a promotion by restoring the .bak file (if present) or deleting the
200
- * newly promoted dna file. Called from runCleanRoom()'s catch block.
211
+ * newly promoted baseline file. Called from runCleanRoom()'s catch block.
201
212
  */
202
213
  function rollbackPromotion(cwd, key) {
203
- const dnaPath = dnaSnapshotPath(cwd, key);
204
- const bakPath = `${dnaPath}.bak`;
214
+ const basePath = baselineSnapshotPath(cwd, key);
215
+ const bakPath = `${basePath}.bak`;
205
216
  try {
206
217
  if (fs.existsSync(bakPath)) {
207
- fs.copyFileSync(bakPath, dnaPath);
218
+ fs.copyFileSync(bakPath, basePath);
208
219
  fs.unlinkSync(bakPath);
209
220
  }
210
- else if (fs.existsSync(dnaPath)) {
211
- fs.unlinkSync(dnaPath);
221
+ else if (fs.existsSync(basePath)) {
222
+ fs.unlinkSync(basePath);
212
223
  }
213
224
  }
214
225
  catch {
@@ -217,7 +228,7 @@ function rollbackPromotion(cwd, key) {
217
228
  }
218
229
  /** Clean up the .bak sidecar after a successful git commit. */
219
230
  function cleanupBackup(cwd, key) {
220
- const bakPath = `${dnaSnapshotPath(cwd, key)}.bak`;
231
+ const bakPath = `${baselineSnapshotPath(cwd, key)}.bak`;
221
232
  try {
222
233
  if (fs.existsSync(bakPath))
223
234
  fs.unlinkSync(bakPath);
@@ -230,8 +241,8 @@ function cleanupBackup(cwd, key) {
230
241
  // GC (orphan dna file cleanup)
231
242
  // ─────────────────────────────────────────────────────────────────────────────
232
243
  /**
233
- * Delete *-dna.png files not referenced by any key in activeKeys.
234
- * Leaves *-live.png files and .bak files untouched — those are transient.
244
+ * Delete *-baseline.png and *-candidate.png files not referenced by any key
245
+ * in activeKeys. Leaves .bak files untouched.
235
246
  *
236
247
  * Returns the number of files deleted (0 when the dir doesn't exist yet).
237
248
  */
@@ -242,18 +253,22 @@ function gcOrphanSnapshots(cwd, activeKeys) {
242
253
  let deleted = 0;
243
254
  try {
244
255
  for (const file of fs.readdirSync(dir)) {
245
- if (!file.endsWith("-dna.png"))
256
+ let key;
257
+ if (file.endsWith("-baseline.png")) {
258
+ key = file.slice(0, -"-baseline.png".length);
259
+ }
260
+ else if (file.endsWith("-candidate.png")) {
261
+ key = file.slice(0, -"-candidate.png".length);
262
+ }
263
+ if (!key || activeKeys.has(key))
246
264
  continue;
247
- const key = file.slice(0, -"-dna.png".length);
248
- if (!activeKeys.has(key)) {
249
- try {
250
- fs.unlinkSync(path.join(dir, file));
251
- deleted++;
252
- logger_1.logger.debug(`VisualSnapshot: GC deleted orphan ${file}`);
253
- }
254
- catch {
255
- // Best effort.
256
- }
265
+ try {
266
+ fs.unlinkSync(path.join(dir, file));
267
+ deleted++;
268
+ logger_1.logger.debug(`VisualSnapshot: GC deleted orphan ${file}`);
269
+ }
270
+ catch {
271
+ // Best effort.
257
272
  }
258
273
  }
259
274
  }
@@ -50,7 +50,7 @@ export interface ElementSnapshot {
50
50
  isInShadowDom?: boolean;
51
51
  /** Visual element snapshot captured during a heal event. Path is cwd-relative. */
52
52
  visualSnapshot?: {
53
- /** cwd-relative path to the committed DNA baseline PNG (e.g. .sela/dna-snapshots/<key>-dna.png). */
53
+ /** cwd-relative path to the committed baseline PNG (e.g. .sela/dna-snapshots/<key>-baseline.png). */
54
54
  path: string;
55
55
  /** ISO timestamp of when the live element screenshot was captured. */
56
56
  capturedAt: string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sela-core",
3
- "version": "0.1.0-alpha.40",
3
+ "version": "0.1.0-alpha.41",
4
4
  "description": "AI self-healing Playwright wrapper - drop-in replacement for @playwright/test",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",