(1) Corner and edge handles carry the same hit radius (1.8 % of the
view), so on furniture narrower than 4·hr — a 40 cm cabinet — the two
circles overlap and whichever is painted last takes the tap. Edges were
painted last. Corners are now, because a side handle scales one axis
while a corner scales both, and the object is small exactly when
proportional resize matters most. The visible beads are unchanged.
The audit called this 'proportional resize becomes unavailable'; the
measurement says otherwise and the spec records the correction: the
corner centre lies outside the edge circle, so the corner was reachable
— its area was halved, not lost. A polish, not a bug, and worth fixing
because it is one line of ordering.
(2) Alignment guides in the devices mode excluded the dragged marker by
_drag, which has been null there since #74 moved device dragging into
_deviceDrag. So the marker being moved was among its own candidates.
Nothing looked wrong because a point always matches itself within
tolerance — the guide was drawn from the marker to itself, visually
identical to an honest one, and the smoke asserted only guides() >= 1.
The smoke now compares the candidate lists with and without the drag and
demands exactly one removed entry.
(3) The 38 settings-help strings stay in the initial chunk, and that is
now a recorded decision rather than an oversight: measured 2 654 B gzip,
0.9 % of the ceiling, against splitting a synchronous dictionary in two,
a second request on first hint, and a second source for the key type
derived from en.json (#391). docs/ARCHITECTURE.md says so, with the
number that would justify revisiting it.
Both mutants run by hand: reverting the paint order reddens the 40 cm
probe while the 160 cm one stays green; restoring _drag reddens the
guides smoke.
User-Visible: yes
Issue: #400
The guard introduced by #394 matched the literal
sys.modules['custom_components... and therefore never looked at
pure_imports.py, which writes through a variable — the third instance of
the #389 class walked straight past the check created for it.
The guard now inspects the write itself and decides by the key: a whole
literal or the literal head of an f-string is safe unless it starts with
custom_components (that is how tests register homeassistant.*, hp_pure.*
and houseplan.trails); anything else — a variable, a concatenation,
setdefault/update — counts as a violation whenever the file is able to
name the package at all, i.e. contains a custom_components. literal. A
file that never names the package cannot poison it through a variable,
so restoring a snapshot stays legal.
load_pure now removes what it registered. Removing its own name is not
enough: relative imports pull neighbours in, so junction_limits leaves
wall_segment_model and coordinate_canonicalization behind. It removes
the whole custom_components difference accumulated during exec_module,
in a finally, and a repeated call still works.
pure_imports.py is a named exemption of the static guard precisely
because that guard cannot see the cleanup — so the cleanup is proven by
an executable test instead, and the mutant pure-imports-stops-cleaning
reddens it. Both mutants were run by hand.
User-Visible: no
Issue: #398
r6 Medium: AC4 was measurable only on a developer's machine — no
workflow invoked mypy, so a typing regression in any of the six
allowlist modules reached dev unnoticed while the issue claimed
measurable backend quality. Coverage and lint had continuous gates;
typing had a text comparison of a committed list.
The backend job now runs mypy right after ruff, from the same pinned
dependency file (mypy==2.3.1 — an unpinned checker would redden on code
that never changed). The step derives its module list from the
pyproject.toml strict allowlist instead of duplicating it, because a
drifted duplicate is a green step checking the wrong modules, and it
refuses an empty list rather than passing silently.
Guarded twice: a contract test pins all three facts (pinned checker,
a step that really invokes it, list read from pyproject) and the new
typing-gate-stops-running mutant reddens when the invocation is
neutered.
User-Visible: no
Issue: #42
Rebase resolution: dev's #392 introduced tests_backend/requirements.txt
(python 3.14, phcc 0.13.357, homeassistant 2026.8.3) — exactly the
single-source-of-pins AC of this issue, so the duplicate
requirements_test.txt is dropped and both workflows keep installing from
the #392 file; ruff is added there for the lint step. The backend
reuse key no longer names the dead file (tests_backend/ as a root
already covers the new one); ARCHITECTURE.md points at the real path.
User-Visible: no
Issue: #42
Tooling: requirements_test.txt becomes the single source of backend CI
dependencies; pyproject.toml configures ruff (E/F/B/I, E501 excluded by
decision) and mypy strict for a grow-only allowlist of six pure modules
(junction_limits annotated to pass). The 42 substantive ruff findings
are fixed — the B023 loop-variable closures bind their variables as
parameter defaults instead of hiding behind noqa, and every remaining
noqa carries a reason (guarded by a test).
Errors: const.ERROR_CODES / ERROR_CODE_FAMILIES formalise the stable
contract; the scanner test proves every emitted code across BOTH paths
(send_error literals; class attrs, literal and variable-passed
MarkerControlError codes, f-string families) is registered and has a
localized message — 22 missing backup.error.* keys added in all four
languages. invalid_passage_fields / invalid_partition_opening_jamb_margin
ship structured JSON details (legacy format read-compat for one beta),
and _errText renders code-first: unknown codes localize, raw English
messages go to the console.
CI: the backend job lints with ruff, refuses a silently skipped HA
harness (import + collect threshold), measures branch coverage over
pure+harness, fails below the committed baseline and uploads
coverage.xml. quality_scale: docs-troubleshooting/examples honestly
done, test-coverage/strict-typing carry staged progress.
User-Visible: yes
Issue: #42
Light grouping and the excluded-integrations list move from hidden keys
to a Discovery-filters section on the catalog's Available tab: a toggle
(unset = on, the legacy behaviour), searchable integration chips with a
Restore-recommended reset (defaults stored as key absence), and
appear/disappear counters computed by diffing the REAL
seedHiddenBindings/buildDevices outputs — no second copy of the filter.
Saving writes settings once over the ordinary expected_rev path. Every
excluded candidate now names its integration in the catalog. Room
climate follows the same user exclusions through the single
effectiveExcludedIntegrations resolver (spec H2); explicit climate
opt-in stays stronger. The field registry passports both keys as
current supported settings.
User-Visible: yes
Issue: #44
M1: the AC7 no-import test scans the whole src tree for the CURRENT dump
name (the old assertion checked the pre-rename string; proven by
execution — a planted fetch now turns it red).
M2: deduplicate the #33 paragraphs in both changelogs and ARCHITECTURE.
M3: the auditor's exit-3 statuses match the spec contract exactly
(migrate-*/deprecated-read); decision-required is live behaviour
awaiting #44, drop-on-validation is the backend's own job.
User-Visible: no
Issue: #33
repo-hygiene caught it: HACS globs *manifest.json over the whole clone
and rejects a repository with two. The dump is scripts/config-schema.json.
User-Visible: no
Issue: #33
The Voluptuous schema is now dumped into a deterministic committed
manifest (265 leaf paths); a pytest fails on drift. A parity test
compares manifest enums with the exported frontend const lists through
a machine-readable allow-list that also refuses to rot. The field
registry gains passports (allow-extra / lovelace-card), enforcedBy
citations for mechanisms that already shipped, and passports for the
v1.68-v1.69 fields; a completeness test bans dead decisions. Lifecycle
fixtures (oldest / current / future) prove lossless loading, and the
config auditor gains the 0/3/2 exit-code contract.
User-Visible: yes
Issue: #33
The Voluptuous schema is now dumped into a deterministic committed
manifest (265 leaf paths); a pytest fails on drift. A parity test
compares manifest enums with the exported frontend const lists through
a machine-readable allow-list that also refuses to rot. The field
registry gains passports (allow-extra / lovelace-card), enforcedBy
citations for mechanisms that already shipped, and passports for the
v1.68-v1.69 fields; a completeness test bans dead decisions. Lifecycle
fixtures (oldest / current / future) prove lossless loading, and the
config auditor gains the 0/3/2 exit-code contract.
User-Visible: yes
Issue: #33
#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
Add Deutsch across all card surfaces and backend flows, backed by the language registry introduced in #62. German loads as a fingerprint-checked page-shared locale chunk so EN/RU remain synchronous and the initial View budget stays intact. Root render gates prevent mixed-language flashes, retry once, and fail open to English. Extend parity, runtime, bundle, browser and visual coverage, plus contributor and user documentation.
Issue: #348
User-Visible: yes
52 блока host/переменных/кросс-поверхностных групп → src/styles/base.styles.ts;
styles.ts — 19-строчный сборщик [base, plan, devices, chrome, dialogs] с
задокументированным контрактом порядка каскада. Два оставшихся мутантных
якоря (:host-гейт ховера устройств) переадресованы на base.styles.ts.
Юниты инвариантов (test/styles-split.test.mjs): состав и порядок сборщика;
непересечение (scope+селектор) между файлами — единственное именованное
исключение: кросс-поверхностная группа «:host(...) .dev:hover, .dev:focus-
visible» живёт в base по §1.1; выживание медиа-обёрток — 2 forced-colors и
10 prefers-reduced-motion (в ТЗ и ревью фигурировали 8 — фактический счёт по
исходнику 10, юнит держит точное число). fix-test-build научился точке в
имени модуля (styles/base.styles → .js). ARCHITECTURE.md — раздел Styles.
Refactor-proof diff (scope-ключ) пуст; golden 129/129 без переприёмки; смоки
plan_snap_overlay/preloader OK; npm test 1318/0; бандл 1 291 440 → 1 291 458
(+18 байт).
Issue: #266
User-Visible: no
Owner-approved review exception: the external reviewer is unavailable. The exact branch SHA passed its recorded gates; the integration keeps #296 diagnostic geometry above masonry and #300 resize measurements above wall bodies. Generated bundles were rebuilt from the combined sources.
Issue: #300
User-Visible: no
Owner-approved review exception: the external reviewer is unavailable. Both r1 High findings were fixed, the exact branch SHA passed all recorded gates, and generated bundles were rebuilt after conflict resolution with #296.
Issue: #298
User-Visible: no