Host floor: one module, a latch for a max-height host, and a gate that pins measure-once
- library/elements/hostFloor.js holds the decision ProfileLoader uses: probeOnce() at mount, follow() on every host resize (reads only; at most one flip of the floor). - A host CAPPED below the floor (max-height) follows its content up to the cap. It is found at mount, or, if the cap arrives later, by the host shrinking right after a flip; either way it is latched as content-sized and never flipped again, so it cannot loop. The post-flip check runs before the collapse rule, so a capped host with empty content is latched rather than flipped back and forth. - check:host-floor (gates:pre; self-test in gates:post): a stub DOM with a scrolled child that clamps when the loader collapses. After mount, host resizes never write the loader nor move the child; a sized host fills; late sizing flips once; a max-height cap (at mount: 0 flips; after mount: at most one flip and one revert) never loops; a collapsed host gets the floor back; an inline host is left alone. Poisons: the old per-resize probe, and a missing latch.
This commit is contained in:
parent
9a8d31d18e
commit
cd55cfd877
4 changed files with 205 additions and 49 deletions
124
internal/check-host-floor.mjs
Normal file
124
internal/check-host-floor.mjs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* check-host-floor.mjs — the profile floor is decided against the host WITHOUT disturbing live content.
|
||||
*
|
||||
* library/elements/hostFloor.js (used by ProfileLoader.vue) measures the host ONCE, at mount, and after that only
|
||||
* reads sizes. The regression this pins: the probe ran on EVERY host resize, collapsing the loader under live
|
||||
* content; a scrolling child's scrollTop was clamped to 0 and the embedded UPG-100 app flipped from Map back to its
|
||||
* first view whenever a phone's URL bar resized the host.
|
||||
*
|
||||
* A stub DOM models the layout that matters: the host's height from its mode and the loader's min-height, the
|
||||
* floor (100svh) as a number the test moves like a window resize, and a scrolled CHILD whose scrollTop clamps to 0
|
||||
* whenever the loader collapses.
|
||||
* A. after mount, host resizes (a content-sized host following a window resize; a sized host) never move the
|
||||
* child's scrollTop and never write the loader's min-height;
|
||||
* B. a sized host (523 px) is filled: the floor goes off at mount;
|
||||
* C. a host styled SHORTER than the floor after mount flips to sized ONCE, then stays;
|
||||
* D. a host capped by max-height (600 < floor) is found at mount and never flips: 0 flips over 20 resizes;
|
||||
* E. a max-height cap that arrives AFTER mount flips at most once and reverts once, then stays (no loop);
|
||||
* F. a sized host that collapses below 1px gets the floor back;
|
||||
* G. an inline host is left alone after mount.
|
||||
* `--self-test`: the old per-resize probe (A fails: the child's scrollTop is clamped) and a missing latch (E
|
||||
* loops) must each FAIL.
|
||||
*/
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
|
||||
const SRC = join(ROOT, 'library/elements/hostFloor.js')
|
||||
const asModule = (src) => import('data:text/javascript;base64,' + Buffer.from(src).toString('base64'))
|
||||
|
||||
// mode: 'content' (height = max(content, min-height)), 'sized' (a fixed height), 'capped' (content-sized up to a cap)
|
||||
function world({ mode = 'content', fixed = 523, cap = 600, content = 0, floor = 844, display = 'block' } = {}) {
|
||||
const w = { mode, fixed, cap, content, floor, display, writes: [], child: { scrollTop: 787 } }
|
||||
const attrs = new Set()
|
||||
const minPx = () => {
|
||||
const v = loader.style._min
|
||||
if (v === '' || v === undefined) return w.floor
|
||||
return parseFloat(v)
|
||||
}
|
||||
w.hostH = () => {
|
||||
if (w.mode === 'sized') return w.fixed
|
||||
const natural = Math.max(w.content, minPx())
|
||||
return w.mode === 'capped' ? Math.min(natural, w.cap) : natural
|
||||
}
|
||||
const loader = {
|
||||
style: {
|
||||
_min: '',
|
||||
get minHeight() { return this._min },
|
||||
set minHeight(v) {
|
||||
this._min = v; w.writes.push(v)
|
||||
if (v === '0px' && w.mode !== 'sized') w.child.scrollTop = 0 // the loader collapsed: the child clamps
|
||||
},
|
||||
},
|
||||
hasAttribute: (n) => attrs.has(n), setAttribute: (n) => attrs.add(n), removeAttribute: (n) => attrs.delete(n),
|
||||
}
|
||||
const host = { getBoundingClientRect: () => ({ height: w.hostH() }) }
|
||||
w.loader = loader; w.host = host
|
||||
w.style = (n) => (n === host ? { display: w.display, minHeight: '0px' } : { display: 'block', minHeight: (loader.style._min === '' ? w.floor : parseFloat(loader.style._min)) + 'px' })
|
||||
w.sized = () => attrs.has('data-host-sized')
|
||||
return w
|
||||
}
|
||||
|
||||
async function audit(src) {
|
||||
const { createHostFloor } = await asModule(src)
|
||||
const fails = []
|
||||
let t = 0
|
||||
const make = (opts) => { const w = world(opts); w.f = createHostFloor(w.loader, w.host, { style: w.style, now: () => t }); return w }
|
||||
|
||||
// A: content-sized host, a window resize moves the floor and the host with it
|
||||
{ const w = make({ mode: 'content', content: 0 }); w.f.probeOnce(); w.child.scrollTop = 787; w.writes.length = 0
|
||||
for (const fl of [830, 760, 700, 844]) { w.floor = fl; w.f.follow() }
|
||||
if (w.child.scrollTop !== 787) fails.push(`A: a host resize after mount clamped a scrolled child (scrollTop 787 → ${w.child.scrollTop})`)
|
||||
if (w.writes.length) fails.push(`A: a host resize after mount wrote the loader's min-height (${w.writes.join(', ')})`)
|
||||
if (w.sized()) fails.push('A: a content-sized host was flipped to sized by a window resize') }
|
||||
{ const w = make({ mode: 'sized', fixed: 523 }); w.f.probeOnce(); w.writes.length = 0; w.child.scrollTop = 300
|
||||
for (let i = 0; i < 4; i++) w.f.follow()
|
||||
if (w.writes.length || w.child.scrollTop !== 300) fails.push('A: a sized host\'s resize callbacks touched the loader') }
|
||||
// B
|
||||
{ const w = make({ mode: 'sized', fixed: 523 }); w.f.probeOnce()
|
||||
if (!w.sized() || w.loader.style.minHeight !== '0px') fails.push('B: a sized host (523) is not filled (the floor stayed on)') }
|
||||
// C
|
||||
{ const w = make({ mode: 'content' }); w.f.probeOnce(); let flips = 0, was = w.sized()
|
||||
w.mode = 'sized'; w.fixed = 600
|
||||
for (let i = 0; i < 10; i++) { t += 100; w.f.follow(); if (w.sized() !== was) { flips++; was = w.sized() } }
|
||||
if (!w.sized() || flips !== 1) fails.push(`C: a host sized after mount should flip to sized exactly once (flips ${flips}, sized ${w.sized()})`) }
|
||||
// D
|
||||
{ const w = make({ mode: 'capped', cap: 600, content: 0 }); w.f.probeOnce(); let flips = 0, was = w.sized()
|
||||
for (let i = 0; i < 20; i++) { t += 50; w.f.follow(); if (w.sized() !== was) { flips++; was = w.sized() } }
|
||||
if (flips !== 0 || w.sized()) fails.push(`D: a max-height host flipped ${flips} time(s) (it must be found at mount and left content-sized)`) }
|
||||
// E
|
||||
{ const w = make({ mode: 'content', content: 0 }); w.f.probeOnce(); let flips = 0, was = w.sized()
|
||||
w.mode = 'capped'; w.cap = 600
|
||||
for (let i = 0; i < 20; i++) { t += 50; w.f.follow(); if (w.sized() !== was) { flips++; was = w.sized() } }
|
||||
if (flips > 2 || w.sized()) fails.push(`E: a max-height cap that arrived after mount LOOPS or stays sized (flips ${flips}, sized ${w.sized()})`) }
|
||||
// F
|
||||
{ const w = make({ mode: 'sized', fixed: 523 }); w.f.probeOnce(); w.mode = 'content'; w.content = 0; w.f.follow()
|
||||
// with the floor off and no content the host is 0 high; follow() must bring the floor back
|
||||
if (w.sized()) fails.push('F: a sized host that collapsed to 0 did not get the floor back') }
|
||||
// G
|
||||
{ const w = make({ mode: 'content', display: 'inline' }); w.f.probeOnce(); w.writes.length = 0; w.floor = 700; w.f.follow()
|
||||
if (w.writes.length) fails.push('G: an inline host was acted on after mount') }
|
||||
return fails
|
||||
}
|
||||
|
||||
const src = readFileSync(SRC, 'utf8')
|
||||
if (process.argv.includes('--self-test')) {
|
||||
const poisons = [
|
||||
['the old per-resize probe (follow() re-measures)', src.replace(/function follow\(\) \{/, 'function follow() { probeOnce(); return;'), /^A: /],
|
||||
['no latch for a host capped after mount', src.replace('set(false); flipH = null; capped = true; return', 'set(false); flipH = null; return'), /^E: /],
|
||||
]
|
||||
let missed = 0
|
||||
for (const [name, poisoned, rx] of poisons) {
|
||||
if (poisoned === src) { console.log(`NOT MEASURED: the poison anchor for "${name}" moved`); process.exitCode = 75; continue }
|
||||
const f = await audit(poisoned); const hit = f.find((x) => rx.test(x))
|
||||
console.log(hit ? `✓ caught: ${name} -> ${hit}` : `✗ MISSED: ${name}`)
|
||||
if (!hit) missed++
|
||||
}
|
||||
if (missed) process.exitCode = 1
|
||||
} else {
|
||||
const f = await audit(src)
|
||||
if (f.length) { for (const x of f) console.log('✗ ' + x); console.log(`FAIL check:host-floor (${f.length})`); process.exitCode = 1 }
|
||||
else console.log('PASS check:host-floor — measured once at mount; host resizes never collapse the loader or move a scrolled child; a sized host fills; late sizing flips once; a max-height cap never loops; a collapsed host gets the floor back; inline hosts are left alone')
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ const QaCorner = __DOXA_QA__
|
|||
import PinHint from './components/hints/PinHint.vue'
|
||||
import LargerScreenNotice from './components/LargerScreenNotice.vue'
|
||||
import { useScreenGate } from './composables/useScreenGate.js'
|
||||
import { createHostFloor } from './elements/hostFloor.js'
|
||||
import { useUIStore } from './stores/uiStore.js'
|
||||
import { useMapStore } from './stores/mapStore.js'
|
||||
import { useDataStore } from './stores/dataStore.js'
|
||||
|
|
@ -345,55 +346,20 @@ provide('uiStore', uiStore)
|
|||
provide('mapStore', mapStore)
|
||||
provide('dataStore', dataStore)
|
||||
|
||||
// ★ THE FLOOR NEVER EXCEEDS A SIZED HOST (measured 2026-10-02 on a phone, DX Life's Home/Pray/Research). The
|
||||
// viewport floor below (min-height: 100svh, see the style block) exists for a host whose height comes from its
|
||||
// CONTENT (the scroll-page leaf, BUG-UPG100). A host that is already SIZED (DX Life's slot: a definite height,
|
||||
// overflow hidden; a tab pane) must be filled exactly: there the floor made the map taller than its host, the
|
||||
// host clipped its bottom, and the legend footer was cut off. It also tied the map's height to the window's,
|
||||
// which is what moved the map as a phone's URL bar came and went.
|
||||
// Which kind of host this is cannot be read from CSS, so it is MEASURED ONCE, at mount: with the floor at 0 and then
|
||||
// at a huge value, a sized host keeps its height and a content-sized one grows. Sized → the floor is switched off
|
||||
// (height:100% fills the host); content-sized → the floor stays. At mount the profile has not rendered yet, so the
|
||||
// probe touches no reader state.
|
||||
// ★ AFTER MOUNT, ONLY PASSIVE MEASUREMENT (2026-10-02, a regression this probe caused): re-probing on every host resize
|
||||
// briefly collapsed and expanded the loader, which clamped a scrolling child's scrollTop, and the UPG-100 MFE, whose
|
||||
// view follows its column's scroll, flipped from "Map" back to its first view whenever a phone's URL bar resized a
|
||||
// content-sized host. Later resizes now only READ sizes and flip one CSS value, never the layout around the profile:
|
||||
// - floor ON: a block host SHORTER than the floor can only be sized from outside (a content-sized host is at least
|
||||
// the floor), so the floor goes OFF. This is how a host that is styled after mount is caught;
|
||||
// - floor OFF: a block host that has collapsed to nothing has lost its size, so the floor comes back;
|
||||
// - a window-height change on a content-sized host moves the host WITH the floor, so nothing changes.
|
||||
// ★ THE FLOOR NEVER EXCEEDS A SIZED HOST, and it is decided WITHOUT disturbing live content: measured once at mount,
|
||||
// then only followed (library/elements/hostFloor.js, which says why, and check:host-floor, which pins it). The
|
||||
// viewport floor below (min-height: 100svh) is for a host whose height comes from its CONTENT; a host SIZED from
|
||||
// outside (DX Life's slot) is filled exactly, so its bottom (a map's legend) is never clipped.
|
||||
const loaderEl = ref(null)
|
||||
let hostRO = null
|
||||
const loaderHost = () => (loaderEl.value && loaderEl.value.getRootNode ? loaderEl.value.getRootNode().host : null)
|
||||
function setHostSized(el, sized) {
|
||||
el.style.minHeight = sized ? '0px' : ''
|
||||
el.toggleAttribute('data-host-sized', sized)
|
||||
}
|
||||
function probeHostOnce() {
|
||||
const el = loaderEl.value, host = loaderHost()
|
||||
if (!el || !host) return
|
||||
el.style.minHeight = '0px'
|
||||
const h0 = host.getBoundingClientRect().height
|
||||
el.style.minHeight = '100000px'
|
||||
const h1 = host.getBoundingClientRect().height
|
||||
setHostSized(el, h0 > 0 && Math.abs(h1 - h0) < 1)
|
||||
}
|
||||
function followHost() {
|
||||
const el = loaderEl.value, host = loaderHost()
|
||||
if (!el || !host) return
|
||||
const display = getComputedStyle(host).display
|
||||
if (display === 'inline' || display === 'contents') return // no box of its own to read
|
||||
const hostH = host.getBoundingClientRect().height
|
||||
if (el.hasAttribute('data-host-sized')) { if (hostH < 1) setHostSized(el, false); return }
|
||||
const floor = parseFloat(getComputedStyle(el).minHeight) || 0
|
||||
if (floor > 0 && hostH + 1 < floor) setHostSized(el, true)
|
||||
}
|
||||
onMounted(() => {
|
||||
probeHostOnce()
|
||||
const host = loaderHost()
|
||||
if (host && typeof ResizeObserver !== 'undefined') {
|
||||
hostRO = new ResizeObserver(() => followHost())
|
||||
const el = loaderEl.value
|
||||
const host = el && el.getRootNode ? el.getRootNode().host : null
|
||||
if (!el || !host) return
|
||||
const floor = createHostFloor(el, host)
|
||||
floor.probeOnce()
|
||||
if (typeof ResizeObserver !== 'undefined') {
|
||||
hostRO = new ResizeObserver(() => floor.follow())
|
||||
hostRO.observe(host)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
63
library/elements/hostFloor.js
Normal file
63
library/elements/hostFloor.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* hostFloor.js — whether a profile's viewport floor applies, decided against its HOST (library/ProfileLoader.vue).
|
||||
*
|
||||
* The loader floors every profile at min-height:100svh, for a host whose height comes from its CONTENT (the
|
||||
* scroll-page leaf; BUG-UPG100). A host that is SIZED from outside (DX Life's map slot: a definite height, overflow
|
||||
* hidden) must be filled exactly: there the floor made the map taller than its host and clipped its legend.
|
||||
*
|
||||
* ★ MEASURED ONCE, THEN ONLY FOLLOWED. Which kind of host this is cannot be read from CSS, so probeOnce() measures it
|
||||
* at mount (the floor at 0, then at a huge value: a sized host keeps its height, a content-sized one grows), before
|
||||
* the profile has rendered. It must never run again: re-measuring on every host resize collapsed the loader under
|
||||
* live content, clamped a scrolling child's scrollTop, and the embedded UPG-100 app (its view follows its column's
|
||||
* scroll) flipped from Map back to its first view whenever a phone's URL bar resized the host. After mount, follow()
|
||||
* only READS sizes and flips one value:
|
||||
* - floor on: a block host SHORTER than the floor can only be sized from outside, so the floor goes off (a host
|
||||
* styled after mount);
|
||||
* - floor off: a block host that collapsed below 1px has lost its size, so the floor comes back;
|
||||
* - a host CAPPED below the floor (max-height) follows its content up to the cap. It is found at mount (probeOnce)
|
||||
* or, if the cap arrives later, by the host shrinking right after a flip; either way it is latched and never
|
||||
* flipped again, so it cannot loop;
|
||||
* - a window-height change on a content-sized host moves the host WITH the floor: nothing changes.
|
||||
* check:host-floor pins all of this, with the old per-resize probe and a missing latch as poisons.
|
||||
*/
|
||||
export const FLIP_SETTLE_MS = 1000
|
||||
|
||||
export function createHostFloor(loader, host, { style = (n) => getComputedStyle(n), now = () => Date.now() } = {}) {
|
||||
let flipH = null, flipAt = 0, capped = false
|
||||
const isSized = () => loader.hasAttribute('data-host-sized')
|
||||
const floorPx = () => parseFloat(style(loader).minHeight) || 0
|
||||
function set(sized) {
|
||||
loader.style.minHeight = sized ? '0px' : ''
|
||||
if (sized) loader.setAttribute('data-host-sized', '')
|
||||
else loader.removeAttribute('data-host-sized')
|
||||
}
|
||||
/** At mount only, before the profile renders: nothing below the loader holds state yet. */
|
||||
function probeOnce() {
|
||||
loader.style.minHeight = '0px'
|
||||
const h0 = host.getBoundingClientRect().height
|
||||
loader.style.minHeight = '100000px'
|
||||
const h1 = host.getBoundingClientRect().height
|
||||
const sized = h0 > 0 && Math.abs(h1 - h0) < 1
|
||||
set(sized)
|
||||
if (!sized) { const f = floorPx(); if (f > 0 && h1 + 1 < f) capped = true } // grows, but stops below the floor
|
||||
}
|
||||
/** On every host resize: reads sizes, and at most flips the floor (one write); it never re-measures. */
|
||||
function follow() {
|
||||
const display = style(host).display
|
||||
if (display === 'inline' || display === 'contents') return // no box of its own to read
|
||||
const h = host.getBoundingClientRect().height
|
||||
if (isSized()) {
|
||||
// Right after a flip, a host that SHRANK followed its content (it was capped, not sized): revert and latch.
|
||||
// Checked FIRST, so a capped host whose content is empty (it shrinks to 0) is latched, not flipped back and forth.
|
||||
if (flipH !== null && now() - flipAt < FLIP_SETTLE_MS && h + 1 < flipH) { set(false); flipH = null; capped = true; return }
|
||||
if (h < 1) { set(false); flipH = null }
|
||||
return
|
||||
}
|
||||
if (capped) return
|
||||
const f = floorPx()
|
||||
if (f > 0 && h + 1 < f) { set(true); flipH = h; flipAt = now() }
|
||||
}
|
||||
return { probeOnce, follow }
|
||||
}
|
||||
|
||||
export default createHostFloor
|
||||
|
|
@ -353,8 +353,8 @@
|
|||
"dev": "bun x vite",
|
||||
"fix:sri": "node internal/fix-sri.mjs",
|
||||
"gallery": "node internal/generate-gallery.mjs",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:script-flags check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:region-paint-single-source check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:leaf-hints check:snippet-engine check:snippet-engine:self-test check:embed-base check:link-coverage check:no-public-binds check:shared-reached check:tree-weight check:no-fleet-names:self-test check:pin-hint-floor:self-test check:poster-sizes:self-test check:shell-header:self-test check:preview-flag-dev-only:self-test check:poster-type-registry:self-test",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:pins-in-state check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-internal-role-nouns check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:side-lanes check:side-lanes:self-test check:poster-section check:poster-section:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams check:poster-design-catalogue check:wall-photo-weight check:no-fleet-names check:pin-hint-floor check:poster-sizes check:upg-count check:shell-header check:pg-name-gate check:preview-flag-dev-only check:poster-type-registry",
|
||||
"gates:post": "node internal/run-parallel.mjs --label=post -- check:capability-derived check:drop check:parse verify check:sri check:public-hygiene check:label-joins check:facet-cascade check:facet-one-field check:heartbeat check:kpi-unit check:composite-reg check:descriptions check:no-abs-paths check:no-codename check:dep-names check:composite-srcs check:embed-targets check:nested-token check:i18n-parity check:tabs-localized check:packets-all check:packet-strings-parity check:zone-census check:zoning-declared check:share-policy check:library-index check:csv-shape check:capability-citations check:catalog-identity check:no-orphan-gates check:quoted-scripts check:script-flags check:capability-exemplars check:packet-fixture check:packet-browser check:provider-switch check:locale-reachable check:url-resolver check:manifest-covers check:one-palette check:pin-dim-slider check:legend-source check:clustering-modes check:composite-embed-knobs check:composite-locale-leaks check:linked-vocabulary check:wp-embed check:dashboard-geocoder-provider check:leaf-engine-per-provider check:auto-engine check:engine-contract check:map-props-unit check:composites-mount check:composite-interop check:no-free-identifiers check:map-fly-reduced-motion check:mst-clustering check:list-prop-unit check:profiles-mount check:cdn-pins check:no-eval check:popup-escaping check:i18n-treeshake check:pinned-umd check:one-palette:self-test check:no-cross-plugin-imports check:no-cross-plugin-imports:self-test check:map-attribution check:studio-rail check:approx-pin-note check:embedder-keys check:provider-for check:legend-logical-insets check:legend-logical-insets:self-test check:optional-t-equivalence check:text-highlight check:religion-label-not-value check:i18n-stopped-scope check:no-silent-engine check:no-silent-engine:self-test check:regions-uses-library check:shadow-css-parses check:square-map-controls check:preset-config-merge check:instance-config-keys check:frozen-labels check:plural-forms check:region-fill-repaint check:region-paint-single-source check:poster-print-fallback check:posters-exportable check:country-names check:reduced-motion check:no-dead-library check:universal-props check:readme-size check:tree-relative check:leaf-hints check:snippet-engine check:snippet-engine:self-test check:embed-base check:link-coverage check:no-public-binds check:shared-reached check:tree-weight check:no-fleet-names:self-test check:pin-hint-floor:self-test check:poster-sizes:self-test check:shell-header:self-test check:preview-flag-dev-only:self-test check:poster-type-registry:self-test check:host-floor:self-test",
|
||||
"gates:pre": "node internal/run-parallel.mjs --label=pre -- check:template-literal check:mjs-parse check:sfc-parse check:tab-radius check:synced-css check:doc-scripts check:setup-order check:package-json-unique-keys check:no-repo-pb-hooks check:no-credentials-omit check:no-credentials-omit:self-test check:shadow-reduced-motion check:poster-action-print check:targets check:targets:self-test check:browser-gates-guarded check:pin-selection check:facet-shape check:hardcoded-english:self-test check:list-selection check:selector-fields check:upg-zone-joins check:gospel-set-charts check:gospel-set-fit check:describe-group check:one-scroll-container check:packet-charts check:pins-in-state check:composite-token-url check:language-tree-layout check:i18n-keys-exist check:i18n-never-translated check:packet-variants check:profile-table check:element-bootstrap check:embed-url-absolute check:frame-target check:language-tree-model check:map-menu-anchor check:selection-kinds check:tiered-points check:whiteboard-projection check:mercator-slice check:ring-vocabulary check:map-props-unit check:mobile-gating check:provide-shadowing check:legend-top-offset check:cdn-cache check:plural-forms check:select-zone-race check:script-registry check:map-control-overlap check:poster-attribution check:board-map-options check:gospel-whiteboard check:packet-map-missing-loud check:poster-layout-optin check:ported-globals-identical check:ring-bands check:packet-a1-structure check:india-states check:batch-selection-export check:zone-title-tier-a check:evangelical-share check:map-labels check:packet-offline check:help-reachable check:gated-not-in-tree check:window-globals-released check:legend-data-contract check:no-internal-role-nouns check:poster-fetch-guard check:people-group-records check:sentence-slots check:popup-href-escaped check:popup-pan-controls check:country-map-refetch check:pane-config-reforward check:viewport-meta check:viewport-meta:self-test check:label-key-coverage check:poster-browser-equiv check:poster-render-pinned check:country-sheet-one-path check:country-sheet-one-path:self-test check:side-lanes check:side-lanes:self-test check:poster-section check:poster-section:self-test check:posterverse-shelf check:host-locale-fetch check:print-frame-reclaim check:click-sheet check:coverage-consistent:chained check:coverage-consistent:poison check:dashboard-filter-a11y check:filter-multivalue check:hover-channel check:kit-options-reachable check:layout-key-namespaced check:legend-mirrors check:map-unmount-race check:no-conflict-markers check:series-isolation check:snapshot-guard check:stale-sweep-claim check:surfaces-read-the-tier check:translator-contacts check:wall-wysiwyg check:wall-print-watch check:wall-no-bg-photos check:wall-pan check:template-i18n check:list-pattern-doc check:shadow-style-labels-unique check:grid-image-loading check:retry-fetch check:panel-sheets check:poster-wall-host-seams check:poster-design-catalogue check:wall-photo-weight check:no-fleet-names check:pin-hint-floor check:poster-sizes check:upg-count check:shell-header check:pg-name-gate check:preview-flag-dev-only check:poster-type-registry check:host-floor",
|
||||
"gen:iso3-to-alpha2": "node library/scripts/generate-iso3-to-alpha2.mjs",
|
||||
"internet:adm1": "node plugin-designs/cities-internet/scripts/build-adm1-internet.mjs",
|
||||
"library-index": "node internal/generate-library-index.mjs",
|
||||
|
|
@ -426,7 +426,9 @@
|
|||
"check:side-lane-photo-pixels": "node plugin-designs/country-posters/scripts/check-side-lane-photo-pixels.mjs",
|
||||
"check:side-lane-photo-pixels:self-test": "node plugin-designs/country-posters/scripts/check-side-lane-photo-pixels.mjs --self-test",
|
||||
"check:poster-type-registry": "node internal/check-poster-type-registry.mjs",
|
||||
"check:poster-type-registry:self-test": "node internal/check-poster-type-registry.mjs --self-test"
|
||||
"check:poster-type-registry:self-test": "node internal/check-poster-type-registry.mjs --self-test",
|
||||
"check:host-floor": "node internal/check-host-floor.mjs",
|
||||
"check:host-floor:self-test": "node internal/check-host-floor.mjs --self-test"
|
||||
},
|
||||
"//scripts": {
|
||||
"_": "What each script does. These notes used to sit inside the command strings as trailing \"# …\" comments, which made the shell discard any arguments passed after \"--\" (so a gate given --url silently measured its hardcoded default port — possibly another agent's server). Keep notes here, never in a command.",
|
||||
|
|
@ -700,6 +702,7 @@
|
|||
"check:poster-section:self-test": "a registry without 'country', a region key without \"&\" → \"and\", and a section that uses a name it does not inject must each make check:poster-section fail.",
|
||||
"check:side-lane-photo-pixels": "BROWSER gate (browser seat; elsewhere 75 NOT MEASURED): the 60x96 side-lane sheet rasterised by poster-raster.js, measured in pixels at every photo slot. A data: photo draws; every photo on its own host stays flat, never the broken-image glyph an external <image> rasterises as.",
|
||||
"check:side-lane-photo-pixels:self-test": "the same raster WITHOUT stripExternalImages must leave the external photo slots NOT flat (the glyph), else the gate proves nothing.",
|
||||
"check:host-floor": "the profile floor (min-height 100svh) is decided against the HOST without disturbing live content (library/elements/hostFloor.js, used by ProfileLoader): measured ONCE at mount; after that host resizes never write the loader's min-height nor move a scrolled child's scrollTop (the regression: a per-resize probe reset the embedded UPG-100 app from Map to its first view); a sized host is filled; a host sized after mount flips once; a max-height cap never loops; a collapsed host gets the floor back; an inline host is left alone. Stub DOM, headless. Run the `check:host-floor:self-test` script for the NEGATIVE test (the old per-resize probe and a missing latch).",
|
||||
"check:poster-type-registry": "the poster type registry is ONE per page: every bundle inlines its own copy of posterTypes.js and <dxm-poster-section> is defined by whichever bundle loads first, so with two independent copies loaded side by side a type registered through one is found through the other; the built-in country is listed once; a name registered twice keeps the first; every registration is announced on window (dxm-poster-type-registered) and PosterSectionView re-resolves on it. Run the `check:poster-type-registry:self-test` script for the NEGATIVE test (a per-bundle registry fails)."
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue