feat(room): «Настройки комнаты» по референсу — серия 3 из четырёх (#600)

§6 SPEC.md референса. Четыре карточки #594 остались, менялось содержимое:

- Basics — Display name и Home Assistant area в одну строку из двух полей
  (#room-name, #room-area — контролы набора); подстановка имени из зоны при
  пустом имени прежняя;
- Fill — «Как у пространства» стала строкой-тумблером (#room-fill-inherit,
  решение владельца Q4): включена — подсказка «Следует за пространством:
  режим»; выключена — сегмент из пяти режимов, который стартует с
  действующего режима пространства, а не с «None». «Свой цвет» — плашка
  вокруг прежнего hp-color-opacity (Q5): свотч, hex, число прозрачности,
  Reset у своего цвета. Границы комфорта при эффективной «Температуре» —
  два поля с единицей °C (#room-temp-min/-max) с плейсхолдерами границ
  пространства, легендой Cold / Comfort / Hot, ошибкой под полями и ссылкой
  «Как у пространства». Модуль room-temperature-controls.ts удалён — его
  разметку заменили поля набора;
- Sensor sources — подзаголовки, прежний сегмент «среднее / конкретный», при
  конкретном — кнопка выбора в стиле селекта и панель с поиском в потоке
  карточки (sourcePicker набора, роль listbox/option; общая с диалогом
  устройства в серии 4). Кандидаты и запись прежние;
- Font sizes — две строки «слайдер + число % + Reset to 100%» с концами шкалы,
  подсказка про размер карточек пространства, образец карточки сохранён
  внутри .hpf-tint (решение владельца Q2).

Футер: Cancel | статус | Save; в create — Keep as walls слева. К10: снимок
черновика при _openRoomEdit (room-form-state.ts поверх dialog-baseline),
в edit Save только при изменениях, закрытие с изменениями спрашивает через
_confirmDanger; в create Save по прежнему условию (имя или зона), отмена без
вопроса — она и раньше значила «не создавать» и возвращала контур на план.
Ошибки черновика: имя (edit) / имя-или-зона (create), нечисло в границах —
подпись под полем, «Review N fields» в футере ведёт к первому полю.

Из eager-листа dialogs.styles.ts удалены .roomtemprange*, .roomdialog .srcrow
и .dropbtn; `overflow-x: hidden` у .body убран — скроллер один (К3).

Смоки переписаны осознанно, с пометкой #600: smoke_room_settings (тумблер
вместо радио, #room-name/#room-area, .hpf-panel .hpf-cand, старт сегмента с
режима пространства), smoke_room_temperature_thresholds (.hpf-temprange,
ссылка сброса). Golden-harness room-temperature — те же селекторы. Новый
фокусный свидетель demo/smoke_room_settings_form.mjs: четыре карточки без
старой разметки, имя и зона в одну строку, оболочка 560 и один скроллер,
тумблер/сегмент/плашка/границы/источники/размеры пишут в свои ключи, Save и
вопрос при закрытии, create-режим, семь полос экранов.

Мутанты: room-save-enabled-without-changes, room-discard-without-asking,
room-fill-segment-starts-at-none, room-temp-min-writes-max. Якорь
form-kit-writes-to-a-neighbour-key прежний. smoke-links: символ
roomTemperatureControls заменён на renderRoomSettingsDialog.

Словарь i18n/settings: девять ключей room.* на четыре локали; в основном
каталоге room.area_label без «(unassigned)». Фикстуры листа диалогов набора
пересняты намеренно (панель — байт в байт, AC11).

Бюджет: стартовый граф 290 575 — потолок ОПУЩЕН 292 200 → 291 600 (ушли CSS
диалога комнаты и модуль границ); ленивый редакторский 239 391 — потолок
238 600 → 240 100 с разбором в bundle-budget.mjs.

Гейты: typecheck, build, bundle:sync (три копии байт в байт), npm test
2790+ pass по четырём частям, mutation-gate --check, смоки: room_settings,
room_settings_form, room_temperature_thresholds, editor_tabs, font_scales,
resize_labels, room_autoclose, binding_ui, color_picker_consumers,
lazy_editor_chunk, space_settings_form, general_settings_form, ux_fixes,
settings_dialog_cards, dialog_footer_width.

Issue: #600
User-Visible: no
This commit is contained in:
Claude
2026-09-20 11:39:46 +03:00
parent de71bd7967
commit e2c0c7ed81
72 changed files with 1893 additions and 1483 deletions
+14
View File
@@ -318,6 +318,20 @@
.hpf-status { display: flex; align-items: center; gap: 6px; min-width: 0; margin-left: 8px; font-size: .875rem; color: var(--hpf-muted); }
.hpf-status > .hpf-link { color: var(--warning-color, #b87940); }
.hpf-divider { border: 0; border-top: 1px solid var(--hpf-line); margin: 4px 0; }
.hpf-picker { display: grid; gap: 6px; }
hp-dialog .hpf-form .hpf-pick { display: flex; align-items: center; gap: 8px; text-align: left; }
hp-dialog .hpf-form .hpf-pick > b { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hpf-pick-ref { min-width: 0; font-size: .8125rem; color: var(--hpf-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hpf-pick-ph { color: var(--hpf-muted); }
.hpf-panel { padding: 8px; border: 1px solid var(--hpf-line); border-radius: 9px; background: var(--hpf-canvas); }
.hpf-list { display: flex; flex-direction: column; gap: 2px; max-height: 220px; margin-top: 6px; overflow: auto; }
.hpf-cand { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%; min-height: 44px; padding: 8px 10px; border: 0; border-radius: 7px; background: none; color: var(--primary-text-color); font: inherit; font-size: .875rem; text-align: left; cursor: pointer; }
.hpf-cand > small { font-size: .8125rem; color: var(--hpf-muted); }
.hpf-cand:hover { background: var(--hpf-surface); }
.hpf-cand.sel { background: var(--hpf-tint); color: var(--hpf-accent); }
.hpf-cand:focus-visible { outline: 2px solid var(--hpf-accent); outline-offset: -2px; }
.hpf-templegend { display: flex; justify-content: space-between; margin-top: -6px; font-size: .8125rem; color: var(--hpf-muted); }
.hpf-preview { display: flex; justify-content: center; }
@media (max-width: 480px) {
.hpf-body { padding: 0 14px 14px; }
.hpf-head { padding: 14px 14px 12px; }
+14
View File
@@ -304,6 +304,20 @@
.hpf-status { display: flex; align-items: center; gap: 6px; min-width: 0; margin-left: 8px; font-size: .875rem; color: var(--hpf-muted); }
.hpf-status > .hpf-link { color: var(--warning-color, #b87940); }
.hpf-divider { border: 0; border-top: 1px solid var(--hpf-line); margin: 4px 0; }
.hpf-picker { display: grid; gap: 6px; }
hp-dialog .hpf-form .hpf-pick { display: flex; align-items: center; gap: 8px; text-align: left; }
hp-dialog .hpf-form .hpf-pick > b { font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hpf-pick-ref { min-width: 0; font-size: .8125rem; color: var(--hpf-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hpf-pick-ph { color: var(--hpf-muted); }
.hpf-panel { padding: 8px; border: 1px solid var(--hpf-line); border-radius: 9px; background: var(--hpf-canvas); }
.hpf-list { display: flex; flex-direction: column; gap: 2px; max-height: 220px; margin-top: 6px; overflow: auto; }
.hpf-cand { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%; min-height: 44px; padding: 8px 10px; border: 0; border-radius: 7px; background: none; color: var(--primary-text-color); font: inherit; font-size: .875rem; text-align: left; cursor: pointer; }
.hpf-cand > small { font-size: .8125rem; color: var(--hpf-muted); }
.hpf-cand:hover { background: var(--hpf-surface); }
.hpf-cand.sel { background: var(--hpf-tint); color: var(--hpf-accent); }
.hpf-cand:focus-visible { outline: 2px solid var(--hpf-accent); outline-offset: -2px; }
.hpf-templegend { display: flex; justify-content: space-between; margin-top: -6px; font-size: .8125rem; color: var(--hpf-muted); }
.hpf-preview { display: flex; justify-content: center; }
@media (max-width: 480px) {
.hpf-body { padding: 0 14px 14px; }
.hpf-head { padding: 14px 14px 12px; }