",
replace: "
",
}],
},
{
id: 'marker-badge-position-forgets-touch',
guard: 'node demo/smoke_device_settings_form.mjs',
because: '#600 К1: сегмент стороны бейджа заменил select и обязан, как и он, ставить '
+ 'valueBadgeTouched — иначе выбор человека проигрывает проекции превью при следующем '
+ 'рендере и в конфиг не попадает',
patches: [{
file: 'src/editors/marker-dialog.ts',
find: " valueBadgePosition: position,\n valueBadgeTouched: true,",
replace: " valueBadgePosition: position,\n valueBadgeTouched: false,",
}],
},
{
id: 'form-kit-segment-breaks-words',
guard: 'node demo/smoke_dialog_segments_i18n.mjs',
because: '#600 ревью r1 M1: пять равных колонок сегмента с overflow-wrap: anywhere рвали '
+ '«Temperature» посреди слова, и ни один смок этого не видел — scrollWidth контейнера не '
+ 'рос. Колонка не сжимается ниже самого длинного слова, слова переносятся только по '
+ 'пробелам; вернуть прежние две строки — вернуть дефект в четырёх локалях',
patches: [{
file: 'src/styles/form-kit.styles.ts',
find: ' min-width: fit-content;\n min-height: 38px;\n flex: 1 1 0;',
replace: ' min-height: 38px;\n flex: 1 1 0;',
}, {
file: 'src/styles/form-kit.styles.ts',
find: ' text-align: center;\n overflow-wrap: normal;\n }',
replace: ' text-align: center;\n overflow-wrap: anywhere;\n }',
}],
},
{
id: 'form-kit-segment-drops-radio-semantics',
guard: 'node --test test/form-kit.test.mjs',
because: '#594 К7: сегментированный переключатель заменил радиосписок, и вся его '
+ 'доступность держится на том, что под капотом осталась радиогруппа. Подмена на '
+ 'чекбоксы выглядит идентично и молча ломает стрелки и объявление скринридером',
patches: [{
file: 'src/editors/form-kit.ts',
// #600: у радиокнопки сегмента появился value — якорь дописан.
find: ' [id, fill && fill.opacity > 0 ? fill : glowBase.byId.get(id) ?? null])))',
replace: "memoIsoLightFloorRooms(null, new Map([...roomFills.byId].map(([id, fill]) => [id, fill && fill.opacity > 0 ? fill : glowBase.byId.get(id) ?? null])), parseCssColor(getComputedStyle(this.renderRoot.querySelector('.hp-paper') ?? this).fill) ?? [255, 255, 255]).rooms",
}],
},
{
id: 'iso-first-frame-reveals-flat-during-lazy-load',
guard: 'node demo/smoke_iso_first_frame.mjs',
because: '#654 AC1/AC3: removing the pending class exposes the effective Flat projection '
+ 'while the 2.5D chunk is still loading, most visibly in kiosk mode after the ordinary '
+ 'boot window has elapsed',
patches: [{
file: 'src/houseplan-card.ts',
find: "${isoFirstFramePending ? ' hpiso-pending' : ''}",
replace: "${isoFirstFramePending ? '' : ''}",
}],
},
{
id: 'iso-light-floor-memo-never-reuses',
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
+ '&& node --test --test-name-pattern="#654 light-floor memo" test/iso-stage6.test.mjs',
because: '#654 AC4: HA state churn that leaves paper, fills and room membership unchanged '
+ 'must reuse the classification Set instead of rebuilding it on every card render',
patches: [{
file: 'src/iso-materials.ts',
find: ' return previous?.key === key',
replace: " return previous?.key === '__never__'",
}],
},
];
const mutationCardSource = readFileSync(join(repoRoot, 'src/houseplan-card.ts'), 'utf8');
const mutationEditorSource = readFileSync(join(repoRoot, 'src/houseplan-editor-runtime.ts'), 'utf8');
export const MUTANTS = MUTANT_DEFINITIONS.map((mutant) => ({
...mutant,
patches: mutant.patches.map((patch) => relocateEditorPatch(
patch, mutationCardSource, mutationEditorSource,
)),
}));