mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 12:49:56 +00:00
A floor with stairs pays for them on every switch to it: the stair layer is emptied on other floors, so Lit recreates every symbol on each return and the browser lays out and paints it again. With 250 stairs (the large-house fixture, the per-floor limit) that was 2,875 SVG elements and about 40 ms per entry locally; each stair carried 3-7 separate tread lines with four bound coordinates each. The treads of one stair are now a single <path class="hp-stair-tread"> with one `M a L b` subpath per tread, in geometry order and with the numbers the lines carried. Treads of one stair never overlap (straight: parallel, >= 20 cm apart; spiral: inner ends >= 6.7 cm apart at the 3.6 cm stroke), so the path paints the same pixels at any opacity. The outline points and the tread data are built once per cached geometry object (cachedStairMarkup, weak keys), not on every render. The View and plan-editor layers share the strings; outline, hit polygon, trapezoid, arrow, attributes and handlers are unchanged. Floor 1 of the fixture drops from 4,210 to 2,960 elements. Witnesses: the unit test for the path data and its cache, and the smoke_stairs markup checks in View and in the plan editor, are red on dev. The stairs-view-tread-lines mutant restores the View lines. Issue: #740 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
288 lines
12 KiB
Markdown
288 lines
12 KiB
Markdown
# Browser guards mutation registry (#659)
|
|
|
|
This is the reviewed classification of every mutation witness that still needs a browser.
|
|
The executable limit is `200`; `mutation-gate --check`, `npm run inventory` and the unit
|
|
contract all read this same inventory. A new browser guard must be added deliberately under
|
|
one reason below, and its mutant `because` must explain the concrete browser-only invariant.
|
|
|
|
Converted witnesses are not listed here: their registry guard names an explicit `node --test`
|
|
suite plus `test/mutation-browser-offload.test.mjs`, and every converted mutant is run once
|
|
to prove that the Node witness actually kills it.
|
|
|
|
| Category | Count | Why a browser is still required |
|
|
| --- | ---: | --- |
|
|
| Performance threshold | 4 | The witness measures real browser wall-time or frame work; a pure assertion cannot prove the budget. |
|
|
| Browser harness integrity | 4 | The mutation breaks page-error, round-trip or page-registration observation in the browser harness itself. |
|
|
| Paint, cascade and layer composition | 26 | The invariant depends on computed CSS, SVG paint, clipping, stacking or pixels produced by Chromium. |
|
|
| Pointer geometry and trusted interaction | 45 | The invariant depends on hit testing, pointer capture, touch/keyboard dispatch or live DOM geometry. |
|
|
| Responsive DOM layout | 36 | The invariant depends on measured element boxes, responsive breakpoints, native/HA dialog shells or focusable target size. |
|
|
| Custom-element and HA browser lifecycle | 89 | The invariant crosses Lit/custom-element lifecycle, browser storage/events, lazy loading or a complete HA-card state transition. |
|
|
| **Total** | **204 / 200** | Growth above the cap fails `mutation-gate --check`. |
|
|
|
|
## Measured effect
|
|
|
|
The measurements below are GitHub-hosted Linux runs from 2026-09-27. They are
|
|
recorded separately from the policy limit: the limit proves that browser guards
|
|
cannot grow unnoticed, while timings show the effect actually observed on the
|
|
shared runners.
|
|
|
|
| Measurement | Before | After | Observed change |
|
|
| --- | ---: | ---: | ---: |
|
|
| Full-registry wall time | [54:18](https://github.com/Matysh/houseplan-card/actions/runs/36298676826) (1,017 mutants) | [48:08](https://github.com/Matysh/houseplan-card/actions/runs/36316263355) (1,028 mutants) | **-6:10 (-11.4%)**, despite 11 additional mutants |
|
|
| Sum of the six `Each test catches its breakage` steps | 4:17:19 | 4:12:32 | **-4:47 (-1.9%)** |
|
|
| Longest full-registry shard step | 50:45 | 45:13 | **-5:32 (-10.9%)** |
|
|
| Same converted-witness slice, aggregate time | 33:43.9 | 12:07.7 | **-21:36.2 (-64.0%)** |
|
|
| Same converted-witness slice, critical shard | 8:05.1 | 2:19.3 | **-5:45.8 (-71.3%)** |
|
|
|
|
The candidate slice compares the same 83 converted mutant ids in the baseline
|
|
full-registry log and in the S7 candidate Validate
|
|
[36316464432](https://github.com/Matysh/houseplan-card/actions/runs/36316464432).
|
|
It sums the interval between consecutive per-mutant completion timestamps on
|
|
each of the six shards. One of the 84 converted ids, `active-tab-not-revealed`,
|
|
was the first completed mutant in its shard, so the log cannot isolate its time
|
|
from clean-guard setup and it is excluded from both sides. The complete candidate
|
|
run, which also selected registry/tooling mutants outside this converted slice,
|
|
finished in 10:03.
|
|
|
|
The original estimate of three to four hours saved on a full-registry run is not
|
|
supported by this A/B evidence. The directly converted slice is materially
|
|
faster, but registry growth and shared-runner variance consume most of that gain
|
|
in the end-to-end total; only the measured reductions above are claimed.
|
|
|
|
## Reviewed per-mutant inventory
|
|
|
|
### Performance threshold
|
|
|
|
The witness measures real browser wall-time or frame work; a pure assertion cannot prove the budget.
|
|
|
|
- `junction-limit-p3-quadratic-again`
|
|
- `junction-limit-p4-bruteforce-again`
|
|
- `wall-draw-full-preflight-again`
|
|
- `wall-draw-wall-artifact-discarded`
|
|
|
|
### Browser harness integrity
|
|
|
|
The mutation breaks page-error, round-trip or page-registration observation in the browser harness itself.
|
|
|
|
- `benchmark-page-verdict-unwatched`
|
|
- `report-page-errors-skips-round-trip`
|
|
- `smoke-guard-blind-to-tail`
|
|
- `smoke-guard-forgets-to-register-pages`
|
|
|
|
### Paint, cascade and layer composition
|
|
|
|
The invariant depends on computed CSS, SVG paint, clipping, stacking or pixels produced by Chromium.
|
|
|
|
- `daycycle-outline-not-promoted`
|
|
- `daycycle-outline-promoted-on-inner-paper`
|
|
- `daycycle-static-outline-promoted-on-inner-paper`
|
|
- `decor-restored-below-room-fills`
|
|
- `device-keyboard-bypasses-click-path`
|
|
- `device-long-value-ellipsis-restored`
|
|
- `device-unavailable-hover-restored`
|
|
- `golden-filled-tunnel-removed`
|
|
- `golden-lamp-out-of-reach`
|
|
- `hatch-static-renderer-untouched`
|
|
- `hatch-stroke-not-scaled`
|
|
- `hatch-zoom-compensation-back`
|
|
- `iso-first-frame-reveals-flat-during-lazy-load`
|
|
- `iso-room-label-44-box-centres-name`
|
|
- `iso-sun-card-drops-occluders`
|
|
- `iso-sun-flat-wedges-remain`
|
|
- `iso-theme-dark-wall-rule-returns`
|
|
- `stage3-w4-device-target-loses-44px-floor`
|
|
- `stage3-w5-runtime-nudge-writes-storage`
|
|
- `stage3-w6-no-borders-keeps-raised-plates`
|
|
- `stage3-w7-sun-state-enters-structural-key`
|
|
- `sun-ray-origin-cache-ignored`
|
|
- `sun-ray-origin-save-forced-inner`
|
|
- `value-static-icon-keeps-live-vacuum`
|
|
- `value-static-icon-keeps-route-warning`
|
|
- `value-static-icon-keeps-vacuum-overlay`
|
|
|
|
### Pointer geometry and trusted interaction
|
|
|
|
The invariant depends on hit testing, pointer capture, touch/keyboard dispatch or live DOM geometry.
|
|
|
|
- `align-guides-exclude-dead-source`
|
|
- `align-point-reads-frozen-snapshot`
|
|
- `decor-default-style-debounce-cut`
|
|
- `decor-default-style-seed-cut`
|
|
- `decor-keyboard-nudge-drops-focus-dialog-guards`
|
|
- `decor-keyboard-nudge-reruns-magnet`
|
|
- `dense-device-hit-browser-skips-painted-priority`
|
|
- `double-fit-bypasses-canonical-fit-all`
|
|
- `furniture-art-editor-adopt-skipped`
|
|
- `furniture-edge-handles-steal-the-corner`
|
|
- `furniture-exterior-surface-removed`
|
|
- `furniture-shift-listeners-not-attached`
|
|
- `furniture-wall-runtime-drops-drag-side`
|
|
- `furniture-wall-runtime-drops-raw-intent`
|
|
- `live-pinch-compositor-demoted-on-active-lit-commit`
|
|
- `opening-dimension-overlay-hidden`
|
|
- `opening-search-hides-none`
|
|
- `opening-search-select-not-wired`
|
|
- `placement-accepts-any-mouse-button`
|
|
- `reorder-skips-materialization`
|
|
- `resize-history-boundary-repair-removed`
|
|
- `resize-label-uses-old-room-gear-centre`
|
|
- `resize-labels-hide-narrow-area`
|
|
- `resize-pointer-capture-removed`
|
|
- `resize-preview-reject-silent`
|
|
- `room-fit-html-overlay-jumps-ahead`
|
|
- `room-fit-pan-release-reaccepted`
|
|
- `room-fit-persists-zoom`
|
|
- `safe-resize-commit-preflight-bypassed`
|
|
- `sections-editor-transition-uses-window-height`
|
|
- `sections-grid-drops-stage-flex-chain`
|
|
- `sections-grid-falls-back-to-viewport-height`
|
|
- `sections-resize-drops-stage-refit-observer`
|
|
- `space-card-decor-capability-change-not-adopted`
|
|
- `space-card-decor-capability-downgrade-does-not-clear-assets`
|
|
- `tab-drag-outlives-the-card`
|
|
- `tab-drag-survives-release-outside`
|
|
- `tab-drag-target-follows-captured-source`
|
|
- `tab-drop-indicator-always-before`
|
|
- `tab-drop-outside-commits-last-target`
|
|
- `tab-reorder-not-persisted`
|
|
- `touch-pinch-marker-hold-rearmed`
|
|
- `touch-pinch-zoom-persists-per-frame`
|
|
- `unavailable-toggle-stays-silent`
|
|
- `wall-draw-rejection-rollback-skipped`
|
|
|
|
### Responsive DOM layout
|
|
|
|
The invariant depends on measured element boxes, responsive breakpoints, native/HA dialog shells or focusable target size.
|
|
|
|
- `dialog-ha-disconnected-reject-reopen-enabled`
|
|
- `dialog-ha-rejected-close-reopen-disabled`
|
|
- `dialog-native-reconnect-recovery-disabled`
|
|
- `dialog-native-surface-stretches-to-viewport`
|
|
- `dialog-native-update-recovery-disabled`
|
|
- `fit-house-hidden-walls-vote`
|
|
- `grid-scale-imperial-roundtrip-drift`
|
|
- `grid-scale-opening-hit-unscaled`
|
|
- `grid-scale-opening-symbol-unscaled`
|
|
- `grid-scale-plan-chrome-unscaled`
|
|
- `grid-scale-static-factor-missing`
|
|
- `header-menu-item-below-44`
|
|
- `header-menu-outside-tap-reaches-plan`
|
|
- `hidden-room-names-compact-svg-fallback`
|
|
- `hidden-room-names-full-svg-fallback`
|
|
- `hidden-room-names-iso-override`
|
|
- `hp-dialog-ignores-flex-content`
|
|
- `panel-readonly-empty-bypasses-write-capability`
|
|
- `phone-editor-close-hidden-with-mode-buttons`
|
|
- `phone-header-at-tablet-width`
|
|
- `phone-header-shows-title`
|
|
- `phone-header-wraps`
|
|
- `static-card-descendants-hit-testable`
|
|
- `summary-dialog-drops-flex-content`
|
|
- `summary-picker-hot-add-stays-filtered`
|
|
- `tab-editing-without-write-access`
|
|
- `toolbar-active-highlight-stops-at-tab`
|
|
- `toolbar-close-back-inside-tab`
|
|
- `toolbar-close-hidden-at-medium-width`
|
|
- `toolbar-close-slot-collapses-outside-editor`
|
|
- `toolbar-close-slot-idle-focusable`
|
|
- `toolbar-close-slot-leaves-mode-group`
|
|
- `toolbar-close-target-below-24`
|
|
- `toolbar-device-count-returns`
|
|
- `toolbar-mode-zoom-gap-regresses`
|
|
- `warm-dialog-drops-transferred-baseline`
|
|
|
|
### Custom-element and HA browser lifecycle
|
|
|
|
The invariant crosses Lit/custom-element lifecycle, browser storage/events, lazy loading or a complete HA-card state transition.
|
|
|
|
- `accepted-marker-rolled-back-by-layout-failure`
|
|
- `area-relocation-clears-whole-history`
|
|
- `area-relocation-loses-position-on-refusal`
|
|
- `backdrop-busy-dismiss-races-decision`
|
|
- `backdrop-downscale-drops-alpha`
|
|
- `backdrop-phase2-falls-back-to-original`
|
|
- `backdrop-probe-always-safe`
|
|
- `barrier-cache-never-invalidated`
|
|
- `camera-anchor-from-presented`
|
|
- `camera-cancel-loses-zoom`
|
|
- `child-readd-clears-parent-tombstone`
|
|
- `cold-view-toggle-delegated-to-runtime`
|
|
- `cold-view-vacuum-mapid-delegated`
|
|
- `color-picker-confirm-click-through`
|
|
- `color-picker-invalid-confirm-latch-removed`
|
|
- `config-updated-event-ignored`
|
|
- `confirm-dialog-loses-alertdialog`
|
|
- `current-rejected-physical-write-keeps-optimistic-wall`
|
|
- `danger-confirm-back-into-the-branch`
|
|
- `danger-confirm-lost-space-request-guard-removed`
|
|
- `danger-confirm-lost-space-transition-cancel-removed`
|
|
- `danger-confirm-uses-last-rendered-language-gate`
|
|
- `danger-confirm-warm-language-guard-removed`
|
|
- `danger-confirm-warm-transition-cancel-removed`
|
|
- `daycycle-programmatic-camera-skips-safe-outline`
|
|
- `device-echo-keeps-local-noncanonical`
|
|
- `device-focus-tooltip-blur-cleanup-removed`
|
|
- `device-focus-tooltip-handler-removed`
|
|
- `device-focus-tooltip-room-hover-overwrites`
|
|
- `device-inbox-batch-rollback`
|
|
- `household-enter-stops-acting`
|
|
- `household-marker-drops-keyboard-reach`
|
|
- `device-markers-rendered-without-keys`
|
|
- `device-pointer-leave-clears-focus-fallback`
|
|
- `device-position-cancel-routed-to-commit`
|
|
- `discovery-reset-writes-a-copy`
|
|
- `editor-neutral-escape-does-not-exit`
|
|
- `empty-space-cleanup-disabled`
|
|
- `fixed-floor-transition-guard-bypassed`
|
|
- `french-locale-wrong-dictionary`
|
|
- `hp-dialog-escape-does-not-close`
|
|
- `junction-limit-baseline-cache-stale`
|
|
- `junction-limit-candidate-fail-open`
|
|
- `junction-limit-write-gate-removed`
|
|
- `locale-failure-toast-dropped`
|
|
- `marker-reject-keeps-optimistic-candidate`
|
|
- `marker-rollback-keeps-enqueue-time-revision`
|
|
- `moon-static-clock-tick-off`
|
|
- `namespace-loader-returns-english`
|
|
- `near-axis-authoring-snap-bypassed`
|
|
- `onboarding-loader-skips-namespace-ensure`
|
|
- `openings-rendered-without-keys`
|
|
- `plan-only-preview-label-hidden`
|
|
- `plan-room-area-icon-hidden`
|
|
- `plan-room-area-icon-navigates`
|
|
- `plan-upload-client-limit`
|
|
- `plan-upload-guard-original`
|
|
- `plan-upload-reduced-over-limit-staged`
|
|
- `post-write-tail-runs-on-refused-gate`
|
|
- `readonly-view-syncs-new-devices`
|
|
- `render-invalidation-renders-irrelevant-ha`
|
|
- `render-reconcile-restarts-editor-runtime-cycle`
|
|
- `reopened-room-from-registry-space`
|
|
- `room-accept-leaves-coincident-partitions`
|
|
- `room-gear-second-touch-keeps-drag`
|
|
- `room-settings-click-does-not-open`
|
|
- `room-tooltip-off-skips-pointer-modality`
|
|
- `rooms-rendered-without-keys`
|
|
- `rooms-rendered-without-space-key`
|
|
- `same-space-room-change-recenters`
|
|
- `space-create-hidden-display-override`
|
|
- `stairs-view-pan-opens-target-floor`
|
|
- `stairs-view-tread-lines`
|
|
- `support-invalid-response-leaks-issued-token`
|
|
- `support-stale-preview-response-revives-consent`
|
|
- `support-timeout-claims-success`
|
|
- `vacuum-overlay-back-to-the-dock-space-filter`
|
|
- `volumetric-kiosk-ignores-setting`
|
|
- `view-current-space-aria-removed`
|
|
- `wall-face-apply-skips-overlap-guard`
|
|
- `wallthick-hit-narrowed`
|
|
- `writer-history-skips-finished-chain-normalization`
|
|
- `zigbee-topology-endpoint-cleanup-skipped`
|
|
- `zigbee-topology-endpoint-elevation-removed`
|
|
- `zigbee-topology-hovered-endpoint-elevation-removed`
|
|
- `zigbee-topology-overlay-double-live-projection`
|
|
- `zigbee-topology-overlay-layer-lowered`
|
|
- `zigbee-topology-unknown-casing-gaps-filled`
|
|
- `zigbee-topology-unknown-casing-removed`
|
|
- `zigbee-topology-unrelated-markers-raised`
|