Publish the three integrated S8 issues since beta.2. Synchronize all seven version sources, rebuild committed frontend assets, tighten ratchets to measured facts, and update release metadata for LED strips. Canonical documentation screenshots were reviewed from Linux CI and accepted in the preceding derived commit.
Local checks: build/typecheck, bundle policy, bundle budgets, monolith checks and release-contract tests passed. Native-Windows unit run passed 3517 tests; two GNU-bash tests and one TypeScript diagnostic-shape test remain Linux-only. Full exact-SHA Validate is mandatory before publication.
Release: v1.79.0-beta.3
Issue: #765
Issue: #780
Issue: #781
User-Visible: yes
r1 H1: a previous frontend sends every space without the unknown field and
the ordinary write path replaced the document with it, erasing every LED
strip. The shared ordinary-writer helper now copies the stored shapes of a
space the payload omits (config/set and Optimize); an explicit list from a
new client still deletes, a removed space takes its strips, and a link to a
marker the same write deleted unbinds by the usual rule. config/set reports
the normalisation counters after the preservation.
Issue: #780
User-Visible: no
Stage 1 of #780 — the data model. A space carries an optional
`led_strips: [{id, points, marker, active?}]` (custom_components/houseplan/
led_strips.py, pure, strict mypy).
- Type schema inside SPACE_SCHEMA: 2–50 finite numeric points (no strings,
booleans, NaN or off-canvas values), ≤50 strips per space including hidden
shapes, strict boolean `active`, marker = non-empty string or null.
- A config-level step after coordinate canonicalisation judges the shape
(two distinct points, non-zero length, a closed strip needs three distinct
vertices, a hidden shape needs a marker, unique ids per space) and the links
in the order the spec fixes: duplicates are rejected before any
normalisation (two links to the same missing id still conflict); a link to
a marker that is not live becomes an unbound strip (marker null, active
true, id and points kept), so a client that does not know strips can delete
a bound marker without its save failing; a live marker with an empty space
adopts the strip's space; a non-empty foreign space rejects the write.
- config/set answers with `led_strips: {unbound, space_adopted}` when the
write was normalised, so a new client re-reads; old clients ignore it.
- Space import remaps links through the marker id map; a skipped or
virtualised duplicate leaves the strip unbound; a coinciding old id never
binds. Plan-only export keeps geometry and nulls every link. Import details
report `unbound_led_strips`, computed by the server, never read from the file.
- Coordinates get JSON-noise cleanup only, like stairs (face contacts are
off-lattice), in both canonicalisers with a shared fixture case.
- Support package: counters only (total/unbound/hidden), no coordinates or ids.
Tests: tests_backend/test_led_strips.py (41, pure), test_ha_import_export
(6 cases: full round trip with a hidden shape, orphan count, remap against a
coinciding id, skip and virtual duplicates, plan-only), test_ha_websocket
(old client deletes a bound marker → save stands, counters, foreign space
rejects without a new revision). Full backend with the HA harness: 969 passed.
Mutating the shape check, the duplicate check, the orphan normalisation or the
space adoption each turns the pure suite red.
Issue: #780
User-Visible: no
At dawn, dusk and night the environment shows the moon in the top-left
corner of the scene, behind the plan: computed in the card from the home
coordinates and the browser clock (short Meeus series + topocentric
parallax, within 1.4° / 1.8 pp of JPL Horizons), one designer image under
a continuous phase mask with the lit side always on the left (owner
2026-09-29), a feathered terminator, 3°/3 % thresholds and the 2 s fade of
the window rays. Everything but a small gate lives in the lazy
moon-runtime chunk, with its own 30 s ticker. General settings: "Sun"
becomes "Sun and Moon" with one switch, on for new installations
(DEFAULT_CONFIG), off for existing ones.
The initial View graph sat 728 B under its budget: the gate is paid for by
moving fifteen dialog-only strings of General settings into the lazy
settings dictionary (#459) and by one build fingerprint literal instead of
three, so the graph ends 4 B above dev. Golden: two new moon scenes, and
the two General settings help frames show «Sun and Moon»; the WSL artifact
test fixture now models scenes whose first capture awaits acceptance.
Issue: #661
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
Волна 2 эпика #674 — у каждого правила один дом, остальные места ссылаются.
DECOR-EDITOR.md ← BACKDROP.md + LIVE-TEXT.md: один документ с нумерованными
разделами (§3 подложка, §5 текст с живыми значениями), на которые теперь
указывают комментарии кода вместо несуществовавших «BACKDROP §2/§3»;
исправлено утверждение, что space-card не рисует декор (он рисует подложку
и картинки декора, но не фигуры, мебель и текст). LIGHT.md ← матрица
настроек света (перевод, тест назван явно: test/devices.test.mjs «issues
84/88»). DEVICE-PRESENTATION.md ← правила «что показывает маркер» из
FILTERING.md (порядок cover → light sources → device role, шторы,
медиаплееры); «в одном pull request» → «в одном коммите». CANVAS.md: §9.5
«Оптимизировать планы» → CONFIG-COMPATIBILITY.md, overlay и планарные грани
Walls → WALL-THICKNESS.md §10–11, таблицы «было/стало» сняты. TESTING-DEMO.md
→ demo/stand/README.md: карта демо-дома и «чего на стенде нет», ручной
чек-лист снят (ручной фазы в процессе нет). ISOMETRIC.md — только текущее;
история Stage 2/4 — docs/adr/570-isometric-stage4-visual-handoff.md.
SUN.md: удалённый контракт фона снят, правило бумаги — в текущем разделе.
UX-MODES.md: декор над заливками, а не «под комнатами»; «hidden isometric»;
follow-up из #3 — все выпущены. Шапки VACUUM, WARM-REMOUNT («Выровнять всё
по сетке» → «Оптимизировать планы»), WALL-THICKNESS, STYLING-HOOKS,
CONFIG-COMPATIBILITY (#33), PDF-EXPORT — без устаревших статусов и планов.
README EN/RU: абзац про пересъёмку скриншотов → CONTRIBUTING.md, RADAR и
PDF-EXPORT в списке документации, RU догнал EN (2.5D, повторное
использование загруженного изображения, STAIRS). Один список канонических
документов подсистем в AGENTS.md и промпте ревьюера (_process.yml).
WALL-THICKNESS.md ссылается на ADR 282.
Правки src/** и validation.py — только пути документов в комментариях.
Issue: #679
User-Visible: no
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
Волна 1 эпика #674 — мёртвое без риска, каждое имя проверено git grep по
текущему dev.
Удалено: шесть demo/shot_*.mjs без читателей (shot_furniture ещё и не
работает с #159), demo/capture_wall_strip_backup.mjs,
demo/benchmark_coordinate_write_barrier.mjs (нигде не запускался, но входил
в манифест smoke через demo/benchmark_*.mjs — теперь лист покрытия чист),
scripts/dev/styles-split.mjs (падает на текущем src/styles.ts),
docs/README.ru.md (индекс четырёх документов из 38 — роль у README.ru.md),
demo/README.md (одна строка в карте пакета AGENTS.md вместо него) и из
legacy/ — снимок аудита v1.58.0, черновик 089, завершённые планы,
продуктовые снимки и две разовые диагностики; история git хранит.
legacy/docs/SUN-CONTRAST.md остаётся: на него ссылаются src/sun.ts и SUN.md.
Перенесено в legacy/docs/: superpowers/specs (11 дизайн-документов до
процесса), QUALITY-560.md, ROADMAP.md, STATUS-FEATURES.md. Ссылки
поправлены там, где они были: FILTERING.md, ТЗ 006/007/058, testing-notes,
комментарии src/open-spans.ts и validation.py (только путь документа),
SCOPE.md, STATUS.md, smoke_household_journeys.mjs, опись legacy/README.md;
ложный маркер benchmark_coordinate_write_barrier снят с чек-листа.
Issue: #678
User-Visible: no
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
Прямые и винтовые лестницы получили отдельную модель, инструменты редактора, безопасную межэтажную навигацию, вычитание из чистой площади и плоское отображение в 2.5D.
Issue: #663
User-Visible: yes
Render only the core and value-badge geometry needed by the common floor-shadow layer. Avoid mounting hidden icons, activity indicators and satellites for every 2.5D marker.
Issue: #649
User-Visible: no
The beta performance gate exposed a duplicate 2.5D scene resolution in one
Lit update: willUpdate built the projection snapshot and immediately cleared
it before render. Invalidate first so the computed snapshot is reused.
Issue: #649
User-Visible: no
CODE-REVIEW-649-r1 M1: the 2.5D beam's cut by physical bodies (AC7) had no
witness. Unit: a column in the beam leaves the floor between window and
column lit and shades it behind the column; smoke: a Solid partition across
the south beam removes floor from the rendered wash. Mutants: occluders
extruded toward the sun, occluders ignored, card drops occluders.
Lows: L1 stale alpha wording (DEVELOPMENT, ARCHITECTURE, card comment,
benchmark); L3 dark-theme state edges computed per theme; L4 Alert beats a
plain Hover and a plain Selected; L5 a virtual marker has no dashed ring;
L6 tiles and shadows stay without walls (show_borders off).
Issue: #649
User-Visible: no
- settings.volumetric_view (General settings › Display, third switch) replaces
the alpha entry, the header projection toggle and the phone-menu item; one
rule for card, sidebar page and kiosk; editors stay Flat; backend accepts
only a boolean, support package copies it.
- Raised tiles in 2.5D View: no ring, rounded body min(0.275 D, 0.3 h), edge
0.1 D with the lab filters, ×1.12 size also in layout/collision, lift
0.075 D, one floor-shadow layer under all markers (theme × floor table),
frames hugging tile and edge (Alert > Focus > Selected > Hover), forced
colours without edge and shadow (src/iso-tiles.ts, styles/iso-tiles).
- Soft sun wash instead of projected Flat wedges (src/iso-sun.ts): same gates
and windows as sun_rays, length from elevation, parallelogram along the sun,
tone and streaks by floor lightness, sill line.
- Walls take the user's wall colour (top opaque, sides × .77/.68/.60); theme
rules for walls/openings/labels removed; furniture uses the Flat stroke rule.
- Smokes smoke_iso_tiles/iso_sun/iso_theme_walls/volumetric_setting, 16 new
mutants, acceptance scenes STAGE6_ACCEPTANCE_SCENARIOS (golden with their
Linux CI baselines), ACCEPTANCE.md side by side.
Issue: #649
User-Visible: yes
На окне не шире 480 px шапка карточки и страницы бокового меню занимает одну
строку ≤ 56 px: вкладки пространств (без шестерёнок и «+», прокрутка вбок,
текущая вкладка докручивается в видимую область), масштаб, одна шестерёнка
«Действия и настройки» и у админа слот крестика #647. Заголовок скрыт.
Меню шестерёнки (src/header-menu.ts): редакторы, настройки и добавление
пространства, общие настройки, PDF, помощь, сводная панель и объёмный вид
(alpha) — в фиксированном порядке и с теми же условиями и действиями, что у
прежних кнопок. Пункт закрывает меню; Escape возвращает фокус на шестерёнку;
касание вне меню поглощается прозрачной подложкой. На > 480 px шапка прежняя,
в киоске меню нет.
- три кнопки шапки вынесены в renderHeaderActions — шапка карточки не растёт;
- сводная панель отдаёт свои пункты методом menuItems();
- i18n: title.header_menu (en/ru/de/fr);
- потолок initial View 291 000 → 292 600 (замер 291 635), база bundleBytes;
- смок smoke_mobile_view_header, юнит header-menu, 10 мутантов; смоки
support_feedback, gear_tabs, modes, toolbar_stable_width переведены на
меню на ≤ 480 px; подпись вкладки в span.tabtitle без pointer-events;
- USER-GUIDE.ru, UX-MODES, TOUCH-SUPPORT, STYLING-HOOKS, data-hp-contract,
оба changelog.
Issue: #616
User-Visible: yes
Счётчик «N устр.» и ключ count.devices удалены вместе с правилом
`.head .count` (скрывало счётчик на ≤1100 px). Крестик закрытия редактора
вынесен из активной кнопки режима в отдельный слот `.editor-close-slot`
сразу после группы кнопок режимов: 24 × 24 px (--hp-editor-close-size),
одинаковый в редакторе и вне его; вне редактора слот пуст, aria-hidden,
без фокуса и pointer-events. Кликабельная зона × — весь слот (≥24×24, #195),
глиф 13 px; × виден на любой ширине, где видны кнопки режимов.
Смок smoke_toolbar_stable_width (1400/1000/768/390 px, en/ru): ширина
шапки и отступы кнопок стабильны во всех переходах, × виден, попадание
в край зоны закрывает редактор, нет нового overflow. smoke_editor_tabs,
smoke_edge_cases, smoke_hidden_flag переведены на слот/список устройств.
6 мутантов. docs: UX-MODES.md, STYLING-HOOKS.md, data-hp-contract.json.
Issue: #647
User-Visible: yes
Транзитная геометрия Resize теперь только вычисляет центр для текущего кадра и не удаляет сессионную позицию. Окончательная геометрия по-прежнему очищает точку, если она действительно вышла за комнату.
Issue: #645
User-Visible: yes
Кнопка сохраняет положение в пределах комнаты только на время сессии редактора,
корректно отменяется при pinch/cancel и учитывается подписями Resize.
Issue: #645
User-Visible: yes
- Ветка переиграна поверх dev с #642 (диалог «Оптимизировать планы» —
свой модуль), #627 (ленивые словари), #617 (загрузка плана по HTTP),
#618 (пакетное скрытие в каталоге), #615 (шахматка альфы в плитках цвета).
Текстовые конфликты — в сгенерированном (бандл) и в
demo/smoke_grid_snap.mjs (#642 сменил адрес вызова на
_editorRuntime.optimizePlans.open/run, #629 — ввод через фасад; оставлены
оба); scripts/mutation-registry.mjs слит без конфликта;
docs/reviews/INDEX.md пересобран reviews-index.mjs.
- После ребейза поверх #615 в дереве остался осиротевший чанк
namespace-language-ani9_pFw.js (переименование с обеих сторон, разрешено
версией dev без удаления нашей) — каталоги ассетов собраны с нуля,
bundle-tree чист.
- #642 опустил потолок src/houseplan-card.ts до факта; хук mode-tab
перенесён на строку data-editor-navigation (коммит хука), ядро не растёт.
- Бандл пересобран, три копии совпадают; monolith-baseline — числа равны
базе dev.
Issue: #629
User-Visible: no
General settings colour tiles (room fills and glow) paint their colour at
its opacity over the checkerboard again, so "No light sources" at 0 % no
longer looks like solid grey. The label ink is chosen from the visible
colour (hex mixed with the checkerboard by alpha). Colour plates
(flat-swatch without cover-swatch) stay solid — now witnessed by the
- hp-color-opacity: one `_solidSwatch` condition drives background and
opacity; cover-swatch keeps the checkerboard
- editors/color-tile-ink.ts: pure isLightHex/colorTileInk, unit-tested
- smoke_dialog_polish_605: tileAlpha, defaultsDiffer, noneInkDark,
liveAlpha, platesFlat, plateSolid; oneSurface drops the opaque check
- mutants M-615-tile, M-615-plate, M-615-label
- USER-GUIDE (ru/en): "Colour tile" row; both changelogs
Issue: #615
User-Visible: yes
Код-ревью r1 зелёное; слияние отказало на конфликте с dev. Ветка
переиграна на origin/dev 2b3959e5 (32 коммита).
- Конфликты: бандл — версия dev + пересборка; оба changelog — запись
#618 под записью #617 в том же разделе; bundle-budget.mjs и
monolith-baseline.json — версия dev, числа #618 измерены заново;
docs/reviews/INDEX.md — node scripts/reviews-index.mjs.
- Ключи device_inbox.* остаются в основных словарях: #627 вынес в
ленивые чанки только settings/support/topology, каталог устройств
туда не входит.
- bundle-budget: LAZY_EDITOR_GZIP_CEILING 229 600 → 231 200 (замер
230 208, база dev 228 997, +1 211 Б gzip); initial View 289 558 →
289 884 (+326 Б, en-строки), в пределах потолка 290 400.
- monolith-baseline: hostRefs 4872 → 4883 (+11, как в r1),
bundleBytes 2509909 → 2515825.
- src/houseplan-card.ts: снято type-зеркало `selected?` в
DeviceInboxDialogState карточки (2 строки). После #627 запаса под
потолком ядра (12889) не осталось; карточка поле не читает, пишет и
читает его только рантайм через свой тип — так же, как черновики
фильтров #44. Типы стираются, поведение не меняется.
Issue: #618
User-Visible: no
- Ветка переиграна поверх dev с #642 (вынос диалога «Оптимизировать
планы») и #627 (ленивые словари settings/support/topology). Текстовые
конфликты — только в сгенерированном (бандл, база монолита) и в
docs/reviews/INDEX.md (пересобран reviews-index.mjs); src/**, i18n,
реестр мутантов, smoke-links слились без конфликтов.
- Бандл пересобран, три копии совпадают, bundle-tree: 30 ассетов.
- monolith-baseline: hostRefs 4 869 → 4 872 (+3 — те же три обращения к
хосту, что и до ребейза: 4 948 → 4 951, приняты на ревью r1),
bundleBytes 2 508 211 → 2 509 909 (+1 698, в полосе ±2 000); прочие
числа равны базе dev.
- bundle-budget: потолки не менялись, все замеры в полосе.
Issue: #617
User-Visible: no
План уходил base64 в WebSocket-кадре: файл больше ~3 МиБ давал кадр больше
4 МиБ, HA закрывал сокет до обработчика, и обещанные 8 МБ были недостижимы.
- бэкенд: HouseplanPlanUploadView (POST /api/houseplan/plans/upload), потоковый
предел MAX_PLAN_BYTES (read_bounded), общий writer store_plan_upload для view
и ws_plan_set (контракт WS без изменений);
- карточка: stagePlanFile/uploadPlanFile/renderPlanBackdropGuard в
backdrop-pick.ts для обоих рантаймов; PlanFilePayload хранит Blob вместо b64;
SVG больше предела — тост при выборе, растр — диалог #39 только с уменьшенной
копией, копия больше предела не попадает в staging, 413 называет предел;
- i18n toast.plan_too_large, backdrop.over_limit_body (en/ru/de/fr);
USER-GUIDE ru/en, CHANGELOG ru/en, docs/testing-notes (#617);
- тесты: test/plan-upload-limit.test.mjs, tests_backend/test_plan_upload.py,
test_ha_upload.py (#617), smoke_plan_upload_limit.mjs; три смока переведены
с b64/plan/set на blob/fetchWithAuth; мутанты plan-upload-*;
- база монолита: hostRefs +3 (общий хелпер плана вместо двух копий в рантаймах,
новый тост уменьшенной копии).
Issue: #617
User-Visible: yes
- Бандл пересобран поверх dev с #642 (вынос диалога «Оптимизировать
планы»), три копии совпадают.
- bundle-budget: потолок ленивого графа редактора 246 000 (#642) → 229 600,
замер 228 621 (до ребейза 228 900 при 227 869; +752 — модуль диалога #642).
Центр полосы: 979 Б сверху, 1 021 Б до нижней границы. Потолок онбординга
28 500 без изменений (замер 27 468).
- monolith-baseline bundleBytes 2 499 182 → 2 508 211 (+9 029: десять новых
чанков словарей и записи манифеста, та же причина, что и до ребейза);
прочие числа равны базе dev.
- smoke_optimize_geometry_preflight: ожидание языкового гейта #627 перед
открытием диалога через новый порт #642 (`_editorRuntime.optimizePlans.open()`),
разрешено в коммите задачи.
Issue: #627
User-Visible: no
Первый вынос из монолита по образцу live-*/RadarSetupController.
src/optimize-plans-dialog.ts: OptimizePlansDialogState, чистые
preflightDiagnostics/preflightVersionsDiffer и класс OptimizePlansDialog
(open/preview/toggleLivePositions/run/copyDiagnostics/
reportPreflightFailure/render, clipboardFallback) за портом из 18 членов.
Модуль не знает HouseplanEditorHostPort; импортирует его только рантайм,
карточка берёт тип.
Инлайн-фолбэк буфера обмена — WeakMap по объекту диалога
(CODE-REVIEW-295-r1 M2), дедуп dev-лога — поле класса: из карточки и порта
ушли _preflightClipboardFallback и _reportedPreflightFingerprint, пять
делегатов и две стрелки-заглушки. Харнесс (13 смоков, wall-draw-click,
golden) зовёт card._editorRuntime.optimizePlans.* — меняется только адрес.
11 текстовых утверждений i18n.test.mjs о разметке и тосте диалога и гварды
8 мутантов переехали в юнит test/optimize-plans-dialog.test.mjs
(test-build); новый мутант optimize-dialog-imports-host-port.
monolith-baseline: delegates 159→154, portMembers 350→348,
hostRefs 4948→4869, portPrivates 96→94, harnessPrivates 107→101,
bundleBytes 2499647→2499182. Потолки ядер опущены на выигрыш;
потолок lazy editor gzip пересчитан (+555 Б gzip при −465 Б сырых).
Issue: #642
User-Visible: no
- .githooks/pre-push + scripts/pre-push-gate.mjs --hook: gate:small runs by
default for issue/* branches with an executable diff; C/D-only diffs and
non-issue refs are skipped, HP_PREPUSH_GATE=0 turns it off, =1 forces it.
A non-HEAD push with an executable diff is rejected (the gate checks the
working tree). The gate runs with every GIT_* variable removed: git gives
hooks GIT_DIR (and GIT_CONFIG_PARAMETERS with the pusher's -c), and unit
tests that `git init` temporary repositories otherwise write into the real
one — seen on the first live run. The manual #343 mode is unchanged.
- scripts/sandbox-bootstrap.sh: idempotent worktree / deps / chromium (npm
@sparticuz/chromium@152.0.0 + Playwright shims) / bundle / check steps,
no owner paths or credentials.
- scripts/test-chunk.mjs, npm run test:chunk -- N/M: round-robin over
test/*.test.mjs sorted by code point.
- Tests, seven mutants, docs/DEVELOPMENT.md «Локальный контур за 5 минут»,
docs/TESTING.md. package.json changed → bundle rebuilt (fingerprint input).
Issue: #633
User-Visible: no
Карточка и редакторский рантайм держали ≈380 неиспользуемых импортов, 56
мёртвых объявлений и дублей типов (warm-boot, LS_*, GLOW_*, debounce,
navigate, lruRead — копии карточки в рантайме) и 112 приватных членов
карточки, которых не читал никто — делегаты `_editorRuntimeOrThrow()._x()`,
оставшиеся от выноса #425, и аксессоры glow-состояния. Всё это снято; в 9
других файлах — по одиночной ошибке. Делегаты и поля, которых касаются
браузерные смоки (`card._x(...)`), оставлены и посчитаны отдельно.
Гейт `npm run lint:unused` (scripts/unused-locals-gate.mjs, в gate:small и
Validate после сборки): `tsc --noUnusedLocals` чист, кроме приватных членов
карточки из порта HouseplanEditorHostPort / `host.` (portPrivates) и членов,
которых зовёт харнесс (harnessPrivates); храповик по шести числам
scripts/monolith-metrics.mjs против scripts/monolith-baseline.json —
delegates 260→159, portMembers 350, hostRefs 4948, portPrivates 96,
harnessPrivates 107, bundleBytes 2 510 141→2 500 387. `npm run inventory`
печатает те же числа. Заморозка 54 тестов, читающих монолит как текст
(test/monolith-text-anchors.test.mjs); PROCESS.md §2.7 — правило.
Логический исходник для контрактных тестов (test/houseplan-source.mjs)
дописывает члены рантайма без делегата в карточке — контракт продукта не
зависит от наличия заглушки. Потолки ядер и initial gzip опущены на выигрыш
(292 000 → 290 400). Бандл пересобран, три копии синхронны.
Issue: #624
User-Visible: no