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
This commit is contained in:
Claude
2026-09-15 09:48:37 +03:00
parent bae6afaae7
commit 2bec03e6fa
58 changed files with 616 additions and 447 deletions
+10 -4
View File
@@ -1654,10 +1654,16 @@ its configured space.
stay independent and continue to render. The static room card uses the same
data/base projection, omits empty base groups, and renders the same live pools
only when its default-off `light_pools` option is enabled.
- **Custom room fill** (#56): `space.settings.custom_fill` is the space color
and `room.settings.custom_fill` is an optional explicit override. The pure
projection is room → space → `{c:'#607d8b',a:.18}` and every read crosses
`safeStoredColor` plus finite alpha clamping. `resolveEffectiveRoomFill`
- **Custom room fill** (#56, #581): `space.settings.custom_fill` is the space
color and `room.settings.custom_fill` is an optional explicit override that
counts **only together with the room's own `fill_mode: 'custom'`**. The pure
projection is room (own mode) → space → `{c:'#607d8b',a:.18}` and every read
crosses `safeStoredColor` plus finite alpha clamping. A room colour stored
without the own mode — the state "as the space" used to leave behind — is an
orphan: `roomCustomFillOf` paints the space colour, never rewrites the config,
and the next save of that room drops the field. The room dialog keeps the
colour draft only under its own "custom" radio and clears it when the radio
leaves that mode, so the dialog can no longer create the orphan. `resolveEffectiveRoomFill`
remains the single source for room floor, clean-floor holes and thick-wall
tunnel colors; stored `room_color` continues to control borders/names only.
- **Glow pools and additive composition** (#19, #71): every source retains its
+7
View File
@@ -7,6 +7,13 @@
opening reaches the room, instead of shining through solid wall
([#580](https://github.com/Matysh/houseplan-card/issues/580)).
- A room switched back to “As the space” no longer keeps its own colour: that
choice now forgets the room colour, and the colour row in room settings is
shown only under “Custom color”. Rooms already stuck with a foreign colour
(a room colour stored without the room's own mode) are painted in the space
colour after the update, with nothing to do
([#581](https://github.com/Matysh/houseplan-card/issues/581)).
## v1.76.0-beta.4 — 2026-09-15
- General settings now let window sunlight start at either the inner window
+7
View File
@@ -13,6 +13,13 @@
оконный проём часть луча, а не свет сквозь сплошную стену
([#580](https://github.com/Matysh/houseplan-card/issues/580)).
- Комната, которой вернули «Как у пространства», больше не остаётся своего
цвета: этот выбор теперь забывает цвет комнаты, а строка цвета в настройках
комнаты показывается только при «Свой цвет». Комнаты, уже застрявшие с чужим
цветом (свой цвет без своего режима), после обновления красятся цветом
пространства без каких-либо действий
([#581](https://github.com/Matysh/houseplan-card/issues/581)).
## v1.76.0-beta.4 — 2026-09-15
- В общих настройках теперь можно выбрать, от каких углов окна начинаются
+11 -1
View File
@@ -590,7 +590,17 @@ Current `fill_mode` additionally accepts `custom`. Its optional color is stored
as `{c:'#RRGGBB',a:0..1}` in `space.settings.custom_fill` and, for an explicit
room override, `room.settings.custom_fill`. Missing or invalid historical data
is projected at render time through room → space → `#607d8b`/`0.18`; merely
reading it never rewrites the config. Backend writes keep the strict shared
reading it never rewrites the config.
Since #581 the room override is read only together with the room's **own**
`fill_mode: 'custom'`. A `room.settings.custom_fill` stored without that mode —
older editors wrote it when a room was switched back to "as the space" — is
projected as the space colour on every surface (card, space-card, PDF) without
being rewritten; the next save of that room through its dialog removes the
field. New saves never write `custom_fill` without `fill_mode: 'custom'`. The
field shapes and the backend schema are unchanged, so older clients read
configs saved by newer ones as "a room without its own colour" — the same
picture the newer client shows. Backend writes keep the strict shared
hex/finite-alpha contract. An explicit `null` is accepted at either level and
has the same projection semantics as a missing override.
+9
View File
@@ -1388,6 +1388,15 @@ separately promised workflows:
temp cold/comfy/hot, LQI weak/strong), each with its own opacity slider [manual];
Reset restores defaults; saving defaults stores nothing [manual]
- [ ] Custom fill colors apply to the full card AND the static space-card
- [ ] **Room colour follows the room's own mode (#581):** a room colour counts
only with the room's own «Свой цвет»; «Как у пространства» clears the
colour draft at once, the saved room keeps neither `fill_mode` nor
`custom_fill`, and the plan paints the space colour. A colour stored
without the mode (an orphan from an older editor) paints the space colour,
opens as «Как у пространства» without a colour row and is dropped by a
plain save; `name_scale`/`label_scale` survive [unit: `logic.test.mjs`
`#581 AC1`; auto: `smoke_room_settings` step 7, `smoke_space_settings`
`customRoomOrphanInheritsSpace`; mutation: `room-orphan-colour-wins-again`]
- [ ] LQI gradient interpolates between the configured weak/strong colors [manual]
- [ ] Per-space "Show zigbee signal (LQI)" toggle hides/shows the badges next to
devices and the signal line in room tooltips for that space only [manual]
+4 -1
View File
@@ -894,7 +894,10 @@ non-interactive.
## 13. Room fills and light
Space fill modes include user colour, temperature comfort range and LQI. Room
settings may override the space. Glow is independent from the base fill.
settings may override the space. A room has its own colour only while its fill
is set to its own **Custom color**; choosing **As the space** forgets that
colour and the room is painted like the rest of the space. Glow is independent
from the base fill.
When a room effectively uses the temperature fill, its settings show optional
lower and upper comfort bounds. Each blank field independently inherits the
+1 -1
View File
@@ -1421,7 +1421,7 @@ Power=`off`/`unavailable` подавляет даже устаревший ак
|---|---|
| Общие настройки | Цвет и прозрачность состояний данных, настройки Glow вместе с его радиусом, цвет стен |
| Пространство | Режим заливки, свой цвет и прозрачность, независимый переключатель Glow, температурные границы, показ LQI, фон |
| Комната | Наследовать или явно выбрать Нет/LQI/Свет/Температуру/свой цвет; задать собственные нижнюю и верхнюю границы температуры; свой цвет и каждая пустая температурная граница наследуются от пространства; пока наследует Glow пространства |
| Комната | Наследовать или явно выбрать Нет/LQI/Свет/Температуру/свой цвет; задать собственные нижнюю и верхнюю границы температуры; собственный цвет есть только в режиме «Свой цвет» комнаты — «Как у пространства» забывает его, и комната красится цветом пространства; каждая пустая температурная граница наследуется от пространства; пока наследует Glow пространства |
| Устройство | Выбрать Авто/Всегда/Никогда для собственной световой роли, цвет и яркость Glow, радиус свечения |
При температурной заливке в настройках комнаты появляется диапазон комфорта.
+12 -12
View File
@@ -3,7 +3,7 @@
"fixture": "synthetic-only",
"chromium": "151.0.7922.34",
"oxipng": "oxipng 10.2.0",
"sourceFingerprint": "1d093022e8e71a794c3996bb5ad4e23d473cdf79b6d9fa3e188661603ef1170a",
"sourceFingerprint": "46939ba13473911af6bb51df07e30c93e3f1af9908678c530c49608fca443207",
"captureScriptSha256": "9e4b0ae533407fe003a129078aec020f2c83d67cdb6fc6da696ce3d829beaeb5",
"command": "npm run build && node demo/docs/capture.mjs",
"scenarios": {
@@ -15,7 +15,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "1d093022e8e71a794c3996bb5ad4e23d473cdf79b6d9fa3e188661603ef1170a",
"sourceSha256": "46939ba13473911af6bb51df07e30c93e3f1af9908678c530c49608fca443207",
"imageSha256": "76c35a87810e3dc6890bda7b43da133f57d8860031204a62cda7115c15950090"
},
"view-touch": {
@@ -26,7 +26,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "1d093022e8e71a794c3996bb5ad4e23d473cdf79b6d9fa3e188661603ef1170a",
"sourceSha256": "46939ba13473911af6bb51df07e30c93e3f1af9908678c530c49608fca443207",
"imageSha256": "795585acb09488236ebb8028a7fcbd918404c5fe01560bb9b4559de13d441625"
},
"space-create": {
@@ -37,7 +37,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "1d093022e8e71a794c3996bb5ad4e23d473cdf79b6d9fa3e188661603ef1170a",
"sourceSha256": "46939ba13473911af6bb51df07e30c93e3f1af9908678c530c49608fca443207",
"imageSha256": "9f0970de09772674c3156b9fc5b2d49de0ceb22be11ecf8b136047d37d100a84"
},
"room-contour-close": {
@@ -48,7 +48,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "1d093022e8e71a794c3996bb5ad4e23d473cdf79b6d9fa3e188661603ef1170a",
"sourceSha256": "46939ba13473911af6bb51df07e30c93e3f1af9908678c530c49608fca443207",
"imageSha256": "0c354a79f9c3660788b6c5e889a27b6bd78b1f209dc0eef6bb43e2c1344028c4"
},
"plan-context-tray": {
@@ -59,7 +59,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "1d093022e8e71a794c3996bb5ad4e23d473cdf79b6d9fa3e188661603ef1170a",
"sourceSha256": "46939ba13473911af6bb51df07e30c93e3f1af9908678c530c49608fca443207",
"imageSha256": "64169c543291bc4df806dcf8ff484b42c8ea55eb343323d945344e0e28f2ce97"
},
"device-editor": {
@@ -70,7 +70,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "1d093022e8e71a794c3996bb5ad4e23d473cdf79b6d9fa3e188661603ef1170a",
"sourceSha256": "46939ba13473911af6bb51df07e30c93e3f1af9908678c530c49608fca443207",
"imageSha256": "3c8703175605fcf0387d654cd30783ecfd33e21743e70b45190a5ae1a8d355c2"
},
"device-display-preview": {
@@ -81,7 +81,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "1d093022e8e71a794c3996bb5ad4e23d473cdf79b6d9fa3e188661603ef1170a",
"sourceSha256": "46939ba13473911af6bb51df07e30c93e3f1af9908678c530c49608fca443207",
"imageSha256": "5d6193202102f5e6d85567f1111fa105895b933979fbf369c6ba0b1029deb823"
},
"background-editor": {
@@ -92,7 +92,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "1d093022e8e71a794c3996bb5ad4e23d473cdf79b6d9fa3e188661603ef1170a",
"sourceSha256": "46939ba13473911af6bb51df07e30c93e3f1af9908678c530c49608fca443207",
"imageSha256": "375900a29aa62632883ede6727b7dbb8b48cabe9d7d0e16425f9779c837f3dd9"
},
"room-card": {
@@ -103,7 +103,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "1d093022e8e71a794c3996bb5ad4e23d473cdf79b6d9fa3e188661603ef1170a",
"sourceSha256": "46939ba13473911af6bb51df07e30c93e3f1af9908678c530c49608fca443207",
"imageSha256": "3d85424676c823ddca3fa7dab3cce089b6030e48c05d16d2d48d922c66c0cbd2"
},
"device-info": {
@@ -114,7 +114,7 @@
},
"theme": "dark",
"language": "en",
"sourceSha256": "1d093022e8e71a794c3996bb5ad4e23d473cdf79b6d9fa3e188661603ef1170a",
"sourceSha256": "46939ba13473911af6bb51df07e30c93e3f1af9908678c530c49608fca443207",
"imageSha256": "ac962820f8e1c5cb5b042d39b18dc95905fa840cb2d4749060d991be4f6977c2"
},
"pdf-export": {
@@ -125,7 +125,7 @@
},
"theme": "light",
"language": "en",
"sourceSha256": "1d093022e8e71a794c3996bb5ad4e23d473cdf79b6d9fa3e188661603ef1170a",
"sourceSha256": "46939ba13473911af6bb51df07e30c93e3f1af9908678c530c49608fca443207",
"imageSha256": "4537692fd57a3ed95528eddd748eee5cb6e456ffab7cc49169853d62459252cd"
}
},