mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
With the day/night background the plan went blurry after zooming from 100 %
(sharp when the page was opened at 800 %) and navigating a strongly zoomed
plan flashed the page white. Both came from #582's composition, not from the
wall hatch that #685 replaced:
- `.stage.daycycle.hp-safe-daycycle-outline .plan-svg` promoted the scene
with `will-change: transform`; Chromium freezes the raster scale of such a
layer, so the 100 % raster was shown stretched. The explicit layer #582
needs is now `will-change: opacity` (re-rasters at the current scale).
- The filtered outline had `overflow: visible` and a gesture exposed every
scene (#544) without bound, so the promoted layers grew with zoom squared
(CDP LayerTree, ~460 %: plan-svg 15.9x, outline 13.2x the stage; 39x after
navigating at 800 %). The full card clips its outline to its box and marks
it data-hp-live-overflow="clip" (never exposed); the live viewport bounds
every other exposure with an inline clip-path: inset(-25%) that leaves
with it, so idle DOM stays byte-identical (#531).
Owner-verified in Chrome 152 (built-in browser, DPR 2): sharp after 100 ->
800 %, no white flashes after reloading at 800 %.
Owner decision: #685's analytic gradient is reverted (13af1d5e), the single
<pattern> is back at every zoom; its close-up golden scenes stay and check
the pattern, its terminal-frame smoke checks the pattern.
Witnesses: demo/smoke_daycycle_zoom_layers.mjs (800 % x DPR 2: layers vs
stage, the hint, reload path, button/wheel/pinch); #582/#532 smokes now pin
the opacity hint; test/live-viewport.test.mjs (bounded exposure, clipped
scene); test/daycycle-layers.test.mjs (cascade). Four Node-guarded mutants.
Issue: #689
User-Visible: yes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
283 lines
12 KiB
Markdown
283 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 | 85 | The invariant crosses Lit/custom-element lifecycle, browser storage/events, lazy loading or a complete HA-card state transition. |
|
|
| **Total** | **200 / 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`
|
|
- `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`
|
|
- `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`
|
|
- `same-space-room-change-recenters`
|
|
- `space-create-hidden-display-override`
|
|
- `stairs-view-pan-opens-target-floor`
|
|
- `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`
|