mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-30 11:49:16 +00:00
351fef43d65ffbc6eeceda7e744bfe142db2236d
750
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
351fef43d6 |
ci(process): раунд ревью ждёт Validate событием, а не сном раннера (#636)
Стадия prepare спала ≈ 28 минут на раунд, пока шёл Validate с мутантами на материале (модель работает 10–12); за неделю ≈ 420–500 job-минут простоя и потолок бюджета стадии 55 минут. - validate-gate.mjs: `--no-wait` — гейт диспатчит прогон, убеждается, что тот встал на материал (#539 сохранён), и возвращает `pending` (код 2) вместо ожидания; завершённый зелёный/красный отдаёт сразу, как прежде. - process.yml prepare: третий исход `proceed=pending`: запечатанный маркер `review-pending-<issue>-<run>-<attempt>` (issue, stage, branch, material_sha, validate run) и выход; модель и интеграция не запускаются; возврат автору — только на явном `false`. - process-resume.yml + scripts/process-resume.mjs: на `workflow_run: completed` Validate по ветке issue/* — если метка S7 стоит, активного прогона нет и последний прогон оставил маркер на этот SHA, переставить S7 (HP_PROCESS_TOKEN); новый прогон находит завершённый dispatch сразу. Без маркера не будит. - process-reconcile.mjs: читает маркер и состояние Validate на материале; идёт — wait, завершился/пропал без продолжения — retry; без маркера — прежний escalate. Общий loadSealedArtifact, экспорт processRuns/artifactNames. - preflight сверяет process-resume.yml между main и dev наравне с process.yml. - Тесты: validate-gate (4), process-resume (8, включая контракт трёх workflow), process-reconcile (2); мутанты gate-no-wait-still-sleeps, resume-wakes-round-without-marker, resume-ignores-active-run, reconcile-wakes-pending-while-validate-active. PROCESS.md §10.4, AGENTS.md. Issue: #636 User-Visible: no |
||
|
|
0d047450ce |
fix(ci): стабилизировать релизный proof на main (#619)
Issue: #619 User-Visible: no |
||
|
|
e95936cdce |
fix(touch): refresh device hits and defer pinch zoom save (#613)
Issue: #613 User-Visible: yes |
||
|
|
4c9e013276 |
fix(dialog): align HA settings form shell (#609)
Use Home Assistant's public dialog tokens for the reviewed 560 px canvas, height cap, single scroller and mobile fullscreen layout. Keep generic dialogs and the native fallback unchanged, with authentic-HA evidence and mutation witnesses. Issue: #609 User-Visible: yes |
||
|
|
9bbf6fb248 |
fix(dialog): restore HA modal after rejected close (#607)
Issue: #607 User-Visible: yes |
||
|
|
6d6d6a1317 |
test(golden): isolate furniture correction scene (#606)
Keep the five-kind integration contract for existing scenes and assert the four symbols in the focused furniture scene. Issue: #606 User-Visible: no |
||
|
|
20457660fa |
fix(furniture): correct exercise and shelf artwork (#606)
Publish corrected 0.4.1 pack, preserve old cactus objects as a read-only alias, and cover the catalog, renderers, editor, PDF and visual scene. Issue: #606 User-Visible: yes |
||
|
|
cc2300bf86 |
ci(mutation-gate): шесть шардов ночного прогона, прерванный шард — отказ, а не «ok» (#604)
Шард 2/4 прогона 35565222849 снят по timeout-minutes: реестр вырос до 810 мутантов (~203 на шард), длительность за 11 дней 42 → 61 мин при потолке 60. Отчёт назвал его «ok»: лог без строк FAIL считался зелёным, а обрыв по таймауту строк FAIL не содержит. Агрегатор проверял identity, но не завершённость — evidence шага `if: always()` было на месте. - mutation-gate.yml: matrix из шести шардов, `--shard=i/6`, `--shards=6`; шаг прогона получил id, его `outcome` пишется в evidence. - mutation-gate-report.mjs: шард `ok` только с итоговой строкой `поймано N из M`, N = M, без FAIL и с исходом шага `success`; лог без итога или исход `cancelled`/`skipped` — `interrupted`, отказ; агрегатор отвергает прерванный шард как неполный. `outcome` в evidence необязателен ради старых артефактов, но, если назван, обязан быть из известного набора. - тесты: обрыв → interrupted, исходы шага, evidence с outcome; делитель шардов один во всех местах workflow; фикстуры зелёных логов получили итог. - мутанты: mutation-report-truncated-log-is-ok, mutation-evidence-ignores-cancelled-step. - docs/TESTING.md: шесть шардов, итоговая строка. Потолок 60 минут остаётся стражем от зависшего Chromium. Ledger в ночном прогоне не включён: ночь гоняет всё. Issue: #604 User-Visible: no |
||
|
|
08a56953cc |
fix(settings): polish dialog colours and actions (#605)
Make colour tiles solid, readable and fully clickable; align Data actions and device icon field with the form kit. Cover narrow Windows layouts and both icon-picker branches with a focused browser smoke. Issue: #605 User-Visible: yes |
||
|
|
0a3e0674cc |
fix(dialogs): finish discard, switch and room polish (#603)
Issue: #603 User-Visible: yes |
||
|
|
746147d0e0 |
fix(dialogs): polish settings layout and radar flow (#602)
Issue: #602 User-Visible: yes |
||
|
|
a551af9219 |
ci(validate): мутанты по диффу — только по явному запросу, не на кандидате беты и не в full (#601)
`mutantsRequested` отвечает true лишь на PR и `workflow_dispatch mutants=true` (конвейер ревью, слияние кандидата). Трейлер `Release:` и `full=true` включают тяжёлые гейты — смоки, golden, performance_smoke — но не мутантов: к бете каждая задача прогнана ими на ревью и на слитом после ребейза кандидате, ночь покрыта полным реестром (mutation-gate.yml, #513), а ручной полный прогон ради артефакта эталонов и приёмка эталонов с трейлером на ветке задачи платили шестью job впустую. `schedule` мутантов тоже не запрашивает. Политика release в ci-proof — `mutants: false`: иначе proof кандидата беты без запрошенных mutant-jobs объявлялся бы stale. review и merge по-прежнему требуют шесть исполненных job (#541). Тесты: #510 AC1 переписан под новый список, ci-proof — release без мутантов green, лёгкий stale, review/merge без запроса stale. Мутанты протокола: `mutants-run-on-every-push` перепривязан, новые `mutants-run-on-beta-candidate`, `mutants-run-on-full-dispatch`, `release-proof-demands-mutant-jobs`. PROCESS.md §10.4, AGENTS.md, docs/TESTING.md, комментарии workflow. Issue: #601 User-Visible: no |
||
|
|
8dc60aec6e |
fix(dialogs): находки код-ревью #600 r1 — сегмент без разрыва слов, радиус без блокировки Save, свидетель дефекта №5
M1. Подпись сегмента больше не рвётся посреди слова: колонка `.hpf-seg label` не сжимается ниже самого длинного слова (`min-width: fit-content`, для браузеров без него — прежний `0` строкой выше), слова переносятся только по пробелам (`overflow-wrap: normal`), не поместившийся сегмент переносится на вторую строку целыми кнопками (`flex-wrap`). Новый свидетель demo/smoke_dialog_segments_i18n.mjs — четыре диалога × en/ru/de/fr: у каждой подписи число строк (по Range текстового узла: span — flex-элемент, у блока один прямоугольник даже при переносе) не больше числа слов, текст внутри кнопки. На разметке r1 краснеет на en/ru/fr комнаты («Temperature» в 2 строки, «По температуре» в 3). Мутант form-kit-segment-breaks-words возвращает прежние две строки CSS. ACCEPTANCE.md — строка про подписи сегмента переписана. M2. Glow radius — не условие Save: как на dev, нечисло или неположительное значение при сохранении молча становится общим радиусом. Удалены glowRadiusValid, MarkerProblem marker.error_glow_radius, ключ в четырёх локалях словаря settings, aria-invalid у поля; смок device_settings_form утверждает обратное прежнему (badRadiusDoesNotBlockSave, zeroRadiusKeepsSaveEnabled) и проверяет «Review N fields» на привязке в create-режиме. Changelog EN/RU и USER-GUIDE ru/en больше не называют радиус среди ошибок; таблица устройства говорит про молчаливый общий радиус. M3. Свидетель дефекта №5 (поповер «?» обрезается границей диалога комнаты): demo/smoke_dialog_help_clipping.mjs — короткое окно 900×620, диалог комнаты скроллится, «?» нижней карточки прижат к краю; подсказка выходит за край скроллера и остаётся hit-test'ируемой во всех пяти точках (elementsFromPoint сквозь теневые корни — обрезанная часть не hit-test'ится). Четыре ветки: нативный <dialog> и ha-dialog × Popover API и портальный fallback; ветка ha-dialog — заглушка с ловушками настоящей mwc-поверхности (transform + overflow: hidden + собственный скроллер), portal вне поверхности проверен явно. Настоящий ha-dialog (#505, загрузка колеса HA-frontend) — отдельная диагностическая приёмка владельца; так и записано в ACCEPTANCE.md, где появилась таблица AC8 «девять дефектов — свидетель на каждый». M4. ACCEPTANCE.md: эталонов 13, а не 11 — одиннадцать из ТЗ (список AC10 унаследован от #598 без комнаты) плюс две room-temperature-dialog-*. L1. USER-GUIDE.ru.md: испорченная строка таблицы настроек пространства (хвост удалённой строки про декоративный слой) — убран; пояснение «в редакторе подложки остаются видимыми» вернулось в строку «Видимые слои»; строка про солнце говорит «слой Двери, окна и ворота выключен» вместо «Скрыть проёмы включено». Фикстуры листа диалогов набора пересняты (панель — байт в байт). Бюджет: initial 289 852 / 290 900; lazy editor 242 989 / 243 900. Гейты: typecheck, build, bundle:sync (три копии), npm test 2794 pass по четырём частям, no-new-any, mutation-gate --check, check-inputs --coverage, смоки: segments_i18n, help_clipping, четыре формы, config_parity, help_affordance, room_settings, settings_dialog_cards. Issue: #600 User-Visible: yes |
||
|
|
286c26d18b |
test(docs): прокручивать настоящий скроллер превью (#600)
После #600 единственный скроллер диалога находится в shadow-root .content. Съёмка продолжала менять scrollTop у старой .body, поэтому обычный кадр устройства и кадр Display preview получались побайтово одинаковыми. Тест фиксирует владельца скролла и запрещает возврат к неработающей .body. Issue: #600 User-Visible: no |
||
|
|
c3e64dc60a |
feat(dialogs): финал #600 — мёртвые стили, парные кадры, документация, потолки
Завершение четырёх серий по референсу docs/design/600-settings-dialogs/: - из eager-листов dialogs.styles.ts и chrome.styles.ts сняты стили прежней разметки диалогов (.bindsel/.bindharow/.entcheck, .ctrlchip*, .markerhelp*, .helpfieldlabel, .markerradios, .markerlightdisabled, .markerbadgetechnical, .markerglowvalue, .fileupload, .descin, .pdfedit/.pdftag, .gsrow, .ripple-*row, .radaradditional, .togglehint, футеры marker/room) — 5,8 КБ исходника; с разметки сняты уже пустые классы; - «Additional actions» карточки Details — подзаголовок и строка действий набора, без раскрывашки (radar-section.ts, только additional-placement); - Q3 уточнён по кадру: hp-device-preview стоит без обёртки — у него свой заголовок «Display preview · Now», второй над ним удваивался; пустое состояние — блок на тинте; - §6.1: «?» у комнаты стоит у поля Home Assistant area, а не у заголовка Basics; подписи сегмента заливки — None · Zigbee · Lights · Temperature · Custom (два коротких ключа room.fill_seg_*), источники — «Average of the room’s sensors / Specific device or entity», режим свечения — «Colour and brightness» (тексты прежних ключей укорочены под сегменты); - подпись поля с «?» и без стоят на одной линии в сетке (min-height 32 по кнопке hp-help); иконка внутри подсказки — inline; - парные кадры: demo/capture_design_pairs_600.mjs снимает четыре диалога в двух темах на 560 в состояниях макетов → docs/design/…/pairs/ (8 PNG, палитра 256, ~0,5 МБ); ACCEPTANCE.md — статус по диалогам, расхождения с референсом по каждому со ссылкой на пункт SPEC и принятые адаптации (шапка hp-dialog по §3, тумблеры набора по К7, секции радара/пылесоса по §7.2); - changelog EN/RU («Не выпущено»), USER-GUIDE ru/en: раздел «Как устроены диалоги настроек», таблица настроек пространства по новым карточкам, диалог комнаты, диалог устройства, ссылка «Как у пространства»; - бюджет: стартовый граф 289 853 — потолок 291 600 → 290 900 (снятые стили); ленивый редакторский 243 149 — итоговый потолок 243 900 с разбором по сериям в bundle-budget.mjs. Golden: 11 сцен диалогов (device-dialog-*, room-temperature-dialog-*, toggle-entity-dialog-*, device-help-popover-light-ru, general-color-popover-desktop-en, settings-help-zoom-200-*, device-ripple-color-popover-mobile-ru, space-room-color-popover-desktop-ru) прогнаны диагностически (capture, HP_ALLOW_FOREIGN_CAPTURE, только artifacts/): харнесс проходит все пути без исключений, кадры «different» — эталоны переснимаются на Linux CI / машине владельца по #455. Скриншоты документации (06-device-editor, 06-device-display-preview и др.) устарели по отпечатку — пересъёмка перед кандидатом беты (#479). Гейты: typecheck, build, bundle:sync (три копии байт в байт), npm test 2793 pass по четырём частям, mutation-gate --check, check-inputs --coverage, фокусные смоки четырёх форм и смоки диалога устройства. Issue: #600 User-Visible: yes |
||
|
|
6598af51d9 |
test(form-kit): срез сегмента до следующего экспорта — мутант drops-radio-semantics ловится (#600)
Срез «от export function segmented до конца файла» находил type="radio" у radioRow и choiceCards ниже, и подмена радио на чекбокс внутри самого сегмента проходила зелёной. Срез ограничен телом функции; мутант form-kit-segment-drops-radio-semantics снова красит тест. Issue: #600 User-Visible: no |
||
|
|
bf6602bbd8 |
feat(device): «Устройство на плане» по референсу — серия 4 из четырёх (#600)
§7 SPEC.md референса. Пять карточек #598 остались, менялось содержимое; все ключи _markerDialog и все записи в них прежние (§7.2): - Basics — баннеры привязки стали callout'ами; Name с подсказкой «Shown on the plan»; привязка — сегмент [Virtual | From the HA list] (те же радио bmode) и кнопка выбора #marker-binding с панелью в потоке (sourcePicker набора), чекбокс «Show entities» — внутри панели рядом с поиском (решение владельца Q8); Room селектом с подсказкой «по зоне / переопределяет»; секции радара и пылесоса — своими модулями без изменений; - Tap action — селект с подсказкой Target/Now, «Ask for confirmation» — строка-тумблер только при toggle/run (Q8), Entity to toggle и What to run теми же условиями (список целей — панель набора), «Controls other light sources» — чипы набора и поиск с панелью в потоке; - Light and glow — «Include the device temperature…» строкой-тумблером; роль — сегмент [Auto | Always | Never] с подсказкой результата Auto; Leading light entity по условиям; блок свечения (.hpf-block): подзаголовок с «?», сегмент [From source | Set colour | Colour and brightness] с отключёнными вариантами и aria-describedby на объяснение, плашка цвета вокруг прежнего hp-color-opacity (Q5), яркость и радиус строками с числом и единицей, при Never блок неактивен целиком (aria-disabled) с callout'ом причины; - Appearance — поле иконки с превью, очисткой и ссылкой Pin в подсказке; Display селектом с подсказкой режима; пульс — плашка цвета и строка размера; Value source по условиям; подраздел Value badge — компактный тумблер, Value селектом с техническим идентификатором, Position сегментом [Above | Right | Below | Left]; предупреждения бейджа — заметки и callout'ы; Display preview — прежний hp-device-preview целиком в блоке на тинте с тегом Now (решение владельца Q3); «Icon size and rotation» — две строки range-line в сетке; - Details — Model и Link в ряд, Description textarea набора, Manuals чипами и строкой действий Attach…, «Additional actions» подзаголовком набора. Футер: Hide и Delete слева, статус, Cancel и Save справа. К10: снимок черновика при _openMarkerDialog (marker-form-state.ts поверх dialog-baseline; транзиентные ключи — раскрытие и фильтры списков, busy, объявление подсказки, autoIcon, uploadId); в edit Save только при изменениях, закрытие с изменениями спрашивает через _confirmDanger. Прежние запреты Save (busy, калибровка радара, привязка не выбрана/не подтверждена) остались; привязка и радиус свечения названы словами под полем (marker.error_binding, marker.error_glow_radius), «Review N fields» ведёт к полю. Тумблеры диалога — строки набора (нативный чекбокс, К7), а не ha-switch; слайдеры — прежний _rangeInput (ha-slider в HA). Набор: sourcePicker получил toolbar и disabled; сегмент — value у радио, disabled и describedBy у варианта; callout — role note и id; unitInput — describedBy; rangeLine — id; новые классы .hpf-block/.hpf-disabled, .hpf-toolbar/.hpf-check, .hpf-iconfield/.hpf-iconpreview, .hpf-mono, .hpf-note, .hpf-hint-line, textarea.hpf-input. settingsCopy вынесен в editors/settings-copy.ts (общий для пространства, комнаты, устройства). Смоки переписаны осознанно, с пометкой #600: binding_ui (#marker-binding, .hpf-panel, #marker-show-entities), tap_run (панель набора, #marker-tap- confirm), value_face_source (.hpf-cand.sel), static_icon (callout'ы), climate_temp (#marker-use-climate-temp), help_affordance (#marker-name, .hpf-block.hpf-disabled вместо fieldset[disabled]; второй Escape в грязном диалоге спрашивает — К10), size_angle_parity (сетка Appearance), color_picker_consumers (подписи полей), device_preview_parity (сегмент стороны), ha_controls (тумблеры набора — нативные чекбоксы по К7, ha-slider прежний). Golden-harness device — .hpf-field/.hpf-block, скроллер .content. Новый фокусный свидетель demo/smoke_device_settings_form.mjs: пять карточек без старой разметки, оболочка, привязка/панель/Show entities, Q8, сегменты и блок свечения, плашки, иконка, бейдж, превью на тинте, размеры, Details, цели 44, Save/dirty/вопрос при закрытии, create-режим, семь полос экранов. Мутанты: marker-save-enabled-without-changes, marker-discard-without-asking, marker-confirm-row-shown-for-do-nothing, marker-never-keeps-glow-block-live, marker-badge-position-forgets-touch; якоря same-binding-click-resets-source, state-callout-hidden-under-help, form-kit-segment-drops-radio-semantics переставлены на новую разметку. Контрактные тесты: color-picker (два поля набора вместо flex-строк), native-select-contract (сторона бейджа — сегмент), i18n dead-keys (34 → 38 подсказок), German=English (marker.name_label). Словарь i18n/settings: 21 ключ marker.* на четыре локали; в основном каталоге marker.name_label «Name», marker.size_label «Icon size and rotation», удалены мёртвые marker.room_override, marker.light_role_always/never. Фикстуры листа диалогов набора пересняты намеренно (панель — байт в байт, AC11). Бюджет: стартовый граф 290 489 (потолок 291 600 без изменений); ленивый редакторский 243 173 — потолок 240 100 → 243 900 с разбором в bundle-budget.mjs; старые стили диалога устройства снимаются в финале. Гейты: typecheck, build, bundle:sync (три копии байт в байт), npm test 2790+ pass по четырём частям, mutation-gate --check, 39 смоков диалога устройства + новый + три формы серий 1–3. Issue: #600 User-Visible: no |
||
|
|
e2c0c7ed81 |
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 |
||
|
|
de71bd7967 |
feat(general): «Общие настройки» по референсу — серия 2 из четырёх (#600)
§5 SPEC.md референса. Семь карточек #598 остались, менялось содержимое: - Display — две полные строки с иконкой, подписью и тумблером справа; пояснение радара по §5.2 стало подписью строки (текст прежний, ключ переехал в ленивый словарь как gs.radar_show_live_hint, «?» у строки больше нет); - Zigbee links — заголовок и «?» функции (topology.help) рисует карточка, а блок hp-zigbee-topology-settings получил режим embedded: строка-тумблер, callout «сохраните настройку», подзаголовки ZHA / Zigbee2MQTT, поле тем и строки действий контролами набора. Заголовок больше не удваивается (дефект 1). Лист набора вносится в теневой корень компонента; токены --hpf-* наследуются с хоста. Прежний render без embedded не тронут — его держит контракт #459; - Room fill colors — три подзаголовка (Lights / Temperature / Zigbee signal, Q6) и плитки цвета по три в ряд: название на свотче, hex, число прозрачности; свотч — trigger прежнего hp-color-opacity (Q5); - Light-source glow — две плитки и поле радиуса с единицей внутри рамки; - Plan — плашка цвета стен, сегмент фона, при Static — плашка фона с Reset и подсказкой (тема / свой цвет); - Sun — gs.sun_missing остаётся callout'ом (К5); север: поле градусов с «°», ссылка Clear, «N» и компас 44 px справа — индикатор набора, но тянуть его по-прежнему можно (smoke_sun гоняет настоящий drag; старый 120-px SVG и его CSS в plan.styles.ts удалены); лучи — строка-тумблер с подписью; источник лучей — сегмент (sun-settings-view.ts переписан с <select> на segmented, логика та же); - Data — подзаголовки с «?» и строки действий набора. К10 (Q1): Save только при изменениях и без ошибок (радиус > 0, север 0–359), статус в футере, вопрос при закрытии с изменениями; снимок при открытии — editors/general-form-state.ts поверх общего editors/dialog-baseline.ts (на него же переведён space-form-state.ts). _settingsDialog не получил новых ключей. Удалено мёртвое: _renderColorRow (рантайм и карточка), .sunrow/.suncol, ключи gs.wall_group, gs.bg_group, gs.bg_theme, gs.radar_show_live.help(.aria) в четырёх локалях. Девять новых ключей — в i18n/settings. Смоки переписаны осознанно, с пометкой #600: smoke_general_settings (инвентарь по классам набора, подзаголовки .hpf-sub h4, пояснения под «?» и подписью, ни одного .rhint), smoke_sun (сегмент источника лучей вместо select, поле #gs-north, radius drag 18 px), smoke_backup_transfer (подзаголовок и строка действий), smoke_settings_dialog_cards (callout вместо .rhint). Новый фокусный свидетель demo/smoke_general_settings_form.mjs: семь карточек и отсутствие старой разметки, один заголовок у Zigbee, десять плиток, компас 44, цели 44, запись плиток/плашки/радиуса/севера/сегментов в свои ключи, Save/dirty/вопрос при закрытии. Мутанты: general-save-enabled-without-changes, general-tile-opacity-writes-a-neighbour-key, general-north-clear-writes-zero, topology-embedded-draws-its-own-heading; якоря state-callout-hidden-under-help и dialog-card-loses-its-heading переставлены на новую разметку. Контрактные тесты: houseplan-source.mjs приводит `host.` к `this.` и в телах вынесенных диалогов; color-picker — три явных showOpacity=true (плитки, плашка стены, цвет комнаты); счётчик производных .aria 35 → 34. Бюджет: стартовый граф 290 711 — потолок ОПУЩЕН 292 500 → 292 200 (ушёл 120-px компас из eager-листа и мёртвые ключи); ленивый редакторский 237 929 — потолок 236 300 → 238 600 с разбором в bundle-budget.mjs. Гейты: typecheck, build, bundle:sync, npm test 2792 pass, mutation-gate --check, семь смоков «Общих», smoke_color_picker_consumers без правок. Issue: #600 User-Visible: no |
||
|
|
91a008db9c |
feat(space): диалог «Пространство» по референсу — серия 1 из четырёх (#600)
§4 SPEC.md референса, четыре карточки: Basics (имя и масштаб в сетке 1fr/175, подложка двумя choice-карточками, callout о пересчёте масштаба), Appearance (строка-тумблер границ с иконкой и подписью, сегмент стиля стен с образцами линии, плашка цвета с hex/прозрачностью/Reset, сегмент заливки с деталями режима, компактный список «Видимые слои» с логикой «включено = видно» — К2), Room cards (строка-тумблер названий, четыре плитки значений с отключением и callout-ссылкой, слайдер размера с числом, концами и «Сбросить до 100%», образец карточки внутри блока на тинте — Q2), Sun & light (сегмент фона, плашка цвета фона, радио «свет через окна» в строку, север селектом с компасом 44 px и полем градусов, строка-тумблер свечения). Одна форма на два рантайма. `renderSpaceForm` в `editors/space-form.ts` собирает тело и футер; редакторский вход (`space-settings-dialog.ts`) и онбординг подают порт с тем, что у них различается, и сами рисуют `<hp-dialog data-kind=…>` — контракт #489 требует литеральный kind. Копия разметки онбординга на 300 строк удалена; локальные дубли CELL_CM_* и strictNumber там же — тоже («одно число — один источник»). К10 (решение владельца Q1): Save активен только при изменениях и без ошибок, в футере «Есть несохранённые изменения» либо ссылка «Проверьте поля: N» к первому ошибочному; «✕», Cancel и Escape с изменениями спрашивают через `_confirmDanger`, без изменений закрывают сразу. Снимок делается при открытии (`editors/space-form-state.ts`, WeakMap по хосту) — `SpaceDialogState` не получил ни одного ключа. Валидация: имя, подложка без изображения, диапазон температур, север 0–359 — с `aria-invalid` и текстом под полем. Строки. 51 новый ключ живёт в ленивом словаре `i18n/settings` (приём #423/#459), не в первом кадре: стартовый граф 291 260 при потолке 292 500. Отдельный словарь, а не `support`, — иначе онбординг тянул бы весь словарь редактора ради одной формы. 11 существующих ключей переименованы по Q6 («Space name», «Appearance», «Room cards», «Draw it myself», «Zigbee signal next to devices»…), 9 мёртвых удалены из четырёх локалей. `hp-color-opacity` получил `hide-label`: подпись рисует плашка, `label` остаётся для title/aria и `smoke_color_picker_consumers` проходит без правок (AC5). Смоки. Переписаны осознанно (К6 снят), каждый — с пометкой #600 у правки: smoke_hide_layers (компактные строки, инверсия К2 проверяется кликом), smoke_ux_fixes (сегмент, строка-тумблер, поля °C, ширина 560 — Q7), smoke_dialog_footer_width (560 для form-shell), smoke_temp_fill (два поля по id), smoke_settings_dialog_cards (три сегмента), smoke_space_settings и smoke_space_create_display_defaults и smoke_lazy_editor_chunk (choice-карточка по смыслу, не по индексу — порядок §4.2 draw → file), smoke_space_scale_defaults (поле с единицей). Новый фокусный свидетель demo/smoke_space_settings_form.mjs: состав карточек и отсутствие старой разметки, оболочка 560 и один скроллер, цели 44, запись каждого нового контрола в свой ключ, Save/dirty/вопрос при закрытии, семь контрольных ширин (320…1920). Мутанты: space-fill-segment-writes-a-neighbour-key, space-save-enabled-without-changes, space-discard-without-asking, space-layer-toggle-loses-inversion. Контрактные тесты: `houseplan-source.mjs` дописывает общие модули диалогов к логическому исходнику; коллектор i18n видит `help(...)` порта и `shelp(...)` (заодно встал на учёт `radar.help`, проходивший мимо счётчика — 30 → 35). Потолок ленивого редакторского графа 225 900 → 236 300 (замер 235 560) с разбором в bundle-budget.mjs: словарь на четыре локали и подписи — большая часть роста; промежуточно, итог в конце серий. Гейты: typecheck, build, bundle:sync, npm test 2792 pass (красным был только потолок — поднят с доводом), 10 смоков пространства зелёные, smoke_color_picker_consumers без правок, mutation-gate --check. Issue: #600 User-Visible: no |
||
|
|
30da00403d |
feat(form-kit): набор контролов по референсу и оболочка диалогов настроек
Серия 0 из пяти по #600. Ничего из четырёх диалогов ещё не переложено — эта серия даёт им инструменты и оболочку. Набор (`form-kit.ts` + `form-kit.styles.ts`), числа из §3.1 SPEC.md референса, цвета через переменные темы HA: - полная строка настройки `toggleRow` — сетка 28/1fr/44: иконка, название + «?», подпись, тумблер справа; тумблер — нативный чекбокс, нарисованный CSS, как у редактора панели: один вид в HA, на стенде и в golden, и всё тот же `input[type=checkbox]` для смоков; компактный вариант для однотипных списков; - `field`, `fieldGrid` (две колонки и 1fr/175), `subsection`, `unitInput`, `rangeLine`, `rangeEnds`, `radioRow`, `choiceCards`; - `valueTiles` (4 колонки), плитки цвета в CSS; `colorField` — компактная плашка вокруг существующего `hp-color-opacity` (Q5): свотч открывает ту же панель, рядом hex, число прозрачности и Reset; нативного пикера нет; - `callout` (К5: сообщения о состоянии остаются на виду), `compass` 44 px, `footerStatus`, `chip`, `tintBlock`, `textLink`, `divider`; - `segmented` получил образцы линии, иконки и `disabled`; вид — как в референсе: контейнер на канве, активный сегмент на тинте. Оболочка (`hp-dialog.ts`, атрибут `form-shell` у четырёх диалогов настроек): ширина 560 через `--hp-dialog-wide-width`, которую `:host([wide])` уже читал; высота min(940px, 100vh − 48); единственный скроллер — `.content`, тело формы высотой не владеет (К6, дефект 7 из issue); «✕» 44 px; бейдж имени пространства в заголовке (`badge`). Generic `wide` и остальные диалоги не тронуты (Q7). `hp-dialog[form-shell] .body` — без max-height и overflow, канва 16 px. Фикстуры листа диалогов `test/fixtures/form-kit-card-dialog*.css` пересняты намеренно; лист панели совпал с замороженным побайтово (К9, AC11). `editor.loading` → «Загрузка…» в четырёх локалях (дефект 9); aria-текст прежний. Потолок ленивого редакторского графа 222 900 → 225 900 с доводом в `bundle-budget.mjs`: рост — CSS-строки набора, не код; значение промежуточное, серии 1–4 выкидывают старые правила, итог замеряется в конце. Гейты: typecheck, build, bundle:sync, `npm test` (2791 pass; красными были только потолок — поднят — и неотслеженные файлы манифеста — добавлены), `smoke_settings_dialog_cards`, `smoke_room_settings`. Issue: #600 User-Visible: no |
||
|
|
f5365f49c4 |
fix(iso): считать подсказку только верхней границей (#585)
Кешированное смещение больше не завершает поиск до точной проверки пересечений с границами комнаты и стен. Добавлен регрессионный тест для более близкого решения у диагональной границы. Issue: #585 User-Visible: no |
||
|
|
e793993c66 |
perf(iso): ускорить точный граничный поиск (#585)
Issue: #585 User-Visible: no |
||
|
|
4870e08d3d |
perf(iso): ограничить граничные события (#585)
Перечисляются только реальные пересечения конечных границ, новые границы обрабатываются инкрементально, а дробные координаты дают только необходимые floor/ceil-соседи. Issue: #585 User-Visible: no |
||
|
|
ed07c3ad5f |
fix(iso): учитывать реальные границы стен в поиске (#585)
Issue: #585 User-Visible: no |
||
|
|
6f226a5b6c |
fix(iso): находить узкие щели по границам препятствий (#585)
Issue: #585 User-Visible: no |
||
|
|
f736419119 |
test: сделать pinned Windows gate воспроизводимым (#576)
Issue: #576 User-Visible: no |
||
|
|
c22a1f43db |
feat(editors): три диалога настроек собраны в карточки-группы
«Общие настройки», «Пространство» и «Устройство на плане» были плоскими столбцами. В «Общих» девять подписей-заголовков шли вперемешку с рядами и абзацами; в «Устройстве» на 760 строк формы не было ни одного деления, и «Название» лежало в одном списке с «Дополнительными действиями». Теперь это карточки общего набора: семь в «Общих», четыре в «Пространстве», пять в «Устройстве». Набор взят как есть — он поднят в #594 и доказан на диалоге комнаты и редакторе панели (#597). Шесть пояснений переехали под «?» к своему заголовку, подзаголовку или строке; текст не переписан. Два сообщения о СОСТОЯНИИ остались на виду — решение владельца: `gs.sun_missing` и `marker.run_target_gone` говорят, что настройка сломана прямо сейчас, а второе ещё и называет пропавшую цель подстановкой, которой в статической подсказке взяться неоткуда. `gs.hint` при этом переехал из ленивого support-словаря в основной: «?» читает текст через `_t`, а не `supportT`. Тест мёртвых ключей сливает оба словаря, так что копия в support удалена во всех четырёх локалях. Классы-опоры сохранены намеренно: `.srcrow`, `.dispsection`, `.gsrow`, `.colorrow`, `.namein`, `.areasel`, `.tempin` остались на своих элементах, а карточки добавились вокруг. За эти имена держатся одиннадцать смоков, и потеря любого превратила бы ревью в чтение переписанных свидетелей. Изменён ровно один смок — `smoke_general_settings`, и изменён в сторону строгости: вместо одного списка подписей он снимает теперь два (заголовки карточек и подзаголовки внутри), требует непустоты каждой карточки, проверяет что радиус свечения лежит ВНУТРИ карточки свечения (подпись такой границей не была), что четыре переехавших пояснения доступны кнопкой и что их абзацев в форме не осталось. `src/houseplan-onboarding-runtime.ts` затронут двумя строками: он второй потребитель переименованных ключей `space.hide_*_tip` → `space.hide_*.help`. Один текст — один ключ; дублировать его ради неприкосновенности файла хуже. Потолок стартового графа поднят 291 400 → 292 500. Выросли СТРОКИ, а не код: 12 новых ключей в четырёх локалях плюс переехавший `gs.hint`. Разметка диалогов как была в ленивом редакторском графе, так там и осталась. Свидетели: `state-callout-hidden-under-help` прячет сообщение о состоянии под «?» и краснит новый блок `smoke_tap_run`; `dialog-card-loses-its-heading` обнуляет заголовок карточки и краснит `smoke_general_settings` — карточка без заголовка это снова плоский список, только с рамкой. Issue: #598 User-Visible: yes |
||
|
|
b28907f63d |
refactor(styles): редактор панели переезжает на общий набор контролов
Набор контролов был поднят из редактора боковой панели в #594, но сама панель осталась на своей копии тех же правил. Две копии расходятся молча: правка в наборе не доедет до панели, и наоборот. Просто вызвать `formKitCss` из панели нельзя. Общие правила в её листе не идут подряд: между карточкой-группой и обводкой фокуса стоит сетка `.summary-general-grid`, между фокусом и выключенным состоянием — `button { font: inherit }`, между ним и рядом-переключателем — `svg`. Вставить один общий кусок значило бы переставить её объявления, а перестановка правил равной специфичности — уже не подстановка: проверять пришлось бы каждое правило по отдельности. Поэтому генератор разрезан на пять именованных фрагментов — карточка-группа, обводка фокуса, выключенное состояние, ряд-переключатель, подпись ряда, — и панель подставляет каждый на своё место. `formKitCss` остаётся их композицией, чтобы у диалогов не появилось пяти вызовов вместо одного. Доказательство вместо обещания. У настроек панели нет ни одного golden-кадра: в матрице нет сцены `dialog: 'summary'`, и «панель не изменилась» показать пикселями нечем. Замена эталону — побайтовое сравнение собранного листа с замороженным снимком с `origin/dev`: длина было 14585 стало 14585 — ПОБАЙТОВО СОВПАЛО Две находки ревью ТЗ закрыты тестами, а не формулировками: - проверка «копии не осталось» ловила один фрагмент из пяти (`min-height: 54px` встречается только в ряду-переключателе). Теперь цикл по всем пяти текстам; проба — фрагмент карточки-группы, оставленный в комментарии, — краснит именно этот цикл, а прежняя проверка была бы зелёной; - «выход `formKitCss` остаётся байт в байт» опиралось на тесты, сравнивающие подстроки и счётчики. Добавлены две фикстуры с полным текстом обеих форм листа диалогов — с рядом-переключателем и без. Форма с переключателем сегодня не используется никем, и её расхождение не поймал бы никто до первого потребителя. Три замороженные фикстуры — осознанный долг: они удаляются на том шаге эпика, который законно меняет вид панели. Записано рядом с фикстурой. Issue: #597 User-Visible: no |
||
|
|
7df2740f1c |
feat(furniture): пакет 0.4.0 — 60 дизайнерских символов вместо 44 плюс 12 примитивов
Встроенная библиотека мебели была смешанной: 44 предмета рисовал дизайнер, а 12 — код, прямоугольниками и эллипсами в unit box. Они выбивались из стиля, и три категории меню (`computer`, `hood`, `oven`) вообще не открывались, потому что вида сверху для них не было. Пакет 0.4.0 перерисовывает все 56 существующих публичных ID и добавляет четыре новых: `computer`, `hood`, `oven`, `cactus`. Каталог становится равен поставке: 60 символов, 32 непустые категории. Совместимость. Ни один сохранённый план не мигрирует: положение, размер, поворот, отражение, цвет, прозрачность и толщина линии не трогаются, а default-размеры всех 56 прежних ID совпадают со старыми до числа — таблица зашита в `test/furniture.test.mjs`, чтобы сдвиг было видно тестом, а не глазами. Ловушка, ради которой примитивы удалены целиком. Библиотека собиралась союзом каталога и `LEGACY_FURNITURE`, и обе половины несли ОДНИ И ТЕ ЖЕ публичные ID. `BY_ID` — это `Map`, она оставляет последнюю запись, поэтому примитив молча побеждал дизайнерский рисунок, который его заменял: задача выглядит выполненной, 12 предметов не обновились, ни один существующий тест этого не видит. Поэтому `LEGACY_FURNITURE`, `RETAINED_IDS`, тип `Prim`, `box()` и `primitivePathD` удалены, а на дубликаты в каталоге стоит тест и мутант. Два продуктовых решения владельца (#593): - `cactus` живёт в категории `plant`, а не `exercise`, как предлагал пакет: плитка «Тренажёр» рисует тренажёр, и открывать её на кактус — врать о содержимом палитры. `exercise` остаётся скрытой пустой категорией, непустых становится 32, а не 33; - при отказе ленивого чанка с рисунками не рисуется НИЧЕГО. Прежде 12 примитивов рисовались всегда, и `docs/FURNITURE.md` это обещал; обещание снято. Это единственное место, где задача ухудшает наблюдаемое поведение, и оно проверяется смоком, а не словом. Бюджеты. Ревью ТЗ показало, что «бюджет ленивого графа защищён» было заявлением без гейта: размеры печатались в отчёт и ни с чем не сравнивались. Добавлены `lazyGraphCeilingViolation` и два настоящих потолка со своим мутантом. Стартовый граф ушёл ниже полосы, поэтому `INITIAL_VIEW_GZIP_CEILING` опущен 292 400 → 291 400 — вторая половина храповика, без которой выигрыш отыграется. Провенанс. Документ пакета воссоздан под 0.4.0: грант владельца, имя архива и его SHA-256. Это единственная исполнимая защита от повторения истории #159, и она не ослаблена — число проверок в тесте не уменьшилось, а мутант подменяет хеш и обязан её покраснить. Свидетели: пять новых мутантов, новая golden-сцена `furniture-new-symbols-light` для четырёх новых ID, матрица 63 → 64. Шестой мутант правится, а не добавляется: `furniture-placement-needs-art` ссылался на удалённое поле `g` и переставал компилироваться — раннер честно отличал это от «пойман» и отчитывался setup-failure. Ложь мутанта осталась прежней по смыслу, убрана только ветка про примитив. i18n: 7 ключей × 4 локали. `furn.cat_exercise` не добавлен — категория скрыта. Issue: #593 User-Visible: yes |
||
|
|
4344ede13a |
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 |
||
|
|
337d85ea4c |
fix(process): слияние не обрывает дифф кандидата и называет причину сбоя запуска
Шаг слияния конвейера считает patch-id материала и кандидата через `git diff --full-index`, когда `dev` сдвинулся за время ревью. Дифф задачи, пересобирающей бандл, несёт три копии `houseplan-card.js` — у #594 это 7 103 616 байт. `sh()` не задавал `maxBuffer`, умолчание `spawnSync` — 1 МиБ, и процесс убивался по ENOBUFS на 1 027 868 байтах. Вторая половина дефекта прятала первую: `r.error` нигде не читался, а `status: r.status ?? 1` выдавало `null` за «git вернул 1». `must()` брал `r.stderr || r.stdout`, stderr был пуст — и в issue уезжало «Слияние не выполнено: diff: <огрызок диффа>». Огрызок выглядит осмысленным и уводит разбор в сторону. Путь исполняется только при сдвинувшемся `dev`, поэтому дефект дремал. Но конвейер сам пушит в `dev` документ ревью на каждом раунде, так что `devMoved` — норма, а не исключение. Что изменено: - `MAX_COMMAND_OUTPUT_BYTES` (256 МиБ) и явный `maxBuffer` в `sh()`; - `sh()` называет сбой запуска в `stderr` результата вместо молчания; - `sh()` экспортирован, у обоих пунктов есть исполнимый свидетель; - мутант `merge-candidate-truncates-the-candidate-diff` возвращает 1 МиБ. Issue: #596 User-Visible: no |
||
|
|
5b6d5d1d8f |
test: объявленный Baseline-Reviewed прогон проверяется отдельно от вершины ветки
`test/release-gate.test.mjs` строил ожидания из живого состояния репозитория, а `localEvidence` читает `baselines.reviewedRun` из сообщения HEAD-коммита. Пока вершиной был обычный коммит, поле оставалось `null` и всё сходилось. Как только вершиной становился коммит приёмки эталонов, поле становилось непустым, и `evaluateCiProof` законно требовал объявленный прогон — которого заглушка `loadContext` не отдавала. Тест краснел на всей ветке, ради которой #573 и писалась, и вместе с ним краснел бы `dev` сразу после слияния такой ветки. Воспроизведено на `issue/594-form-kit-room@68bb7558`: 2776 pass / 1 fail, из-за чего конвейер ревью не запустился вовсе. Продуктовый код не менялся: контракт «объявленный прогон обязан существовать, быть завершённым, не отменённым и быть Validate» — ровно то, что задумано, и он намеренно не требует `success`, потому что приёмка эталонов по определению ссылается на прогон, где golden покраснел. Что изменено: - зелёный путь теста считается на evidence с `baselines.reviewedRun: null` и больше не зависит от того, каким коммитом оказалась вершина; - контракт объявленного прогона вынесен в отдельный тест и проверяется с трёх сторон: завершённый неотменённый Validate с `conclusion: 'failure'` — зелёно; прогона нет — отказ; прогон отменён — отказ; - мутант `declared-baseline-review-run-never-checked` обнуляет объявление и краснеет на новом тесте. Доказательство на фикстуре «вершина — приёмка эталонов»: старый тест 8 pass / 1 fail, новый 10 pass / 0 fail. Issue: #595 User-Visible: no |
||
|
|
1403fb440b |
test: давать послабление переноса блоку, а не строке (#592)
Находка M1 код-ревью r1, воспроизведена: первая редакция сопоставляла одиночные строки по всему диффу, и этого хватало для обхода. Несвязанная уборка удаляет где-то строку с `any`, новый код добавляет свою — текстуально такую же, — и гейт молчит. Совпадение здесь не экзотика: в базе 887 явных `any`, типовые однострочники повторяются буквально, и две такие строки встретились в самом коммите переноса. Теперь перенесённым признаётся только непрерывный кусок не короче пяти строк, встречающийся подряд и целиком среди удалённых строк ОДНОГО файла. Случайно совпасть пятью строками подряд практически невозможно, а настоящее извлечение подсистемы из таких кусков и состоит: на этом диффе признано 1296 строк из 1395 — на одну меньше, чем при построчном сопоставлении, и эта одна была ровно случайным совпадением. Каждый удалённый кусок оплачивает ровно одно добавление: повторная вставка того же блока остаётся новым кодом. Мутант заменён на `no-new-any-forgives-a-single-matching-line` — он опускает порог до одной строки, то есть открывает ровно найденную дыру; тест обхода на нём краснеет. Тестов пять: перенос куска, обход одиночной строкой, кусок короче порога, бюджет на повторную вставку, смена отступа. Issue: #592 User-Visible: no |
||
|
|
61c74a7012 |
test: не считать дословный перенос новым any (#592)
Validate вернул задачу с красным `no-new-any`: шесть строк в новых модулях диалогов. Все шесть перенесены побайтово из редакторского рантайма — `as any` в них написан годы назад и в этом диапазоне не менялся. Счёт долга это подтверждает: явных `any` в `src/**` было 887 и осталось 887. Гейт мерил «добавленные строки», и для извлечения подсистемы такая мера ложна: она требует типизации ровно там, где ничего не изменилось. Причём единственный способ её удовлетворить — дописать `any-ok` в перенесённое тело, то есть сломать доказательство переноса (тела обязаны совпадать побайтово) ради зелёного гейта. Это починка задом наперёд. Теперь строка, дословно удалённая из одного файла диапазона и добавленная в другой, новым кодом не считается. Бюджет ведётся мультимножеством: два добавления при одном удалении оставляют одну находку, а перенос с изменённым отступом переносом не считается — это уже правка. Отчёт печатает, сколько строк признано перенесёнными, чтобы послабление было видно, а не подразумевалось. Гейт #342 при этом остаётся собой: новый `any` без удаления-близнеца — находка, и три новых теста проверяют обе стороны. Мутант `no-new-any-counts-every-added-line-as-moved` признаёт перенесённой любую добавленную строку — заявленный тест на этом краснеет. Отклонение от скоупа ТЗ: `scripts/no-new-any.mjs` в нём не назван. Альтернатива — шесть `any-ok` в перенесённом коде — стоила бы побайтовой сверки, на которой держится вся приёмка задачи. Issue: #592 User-Visible: no |
||
|
|
b76f3e5756 |
refactor: вынести четыре диалога настроек в модули (#592)
Шаг 0 эпика #591. Видимых изменений нет — это подготовка: оба ядра стояли в трёх строках от жёсткого потолка, а разметка диалогов была крупнейшей перемещаемой частью редакторского рантайма. Перенос механический и проверяемый. Тела всех четырёх методов уехали побайтово: тип `this` объявлен параметром функции, поэтому ни одна строка разметки, ни один обработчик и ни один якорь мутанта не переписаны, а в классе остались однострочные делегаты. Сверка при подготовке коммита: 43 617, 16 795, 10 590 и 6 719 символов совпали посимвольно с оригиналом. src/editors/marker-dialog.ts диалог устройства на плане src/editors/space-settings-dialog.ts диалог пространства src/editors/general-settings-dialog.ts общие настройки src/editors/room-settings-dialog.ts настройки комнаты Имена файлов пространства и комнаты уточнены против ТЗ: рядом уже живёт `src/space-dialog.ts` с логикой того же диалога, и два `space-dialog.ts` в одном дереве путали бы обоих — и человека, и grep. Попутно из рантайма уехали два словаря ярлыков режима отображения (их единственный потребитель — диалог устройства) и границы шага сетки: последние теперь объявлены в модуле, который рисует поле, а кламп записи импортирует их оттуда. Направление импорта то же, что у самой функции рисования, цикла нет. `_radarSetup` стал публичным: диалог устройства обращается к контроллеру из своего модуля, а остальной класс и так живёт на `public _x`. Потолок ядра опущен 14100 → 12810 — ровно на 1290 вынесенных строк, запас над фактом прежний. Незафиксированный выигрыш ядро отыграло бы первой же правкой. Сборщик логического исходника для контрактных тестов (`test/houseplan-source.mjs`) научен подставлять тело вынесенной функции вместо делегата: шесть тестов, читающих разметку, продолжают видеть ровно то же, что и до переноса, — иначе их пришлось бы переучивать на новые пути в тот самый момент, когда они должны доказать, что перенос ничего не изменил. Issue: #592 User-Visible: no |
||
|
|
99e1765d22 |
feat: режим «Значение + статичный значок» (#588)
Пятый вариант «Отображение»: маркер показывает выбранное значение и при этом
никогда не меняет цвет — ни по состоянию, ни по тревоге, ни по недоступности,
ни под цвет RGB-света, ни по активности.
Четыре прежних режима задавали одним выбором две независимые вещи: что
нарисовано внутри маркера и красится ли он состоянием. Поэтому вместо
сравнения с одним токеном появились два производных предиката рядом с
`normalizeDeviceDisplay` — `displayWantsValue` и `displayIsNeutral`, — и их
спрашивают политика, слой пульсации, внешний бейдж, редактор и три ветки
живого слоя пылесоса.
Две ловушки, из-за которых режим не сводится к одной строке в словаре:
- быстрый путь «статичному маркеру источники не нужны» (`sourceDetails: false`)
— это основной путь рендера плана, карточки пространства и PDF. Он оставлен
только режиму без значения: иначе число пропало бы именно на плане и
осталось в предпросмотре редактора;
- три ветки живого пылесоса сравнивают режим строкой и не читают политику,
поэтому зелёная политика их не гарантирует. Свидетель держит две
конфигурации карты: сопоставленная доказывает puck и след, несопоставленная
— бейдж маршрута (при совпадающей калибровке маршрут `ready`, и бейджа не
было бы ни в одном режиме). Бейдж проверяется при наполненном буфере
позиций, иначе его отсутствие объяснялось бы первой веткой.
Потолок initial View перецентрирован 291_700 → 292_400 без изменения общего
бюджета: измеренный факт 291 346 Б оставлял под прежним центром 354 Б —
внутри шумовой полосы метрики.
Эталон: в `device-icon-state-table-{light,dark}` включённая RGB-лампа
переведена в новый режим. Кадры обязаны разойтись; приёмка — отдельным
коммитом класса D с полного линуксового артефакта Validate.
Issue: #588
User-Visible: yes
|
||
|
|
3f95dac451 |
fix: индекс эталонов хранит платформу съёмки, а не платформу приёмки (#571)
Аудит 14.09, §4 и §9. `golden-report.json` не нёс платформу вовсе, поэтому приёмщик вызывал `captureEnvironment()` у себя и записывал СВОЮ платформу как платформу кадров. На `ad4000f9` это дало `"platform": "win32"` у кадров, снятых Linux-прогоном 34853080375, а причина осознанного обхода осталась в stdout и в индекс не попала — хотя `AGENTS.md` обещает след в обоих местах. Сами PNG были и остаются целы: 101 свидетель совпал байт-в-байт. Врал провенанс. Что сделано: - отчёт съёмки получил раздел `capture`: платформа, архитектура, сборка Chromium, отпечаток материала и — в CI — прогон с попыткой и SHA. Схема отчёта поднята до 2; - приёмка читает среду съёмки из отчёта. Гейт чужой среды теперь судит обе стороны: съёмку (из отчёта) и приёмку (свою). Отказ — до единой записи; - индекс эталонов поднят до схемы 2 и различает `capturedOn` и `acceptedOn`, несёт раздел `capture` и причину осознанного обхода в `foreignCapture`; - отчёт схемы 1 платформы съёмки не несёт физически: это отдельная явная ветка, `capturedOn` уезжает `null`. Выдумывать платформу нельзя — ровно этим задача и вызвана; - `--baselines=<dir>` у приёмки: без него проверить «отказ произошёл ДО записи» можно было бы только порчей рабочего дерева, то есть никак (#556). Осознанно отменено решение #455 «не трогать run.mjs»: ради ГЕЙТА цена фингерпринта не окупалась, ради ПРОВЕНАНСА окупилась — платформу кадров знает только тот, кто их снял. Плата разовая: пересобран бандл, индекс скриншотов документации переснят отдельным коммитом. Тест #455 переписан под новый инвариант, а не удалён. Свидетели: `test/golden-capture-provenance.test.mjs` — семь проверок, все пять сценариев приёмки из issue, включая «отказ до записи» и «подмена PNG и неполный артефакт по-прежнему fail-closed». Мутанты `golden-index-invents-capture-platform` (возвращает платформу приёмщика) и `golden-report-provenance-optional` (разрешает отчёт схемы 2 без провенанса) прогнаны лично: оба краснеют. npm test 2762/2761/0 fail, typecheck чистый. Issue: #571 User-Visible: no |
||
|
|
2ea1e5e62a |
ci: reviewed run судится только release-потребителем (#573, ревью r1 M1)
`loadGithubProofContext` спрашивал объявленный `Baseline-Reviewed` run для любого потребителя, а `evaluateCiProof` судил его при `reviewedRun: null` — merge и review начинали зависеть от доступности старого run по чужой причине. Теперь запрос делается только с `withReviewedRun` (release-gate передаёт его вместе с ожиданиями), а проверка стоит внутри `if (expected)` — рядом со сверкой evidence, где ей и место. Тест: без ожиданий merge/review green при reviewedRun undefined/null/пустом; фейковый fetch доказывает, что запроса нет. Issue: #573 User-Visible: no |
||
|
|
47f36e571c |
ci: proof различает продуктовое дерево и overlay эталонов (#573)
Приёмка эталонов на beta.3 (`ad4000f9`) стоила второго полного Validate —
22 минуты, из них 17–22 на шард мутантов. Причина одна: корпус отпечатка
(`source-fingerprint.mjs`) называет `demo/golden` строкой-каталогом, а
замыкание входов раскрывало каталог во все текстовые файлы под ним, включая
`baselines-index.json`. Индекс становился входом smoke, performance_smoke и
каждого гарда через `serve.mjs`: на реальной паре C→B ключи smoke/perf были
DIFFERENT, отпечатки 181 из 183 браузерных свидетелей менялись, журнал их не
пропускал.
- `check-inputs.mjs`: `BASELINE_OVERLAY` — раскрытие каталога не выдаёт
overlay; явный корень golden и явная ссылка на файл — как были. На паре
C→B: ключи smoke/perf/parity/backend same, golden DIFFERENT; отпечатки
743 из 744 равны; план мутантов B с журналом C — 0–1 на шард вместо 38–44
- `ci-proof.mjs`: составное evidence — product tree без overlay, overlay
(tree, sha256 индекса, run из `Baseline-Reviewed`), content-ключи всех
реюзных job (исполненных тоже); `evaluateCiProof({expected, reviewedRun})`
сверяет с локальным расчётом, fail-closed на ключ, tree, индекс, reviewed
run, маркер с чужим ключом; proof без evidence при ожиданиях — stale
- `release-gate.mjs` / `release-prerelease.mjs`: ожидания считаются на
checkout кандидата (`candidateExpectations`), чужой checkout — notice
- мутанты: `baseline-overlay-leaks-into-every-key`,
`proof-trusts-evidence-it-could-verify`,
`reused-marker-key-unchecked-against-candidate`,
`product-tree-identity-counts-baselines`; перенацелен
`ci-proof-ignores-run-attempt`
- docs: TESTING (правило overlay), DEVELOPMENT (evidence в release proof),
STATUS
Issue: #573
User-Visible: no
|
||
|
|
ef3bc6d133 |
fix: габарит мебели равен заявленным размерам (#584)
Указанные ширина и глубина теперь совпадают с видимым габаритом предмета.
Раньше рисунок занимал внутри своего бокса около 88 % стороны, поэтому
«Кухонный напольный модуль» 60 × 60 выглядел меньше «Посудомоечной машины»
60 × 60 примерно на 14 % по стороне и на 30 % по площади: цифры совпадали,
картинка врала.
Пак `fix-584-1` от дизайнера (issue, комментарий от 16.09) проверен исполнением
своей метрикой, а не его отчётом: границы считаются аналитически по той же
математике, что у продакшен-экспорта в PDF. Все 44 плановых SVG заполняют
`viewBox` с худшим отклонением 0.000000 при допуске ТЗ 0,1; `pack.json` и 33
иконки меню побайтно прежние. Для сравнения, прежний набор в `dev` давал 65
нарушений того же контракта.
Что сделано в коде:
- `scripts/svg-path-bounds.mjs` — границы рисунка по настоящим экстремумам
кривых (дуги приводятся к кубикам). Контрольные точки в габарит не входят:
иначе «заполняет бокс» проверялось бы по выпуклой оболочке и поля проходили
бы мимо. Генератор — `.mjs` и обязан работать без сборки TypeScript, поэтому
разбор здесь свой; тест сверяет его с `parseSvgPath` продакшена на всей
библиотеке, чтобы реализации не разъехались молча.
- `scripts/furniture-path-join.mjs` — склейка нескольких `<path>` с
канонизацией только начального `moveto`. Приём подсказан патчем в поставке
дизайнера; реализация и свидетели свои. Прежний `paths.join(' ')` продолжал
координаты предыдущего пути: замер показал, что у иконок меню `stairs` и `tv`
части рисунка уезжали за `viewBox` 110 × 110 — до 119. Теперь склейка
побайтно воспроизводит «каждый путь из своего нуля».
- генератор проверяет плановый символ по контракту: один путь, подконтуры с
абсолютной `M`, рисунок заполняет `viewBox` (допуск 0,1).
- `src/furniture.ts` — legacy `plant` растянут от центра в 1/0.96. Это
единственный из двенадцати retained-примитивов с полями (0.02…0.98 unit box);
форма прежняя, габарит стал заявленным.
Свидетели: `test/furniture-visual-bounds.test.mjs` (AC1–AC3, AC5 и сверка двух
реализаций границ) и `test/furniture-path-join.test.mjs` (AC4, включая реальные
`stairs`/`tv`). У AC1 есть положительный контроль: та же функция обязана видеть
поля на геометрии из шапки issue — утверждение «полей нет» стоит ровно столько,
сколько стоит измерение. Мутанты `furniture-symbol-may-keep-inner-padding` и
`furniture-paths-joined-without-reset` возвращают прежнее поведение и краснеют;
прогнаны лично через `mutation-gate --id=`.
Миграции пользовательских данных нет: координаты, размеры, поворот и
зеркалирование сохраняются.
npm test 2743/2742/0 fail, typecheck чистый, furniture:check зелёный.
Issue: #584
User-Visible: yes
|
||
|
|
245b7f9847 |
ci: строгий режим свежести скриншотов включается на кандидате (#586)
Проверка #479 обязана быть строгой на кандидате беты и на релизном гейте. Фактически она не была строгой ни разу: preflight сравнивал со строкой `heavy=true` ВЕСЬ вывод `classify-changes.mjs --heavy`, а вывод двухстрочный — `heavy=…` и `mutants_requested=…`. В `$(…)` строки схлопываются через пробел, сравнение не совпадало никогда, режим оставался `warn`. Видно построчно в логе прогона 35091507839 на кандидате `4c44ef60`: скриншоты документации: режим warn (heavy=true WARN screenshot source fingerprint is stale; ... ok документация То есть проверка увидела устаревший индекс и пропустила кандидата. Обе беты после `699ab471` уехали с ним; на чистом checkout того же SHA `node scripts/check-docs.mjs --strict` падает с ERROR. Правило, которое из этого следует: формат `$GITHUB_OUTPUT` — построчный `ключ=значение`, читать его надо по ключу либо не читать вовсе. Где нужен один ответ, CLI отдаёт один ответ: `--screenshots-mode` печатает `warn` или `strict`, и в shell не остаётся ни разбора, ни развилки. Свидетели в `test/classify-changes.test.mjs`: режим по каждому событию, форма вызова в workflow (сравнение со строкой `heavy=true` не должно вернуться) и прямая проверка того, что вывод `--heavy` многострочный — то есть целиком сравнивать его нельзя. Мутант `screenshot-freshness-never-strict` возвращает прежнее «никогда не strict» и обязан краснеть. Issue: #586 User-Visible: no |
||
|
|
978035c491 |
ci: база стабильного кандидата — предыдущий стабильный тег (#587)
Относительная половина «Полных бенчмарков» сравнивала кандидата с прошлой вершиной `main`. Для стабильного релиза это давало круг, в котором гейт не может покраснеть дважды: прогон идёт только на push в `main`, кандидат обязан там оказаться, и следующий коммит той же линейки берёт базой первый — то есть линейку саму. На выпуске v1.76.0 это видно построчно: прогон 35097102695 на `c3d64789` честно показал resizePreview 603 → 981 и panZoom 91 → 205 в скрытой изометрии, а прогон на `9683a590` был зелёным и был бы зелёным без всякой правки бюджетов. Теперь база выбирается по намерению коммита: head несёт трейлер `Release:` без пре-релизного суффикса — сравниваем с предыдущим стабильным тегом. Бета, обычный push и ручной `comparison_ref` не меняются. Решение вынесено из shell в `scripts/performance-baseline.mjs` по тому же доводу, что и разбор вердикта ревью (#556): отрицательные случаи — тега нет, тег стоит на самой голове, база перестала быть предком, база старше HP-PERF-01 — в YAML не прогнать ни одним тестом. Обращения к git инжектируются, фикстуры описывают дерево. Отказы по-прежнему уводят в сторону БОЛЬШЕГО сравнения: непригодная база → родитель → последний достижимый релизный тег. Проверено исполнением на этом репозитории: стабильный кандидат v1.76.0 → `2c6410bb` (v1.75.0); бета v1.76.0-beta.5 и обычный push → `push before`; dispatch с `comparison_ref=v1.74.0` → `e63460f0`. Свидетели: `test/performance-baseline.test.mjs` (10 проверок, включая AC2 — второй коммит линейки не сравнивается сам с собой) и мутант `stable-candidate-compares-against-itself`, который возвращает прежнее поведение и обязан краснеть; проверено подменой руками — AC2 падает, оригинал проходит. AC4: других релизных гейтов, судящих о родителя, нет. `validate.yml` берёт `github.event.before` только для ДИАПАЗОНА файлов, и там база уже заменена доказанно зелёным предком (#387/#388), а не сырым родителем. npm test 2731/2730/0 fail, typecheck чистый, check-docs зелёный (кроме известного отпечатка скриншотов, #586). Issue: #587 User-Visible: no |
||
|
|
9683a59016 |
perf: принять шаг изометрии на жесте в бюджетах двух профилей (#585)
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (isometric) (push) Failing after 4m40s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (interaction) (push) Failing after 5m4s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (isometric-stage3) (push) Failing after 4m33s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (blend) (push) Failing after 5m6s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (space-default) (push) Failing after 3m55s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (overlay) (push) Failing after 4m2s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (plan-snap) (push) Failing after 4m1s
Проверка (CI) / Классификация изменённых файлов (push) Successful in 34s
Проверка (CI) / HACS: валидация репозитория (push) Skipped
Проверка (CI) / Hassfest: манифест интеграции (push) Skipped
Проверка (CI) / Фронтенд: типы, юниты, мутанты, синхрон бандла (push) Skipped
Проверка (CI) / Предполёт: документация, провенанс, процесс (push) Successful in 45s
Проверка (CI) / Переиспользование: это дерево уже проверено (push) Failing after 1m9s
Проверка (CI) / Смоки в браузере (шард 1 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 2 из 3) (push) Skipped
Проверка (CI) / Смоки в браузере (шард 3 из 3) (push) Skipped
Проверка (CI) / Смоки: все шарды зелёные (push) Skipped
Проверка (CI) / Golden-кадры против принятых эталонов (push) Skipped
Проверка (CI) / Перф-смок: бюджет времени кадра (push) Skipped
Проверка (CI) / Геометрия: TS/Python parity исполнена (push) Skipped
Проверка (CI) / Бэкенд: pytest в Home Assistant (push) Skipped
Проверка (CI) / Мутанты по диффу (1/6): затронутые свидетели краснеют (push) Failing after 1m47s
Проверка (CI) / Мутанты по диффу (2/6): затронутые свидетели краснеют (push) Failing after 2m21s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (large-house) (push) Failing after 12m40s
Проверка (CI) / Мутанты по диффу (4/6): затронутые свидетели краснеют (push) Successful in 2m51s
Проверка (CI) / Мутанты по диффу (5/6): затронутые свидетели краснеют (push) Failing after 1m33s
Проверка (CI) / Мутанты по диффу (3/6): затронутые свидетели краснеют (push) Successful in 4m31s
Проверка (CI) / Мутанты по диффу (6/6): затронутые свидетели краснеют (push) Successful in 3m28s
Проверка (CI) / Доказательство выполненных проверок (push) Failing after 26s
Полные бенчмарки производительности / Бенчмарки рендера и геометрии (space-glow) (push) Failing after 29m2s
Полные бенчмарки кандидата v1.76.0 (прогон 35097102695, SHA
|
||
|
|
85a5c7387e |
test: repair isometric golden host-face contract (#583)
Issue: #583 User-Visible: no |
||
|
|
5cae93501c |
fix: refine hidden 2.5D presentation (#583)
Issue: #583 User-Visible: no |
||
|
|
54dc836dee |
fix: закрыть compositor-пути day-cycle (#582)
Issue: #582 User-Visible: no |
||
|
|
2a8b5f1dcf |
fix: стабилизировать day-cycle при pinch в HA Companion (#582)
После первого движения камеры переносит фильтр контура на ограниченный размером viewport слой, сохраняя исходный статичный рендер без изменений. Issue: #582 User-Visible: no |
||
|
|
a68e4c6b80 |
fix: ограничить дневной контур размером экрана (#582)
Issue: #582 User-Visible: yes |
||
|
|
2bec03e6fa |
fix: «Как у пространства» забывает свой цвет комнаты (#581)
Режим заливки комнаты и её цвет — два поля конфига под одним переключателем. «Как у пространства» снимало только режим; цвет оставался и продолжал применяться, потому что `roomCustomFillOf` отдавал цвет комнаты независимо от того, чей режим `custom` действует. Так возникало безымянное состояние «режим наследую, цвет свой» — Cabinet на даче. Теперь цвет комнаты участвует в раскраске только вместе с её собственным `fill_mode: 'custom'` (одна функция — все поверхности: карточка, space-card, PDF, черновик диалога). Диалог загружает цвет в черновик только при своём режиме, обнуляет его при уходе с «Свой цвет» и показывает строку цвета только под этим радио; сохранение пишет `custom_fill` только с `fill_mode: 'custom'`, иначе удаляет — включая сироту от прежнего редактора. Чтение конфиг не переписывает: застрявшие комнаты выздоравливают обновлением. - `test/logic.test.mjs`: AC1 — сирота и любой чужой режим → цвет пространства - `demo/smoke_room_settings.mjs` шаг 7: свой цвет → «Как у пространства» → ни режима, ни цвета, во View цвет пространства; сирота открывается как наследование, сохранение её удаляет (проверено красным на базе: 9 фактов) - `demo/smoke_space_settings.mjs`: override с собственным режимом + сирота - `demo/golden/harness.mjs`: `roomCustomFill` ставит комнате её режим — кадры `lighting-custom-glow-*` не меняются - мутант `room-orphan-colour-wins-again` - docs: ARCHITECTURE (#56), CONFIG-COMPATIBILITY, USER-GUIDE ru/en, TESTING; отпечаток скриншотов принят попиксельно (11 кадров) Issue: #581 User-Visible: yes |