From 774388c2e573af3043ead8995358c2930a44c00f Mon Sep 17 00:00:00 2001 From: Codex Date: Sat, 29 Aug 2026 12:29:33 +0300 Subject: [PATCH] docs: name the expected_rev requirement for external writers (#368) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #340/#356 made expected_rev mandatory for config/set and layout/set over a non-empty store — an honest protection the release notes sold only as a stale-tab guard. A third-party script writing plans directly cannot infer from "protects from stale tabs" that it must now read the revision first. Both changelogs gain an explicit breaking-for-external-writers entry with the read-then-write recipe; ARCHITECTURE.md's WS contract section extends the #340 paragraph with the cycle external clients must follow (get rev → send expected_rev → on conflict re-read and retry); and both conflict messages now carry the actionable hint for scripts — "include expected_rev from houseplan/config/get / layout/get" — alongside the tab-oriented "reload" advice. The backend tests pin only the "revision is required" substring and stay untouched. Issue: #368 User-Visible: yes --- custom_components/houseplan/websocket_api.py | 10 ++++++---- docs/ARCHITECTURE.md | 7 ++++++- docs/CHANGELOG.md | 10 ++++++++++ docs/CHANGELOG.ru.md | 10 ++++++++++ 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/custom_components/houseplan/websocket_api.py b/custom_components/houseplan/websocket_api.py index d9d54859..0adb1010 100755 --- a/custom_components/houseplan/websocket_api.py +++ b/custom_components/houseplan/websocket_api.py @@ -594,8 +594,9 @@ async def ws_layout_set(hass: HomeAssistant, connection, msg: dict[str, Any]) -> ) connection.send_error( msg["id"], "conflict", - f"Layout revision is required; reload the layout " - f"(current rev {current_rev})", + f"Layout revision is required; reload the layout, or — for " + f"external clients — include expected_rev from " + f"houseplan/layout/get (current rev {current_rev})", ) return if "expected_rev" in msg and msg["expected_rev"] != current_rev: @@ -1322,8 +1323,9 @@ async def ws_config_set(hass: HomeAssistant, connection, msg: dict[str, Any]) -> ) connection.send_error( msg["id"], "conflict", - f"Configuration revision is required; reload the configuration " - f"(current rev {current_rev})", + f"Configuration revision is required; reload the configuration, " + f"or — for external clients — include expected_rev from " + f"houseplan/config/get (current rev {current_rev})", ) return if "expected_rev" in msg and msg["expected_rev"] != current_rev: diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index af9881bf..75888a84 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -891,7 +891,12 @@ The wire schema permits omission only for the first empty-store bootstrap at revision zero, so the endpoint can return the stable `conflict` domain error instead of a generic format error. A revision-less write over `rev > 0` is rejected under the same `write_lock` before validation, no-op detection, -backup cleanup, file collection or update events (#340). +backup cleanup, file collection or update events (#340). The same rule holds +for `layout/set` (#356). External writers (scripts, automations, custom +integrations) must therefore follow the read-then-write cycle the card uses: +call `houseplan/config/get` (or `layout/get`), keep the returned `rev`, and +send it back as `expected_rev`; a `conflict` answer means the document moved — +re-read and retry with the fresh revision (#368). The normal frontend reaches `houseplan/plan/optimize` only after the exact preview candidate passes `src/plan-geometry-preflight.ts`. That pure barrier diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1aab1b20..6c4afdba 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,16 @@ ## Unreleased +- Breaking for external writers (scripts, automations, custom integrations + that write plans directly): `houseplan/config/set` and `houseplan/layout/set` + over a non-empty store now require `expected_rev` and answer `conflict` + without it. Read the current `rev` from `houseplan/config/get` / + `houseplan/layout/get` first; the card itself has sent the revision since + v1.60 and is unaffected + ([#340](https://github.com/Matysh/houseplan-card/issues/340), + [#356](https://github.com/Matysh/houseplan-card/issues/356), + [#368](https://github.com/Matysh/houseplan-card/issues/368)). + - A gate or door bound to a position-reporting cover no longer stutters the whole card while it moves: the light cut through the opening now steps on a 5% grid, cutting the heavy geometry recomputes from about a hundred per diff --git a/docs/CHANGELOG.ru.md b/docs/CHANGELOG.ru.md index 6ad4ec4d..d3a4b6ae 100755 --- a/docs/CHANGELOG.ru.md +++ b/docs/CHANGELOG.ru.md @@ -8,6 +8,16 @@ ## Не выпущено +- Ломающее для внешних клиентов (скрипты, автоматизации, кастомные + интеграции, пишущие планы напрямую): `houseplan/config/set` и + `houseplan/layout/set` поверх непустого хранилища теперь требуют + `expected_rev` и без него отвечают `conflict`. Сначала прочитайте текущий + `rev` через `houseplan/config/get` / `houseplan/layout/get`; сама карточка + шлёт ревизию с v1.60 и не затронута + ([#340](https://github.com/Matysh/houseplan-card/issues/340), + [#356](https://github.com/Matysh/houseplan-card/issues/356), + [#368](https://github.com/Matysh/houseplan-card/issues/368)). + - Ворота или дверь на cover-сущности с позицией больше не дёргают карточку во время движения: световой вырез проёма ступает по сетке 5%, и тяжёлых пересчётов геометрии вместо ~сотни за цикл — не больше двадцати