The owner decided on 30.09 that the moon is not part of the "Follow the Sun" environment but a switch of its own: with a static background (global or a space's own) the card showed no moon even with the switch on, and the switch said nothing about why the moon was missing right now. With a static background there is no environment, so the moon stands in its own layer, `.hp-moon-sky`: the first child of `.stage` / `.hp-static-stage`, the whole scene, no z-index, filter or will-change, under the plan by DOM order, fading with the #101 View weight. Inside is the very #661 element, so place, size, art and fades are unchanged, and a background switch moves it to its new parent in the same render without a flicker. The phase comes from the same `resolveDayCycle`, computed only while the moon is on and on View; without `sun.sun` both cards keep their 30 s clock ticker and re-render only when the phase changes (the environment is still compared by its whole fingerprint). General settings get a second caption line under the moon switch (`data-moon-status`): one snapshot per opening, judged by the lazy chunk as if the switch were on, first reason wins (no home, day, below 3°, under 3 %), numbers rounded and clamped below the threshold they missed. `moonStatus` decides "shown" with the same `moonShownAt` as the element. It lives in a WeakMap beside the draft, so it never makes the dialog dirty; a closed opening's result is dropped. The dialog loads the chunk through the gate's loader (`withMoon`), now shared by every caller while a load is in flight, so there is still one fingerprint check and one retry token. Bundle (same build, against origin/dev): initial View 300 072 -> 300 248 B gzip (+176 B, under the 500 B of the spec; budget and ceiling not raised); lazy editor 238 558 -> 238 991 B (+433 B, the line and English strings); lazy moon 11 385 -> 11 712 B (+327 B, layer CSS and status). `src/moon.ts` stays out of the initial and the editor graph; bundle-budget now refuses an editor/moon overlap. Monolith metrics: hostRefs 4 885 -> 4 888 — the three `host.` reads of `src/editors/moon-status.ts` (hass, `_settingsDialog`, requestUpdate) through its own three-member interface, not the editor port; the other five metrics are unchanged. houseplan-editor-runtime.ts grows by two lines (import, call). Tests: AC9/AC10/AC15 and the sky layer in test/moon.test.mjs (the #661 "static -> nothing" check inverted), AC14 and the opening lifecycle in test/moon-settings.test.mjs, smokes demo/smoke_moon_static.mjs (AC1-AC6; AC1 and AC3 were red on dev) and demo/smoke_moon_status.mjs (AC11/AC12), AC7 in smoke_daycycle_layer_budget. Golden: two new scenes (static-bg-moon-gibbous-white-light, static-bg-moon-crescent-south-dark, matrix v70), the harness checks the moon's parent by background and waits for the status line in the General settings frames. Four new mutants; the clock ticker one is a browser guard (201 at the guideline of 200). Issue: #718 User-Visible: yes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
12 KiB
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 | 86 | The invariant crosses Lit/custom-element lifecycle, browser storage/events, lazy loading or a complete HA-card state transition. |
| Total | 201 / 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 (1,017 mutants) | 48:08 (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.
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-againjunction-limit-p4-bruteforce-againwall-draw-full-preflight-againwall-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-unwatchedreport-page-errors-skips-round-tripsmoke-guard-blind-to-tailsmoke-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-promoteddaycycle-outline-promoted-on-inner-paperdaycycle-static-outline-promoted-on-inner-paperdecor-restored-below-room-fillsdevice-keyboard-bypasses-click-pathdevice-long-value-ellipsis-restoreddevice-unavailable-hover-restoredgolden-filled-tunnel-removedgolden-lamp-out-of-reachhatch-static-renderer-untouchedhatch-stroke-not-scaledhatch-zoom-compensation-backiso-first-frame-reveals-flat-during-lazy-loadiso-room-label-44-box-centres-nameiso-sun-card-drops-occludersiso-sun-flat-wedges-remainiso-theme-dark-wall-rule-returnsstage3-w4-device-target-loses-44px-floorstage3-w5-runtime-nudge-writes-storagestage3-w6-no-borders-keeps-raised-platesstage3-w7-sun-state-enters-structural-keysun-ray-origin-cache-ignoredsun-ray-origin-save-forced-innervalue-static-icon-keeps-live-vacuumvalue-static-icon-keeps-route-warningvalue-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-sourcealign-point-reads-frozen-snapshotdecor-default-style-debounce-cutdecor-default-style-seed-cutdecor-keyboard-nudge-drops-focus-dialog-guardsdecor-keyboard-nudge-reruns-magnetdense-device-hit-browser-skips-painted-prioritydouble-fit-bypasses-canonical-fit-allfurniture-art-editor-adopt-skippedfurniture-edge-handles-steal-the-cornerfurniture-exterior-surface-removedfurniture-shift-listeners-not-attachedfurniture-wall-runtime-drops-drag-sidefurniture-wall-runtime-drops-raw-intentlive-pinch-compositor-demoted-on-active-lit-commitopening-dimension-overlay-hiddenopening-search-hides-noneopening-search-select-not-wiredplacement-accepts-any-mouse-buttonreorder-skips-materializationresize-history-boundary-repair-removedresize-label-uses-old-room-gear-centreresize-labels-hide-narrow-arearesize-pointer-capture-removedresize-preview-reject-silentroom-fit-html-overlay-jumps-aheadroom-fit-pan-release-reacceptedroom-fit-persists-zoomsafe-resize-commit-preflight-bypassedsections-editor-transition-uses-window-heightsections-grid-drops-stage-flex-chainsections-grid-falls-back-to-viewport-heightsections-resize-drops-stage-refit-observerspace-card-decor-capability-change-not-adoptedspace-card-decor-capability-downgrade-does-not-clear-assetstab-drag-outlives-the-cardtab-drag-survives-release-outsidetab-drag-target-follows-captured-sourcetab-drop-indicator-always-beforetab-drop-outside-commits-last-targettab-reorder-not-persistedtouch-pinch-marker-hold-rearmedtouch-pinch-zoom-persists-per-frameunavailable-toggle-stays-silentwall-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-enableddialog-ha-rejected-close-reopen-disableddialog-native-reconnect-recovery-disableddialog-native-surface-stretches-to-viewportdialog-native-update-recovery-disabledfit-house-hidden-walls-votegrid-scale-imperial-roundtrip-driftgrid-scale-opening-hit-unscaledgrid-scale-opening-symbol-unscaledgrid-scale-plan-chrome-unscaledgrid-scale-static-factor-missingheader-menu-item-below-44header-menu-outside-tap-reaches-planhidden-room-names-compact-svg-fallbackhidden-room-names-full-svg-fallbackhidden-room-names-iso-overridehp-dialog-ignores-flex-contentpanel-readonly-empty-bypasses-write-capabilityphone-editor-close-hidden-with-mode-buttonsphone-header-at-tablet-widthphone-header-shows-titlephone-header-wrapsstatic-card-descendants-hit-testablesummary-dialog-drops-flex-contentsummary-picker-hot-add-stays-filteredtab-editing-without-write-accesstoolbar-active-highlight-stops-at-tabtoolbar-close-back-inside-tabtoolbar-close-hidden-at-medium-widthtoolbar-close-slot-collapses-outside-editortoolbar-close-slot-idle-focusabletoolbar-close-slot-leaves-mode-grouptoolbar-close-target-below-24toolbar-device-count-returnstoolbar-mode-zoom-gap-regresseswarm-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-failurearea-relocation-clears-whole-historyarea-relocation-loses-position-on-refusalbackdrop-busy-dismiss-races-decisionbackdrop-downscale-drops-alphabackdrop-phase2-falls-back-to-originalbackdrop-probe-always-safebarrier-cache-never-invalidatedcamera-anchor-from-presentedcamera-cancel-loses-zoomchild-readd-clears-parent-tombstonecold-view-toggle-delegated-to-runtimecold-view-vacuum-mapid-delegatedcolor-picker-confirm-click-throughcolor-picker-invalid-confirm-latch-removedconfig-updated-event-ignoredconfirm-dialog-loses-alertdialogcurrent-rejected-physical-write-keeps-optimistic-walldanger-confirm-back-into-the-branchdanger-confirm-lost-space-request-guard-removeddanger-confirm-lost-space-transition-cancel-removeddanger-confirm-uses-last-rendered-language-gatedanger-confirm-warm-language-guard-removeddanger-confirm-warm-transition-cancel-removeddaycycle-programmatic-camera-skips-safe-outlinedevice-echo-keeps-local-noncanonicaldevice-focus-tooltip-blur-cleanup-removeddevice-focus-tooltip-handler-removeddevice-focus-tooltip-room-hover-overwritesdevice-inbox-batch-rollbackhousehold-enter-stops-actinghousehold-marker-drops-keyboard-reachdevice-markers-rendered-without-keysdevice-pointer-leave-clears-focus-fallbackdevice-position-cancel-routed-to-commitdiscovery-reset-writes-a-copyeditor-neutral-escape-does-not-exitempty-space-cleanup-disabledfixed-floor-transition-guard-bypassedfrench-locale-wrong-dictionaryhp-dialog-escape-does-not-closejunction-limit-baseline-cache-stalejunction-limit-candidate-fail-openjunction-limit-write-gate-removedlocale-failure-toast-droppedmarker-reject-keeps-optimistic-candidatemarker-rollback-keeps-enqueue-time-revisionmoon-static-clock-tick-offnamespace-loader-returns-englishnear-axis-authoring-snap-bypassedonboarding-loader-skips-namespace-ensureopenings-rendered-without-keysplan-only-preview-label-hiddenplan-room-area-icon-hiddenplan-room-area-icon-navigatesplan-upload-client-limitplan-upload-guard-originalplan-upload-reduced-over-limit-stagedpost-write-tail-runs-on-refused-gatereadonly-view-syncs-new-devicesrender-invalidation-renders-irrelevant-hareopened-room-from-registry-spaceroom-accept-leaves-coincident-partitionsroom-gear-second-touch-keeps-dragroom-settings-click-does-not-openroom-tooltip-off-skips-pointer-modalitysame-space-room-change-recentersspace-create-hidden-display-overridestairs-view-pan-opens-target-floorsupport-invalid-response-leaks-issued-tokensupport-stale-preview-response-revives-consentsupport-timeout-claims-successvacuum-overlay-back-to-the-dock-space-filtervolumetric-kiosk-ignores-settingview-current-space-aria-removedwall-face-apply-skips-overlap-guardwallthick-hit-narrowedwriter-history-skips-finished-chain-normalizationzigbee-topology-endpoint-cleanup-skippedzigbee-topology-endpoint-elevation-removedzigbee-topology-hovered-endpoint-elevation-removedzigbee-topology-overlay-double-live-projectionzigbee-topology-overlay-layer-loweredzigbee-topology-unknown-casing-gaps-filledzigbee-topology-unknown-casing-removedzigbee-topology-unrelated-markers-raised