fix: preflight diagnostics hash the judged candidate and the fallback dies with its dialog (#295)

CODE-REVIEW-295-r1, both Medium findings:

M1 — _preflightDiagnostics hashed this._serverCfg, the saved config a space
export would reproduce anyway. The hash now comes from the candidate the
preflight actually judged: the report path passes r.config / d.config and the
copy button reads the dialog's own candidate. The smoke no longer masks the
difference — its dialog carries a candidate whose geometry differs from the
saved config, and swapping the two must change the reported hash.

M2 — the inline clipboard fallback survived dialog close and reopen, so a
later refusal could hand the previous refusal's JSON to a bug report. The
field now dies with its dialog: reset on open (_previewAlignDialog) and on
every close path (escape, mode reset, successful apply, hp-close, cancel).

Both regressions are pinned by execution: reverting either fix turns the
extended smoke red (fingerprintTracksCandidate / fallbackClearedOnClose),
and two new gate mutants keep it that way.

Issue: #295
User-Visible: no
This commit is contained in:
Codex
2026-08-26 10:52:47 +03:00
parent 43f91a214b
commit b542f44bd6
6 changed files with 111 additions and 37 deletions
+29 -2
View File
@@ -2258,6 +2258,33 @@ export const MUTANTS = [
replace: " ${failure.displayName}",
}],
},
{
id: 'preflight-fingerprint-from-saved-config',
// CODE-REVIEW-295-r1 M1: хэш геометрии обязан браться из кандидата,
// который проверял preflight, а не из сохранённого конфига — иначе блок
// повторяет то, что и так даст экспорт пространства.
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
+ '&& node demo/smoke_preflight_diagnostics.mjs',
because: 'диагностика с хэшем сохранённой геометрии не несёт ничего сверх экспорта (AC4)',
patches: [{
file: 'src/houseplan-card.ts',
find: " const spacesById = new Map(((candidate as any)?.spaces || [])",
replace: " const spacesById = new Map(((this._serverCfg as any)?.spaces || [])",
}],
},
{
id: 'preflight-fallback-survives-dialog-close',
// CODE-REVIEW-295-r1 M2: инлайн-фолбэк живёт одно показание диалога;
// переживший закрытие блок подсунет в отчёт диагностику чужого отказа.
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
+ '&& node demo/smoke_preflight_diagnostics.mjs',
because: 'застрявший фолбэк отдаёт в баг-репорт JSON предыдущего отказа, не текущего (AC4)',
patches: [{
file: 'src/houseplan-card.ts',
find: "dismiss-on-scrim @hp-close=${() => { this._alignDialog = null; this._preflightClipboardFallback = null; }}>",
replace: "dismiss-on-scrim @hp-close=${() => { this._alignDialog = null; }}>",
}],
},
{
id: 'preflight-diagnostics-without-reason',
// #295: копируемый блок без reason бесполезен для отчёта об ошибке.
@@ -2278,8 +2305,8 @@ export const MUTANTS = [
because: 'структурированная запись отказа в консоли — часть контракта диагностики #295',
patches: [{
file: 'src/houseplan-card.ts',
find: " console.warn('[houseplan] optimize preflight failed', this._preflightDiagnostics(preflight));",
replace: " void preflight;",
find: " console.warn('[houseplan] optimize preflight failed', this._preflightDiagnostics(preflight, candidate));",
replace: " void preflight; void candidate;",
}],
},
{