feat: общий набор контролов формы и диалог комнаты по нему (#594)

Шаг 1 эпика #591. Набор поднят из редактора боковой панели — он сделан по
макетам дизайнера и уже живёт в продукте, — а не собран заново по прототипу.

Стили набора выдаёт генератор, параметризованный именами: контейнер формы,
префикс классов, карточки, футер и токены цвета. Это не украшение архитектуры,
а условие переезда панели на общий источник: тест с замороженной фикстурой
показывает, что с параметрами панели генератор отдаёт её нынешние правила
дословно — карточку, обводку фокуса, выключенное состояние и ряд-переключатель.
После этого переезд станет подстановкой, а не редизайном, и пиксели панели не
дрогнут. Сама панель в этом коммите не тронута.

Диалог комнаты собран в четыре карточки: «Основное», «Заливка», «Источники»,
«Размеры шрифтов». Порядок полей прежний, записи прежние — те же ключи
черновика, те же условия «Сохранить», тот же `hp-color-opacity` с атомарной
записью цвета и прозрачности. Пояснения уехали под «?» у заголовков групп
(решение владельца 18.09), выбор источника стал сегментированным
переключателем — под капотом это по-прежнему радиогруппа, поэтому стрелки и
скринридер работают как раньше.

Два измерения определили форму решения, и оба стоило сделать до кода.

Первое: лист набора в синхронном графе дал +1459 Б gzip и пробил потолок
initial View на 415 Б. Диалоги — инструмент администратора, во View их не
открывают, поэтому лист уехал в ленивый редакторский граф и вносится в теневой
корень при открытии диалога — тем же способом, которым это делает боковая
панель. Потолок не поднимался.

Второе: `css` с интерполяцией запрещён минификатором сборки (он намеренно
падает, чтобы не зависеть от рантайма), поэтому лист собирается `unsafeCSS`.
Цена — несжатые пробелы генератора, замерено 59 Б gzip; копия литералом стоила
бы расхождения с генератором, то есть единого источника.

Оставшийся рост initial View — 291 356 → 291 872 Б (+516) — это четыре текста
подсказок в двух встроенных локалях. Тексты сокращены до одного предложения:
подсказка под «?» и должна быть короткой.

`_renderRoomSource` переехал в модуль комнаты вместе со стабом карточки: у него
остался ровно один потребитель.

Issue: #594
User-Visible: yes
This commit is contained in:
Claude
2026-09-19 11:08:03 +03:00
committed by Claude
parent e249e3dbc2
commit 4344ede13a
59 changed files with 1445 additions and 819 deletions
+24
View File
@@ -10172,6 +10172,30 @@ const MUTANT_DEFINITIONS = [
replace: 'export const MOVED_BLOCK_MIN = 1;',
}],
},
{
id: 'form-kit-writes-to-a-neighbour-key',
guard: 'node demo/smoke_room_settings.mjs',
because: '#594: два источника комнаты — две одинаковые строки, отличающиеся одним словом. '
+ 'Перепутать их местами ничего не стоит, а на глаз форма выглядит исправной: значение '
+ 'просто уезжает в соседнее поле черновика и сохраняется туда же',
patches: [{
file: 'src/editors/room-settings-dialog.ts',
find: " if (kind === 'temp') host._roomTempSrc = v;\n else host._roomHumSrc = v;",
replace: ' host._roomTempSrc = v;',
}],
},
{
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',
find: ' <input type="radio" name=${name} .checked=${option.value === value}',
replace: ' <input type="checkbox" name=${name} .checked=${option.value === value}',
}],
},
];
const mutationCardSource = readFileSync(join(repoRoot, 'src/houseplan-card.ts'), 'utf8');