Caption: one helper for the condition; arm M checks the frame class; a self-test proves it
From studio's review: PosterSection decides the caption with one helper, captioned(item), for both the frame's has-caption class and the caption node, so the two cannot drift. Arm M now also asserts the frame class (no has-caption when off or pending, has-caption on a drawn card when on). check:poster-wall-host-seams:self-test (in gates:pre) compiles four mutants of PosterSection.vue in memory through the gate's loader (a caption with the option off, never shown, a has-caption frame when off, a caption while pending) and each must fail arm M; the real file passes after. Source only: the bundles that compile PosterSection are rebuilt in the next commit, after the heavy slot reopens.
This commit is contained in:
parent
b3cbc8735d
commit
f0185abaf4
3 changed files with 56 additions and 14 deletions
|
|
@ -109,6 +109,10 @@ const builtScriptUrl = {
|
|||
// in the build. The gate runs Vite without the Vue plugin, so without this hook a child .vue import fails to LOAD and
|
||||
// the gate goes red on correct code. Arm L proves the hook with a parent/child fixture.
|
||||
const CLIENT = '?gate-client'
|
||||
// --self-test: a file's source replaced in memory (a mutant), so an arm can be shown to FAIL on broken code.
|
||||
const SOURCE_OVERRIDES = new Map()
|
||||
const SELF_TEST = process.argv.includes('--self-test')
|
||||
const SECTION = join(ROOT, 'library/components/poster/PosterSection.vue')
|
||||
const clientCompile = {
|
||||
name: 'gate-client-sfc',
|
||||
// 'pre': Vite's own resolver would otherwise resolve a relative './Child.vue' first and load it raw.
|
||||
|
|
@ -124,7 +128,7 @@ const clientCompile = {
|
|||
if (!id.endsWith(CLIENT)) return null
|
||||
const file = id.slice(0, -CLIENT.length)
|
||||
const sfc = await import('@vue/compiler-sfc')
|
||||
const { descriptor, errors } = sfc.parse(readFileSync(file, 'utf8'), { filename: file })
|
||||
const { descriptor, errors } = sfc.parse(SOURCE_OVERRIDES.get(file) ?? readFileSync(file, 'utf8'), { filename: file })
|
||||
if (errors.length) throw new Error(`${relative(ROOT, file)} did not parse: ${errors[0].message}`)
|
||||
return sfc.compileScript(descriptor, { id: `gate-${basename(file, '.vue')}`, inlineTemplate: true }).content
|
||||
},
|
||||
|
|
@ -174,7 +178,9 @@ try {
|
|||
parentNode: (n) => n.parent, nextSibling: (n) => { const p = n.parent; return p ? p.children[p.children.indexOf(n) + 1] || null : null },
|
||||
patchProp: (el, key, _prev, next) => { el.props[key] = key === 'class' ? classStr(next) : next },
|
||||
})
|
||||
const PosterWall = (await server.ssrLoadModule('virtual:poster-wall-client')).default
|
||||
let PosterWall = (await server.ssrLoadModule('virtual:poster-wall-client')).default
|
||||
// Reload PosterWall after SOURCE_OVERRIDES changed (the module graph caches the compiled files).
|
||||
async function reloadWall() { server.moduleGraph.invalidateAll(); PosterWall = (await server.ssrLoadModule('virtual:poster-wall-client')).default }
|
||||
const item = { id: 'gate-item', label: 'Gate Item', count: 7 }
|
||||
|
||||
async function mountWall(extra, items = [item]) {
|
||||
|
|
@ -390,23 +396,52 @@ try {
|
|||
app.unmount()
|
||||
}
|
||||
|
||||
// M: captionUnderPreview. OFF (the default): a drawn card shows only its picture, as every wall always has. ON: a
|
||||
// pending card is not captioned (its placeholder already names it), and the drawn card names itself under the picture.
|
||||
{
|
||||
// M: captionUnderPreview. OFF (the default): a drawn card shows only its picture and its frame has no has-caption,
|
||||
// as every wall always has. ON: a pending card is not captioned (its placeholder already names it), and the drawn card
|
||||
// names itself under the picture in a has-caption frame. A function, so --self-test can run it against mutants.
|
||||
async function armM() {
|
||||
const fs = []
|
||||
const items = [{ id: 'm1', label: 'Mone', count: 1 }]
|
||||
const ps = () => Promise.resolve('blob:gate-m')
|
||||
for (const on of [false, true]) {
|
||||
const w = await mountWall({ previewSrc: ps, instanceId: `gate-m-${on ? 'on' : 'off'}`, ...(on ? { captionUnderPreview: true } : {}) }, items)
|
||||
const w = await mountWall({ previewSrc: ps, instanceId: `gate-m-${on ? 'on' : 'off'}-${Math.random()}`, ...(on ? { captionUnderPreview: true } : {}) }, items)
|
||||
await settle()
|
||||
const caps = () => { const out = []; walk(w.root, (n) => { if (hasClass(n, 'cap')) out.push(n) }); return out }
|
||||
if (caps().length) fail(`M: a PENDING card must not be captioned (${on ? 'on' : 'off'})`)
|
||||
const all = (cls) => { const out = []; walk(w.root, (n) => { if (hasClass(n, cls)) out.push(n) }); return out }
|
||||
if (all('cap').length) fs.push(`M: a PENDING card must not be captioned (${on ? 'on' : 'off'})`)
|
||||
if (all('has-caption').length) fs.push(`M: a PENDING card's frame must not be has-caption (${on ? 'on' : 'off'})`)
|
||||
const ph = phFor(w.root, 'Mone'); if (ph) inView(ph)
|
||||
await settle(); await settle()
|
||||
if (imgs(w.root).length !== 1) fail(`M: the card should be drawn (${on ? 'on' : 'off'}), got ${imgs(w.root).length} img`)
|
||||
if (!on && caps().length) fail('M: with captionUnderPreview OFF (the default) a drawn card must show no caption')
|
||||
if (on && (caps().length !== 1 || textOf(caps()[0]).trim() !== 'Mone')) fail(`M: with captionUnderPreview ON the drawn card should be captioned "Mone", got ${JSON.stringify(caps().map((c) => textOf(c)))}`)
|
||||
if (imgs(w.root).length !== 1) fs.push(`M: the card should be drawn (${on ? 'on' : 'off'}), got ${imgs(w.root).length} img`)
|
||||
if (!on && all('cap').length) fs.push('M: with captionUnderPreview OFF (the default) a drawn card must show no caption')
|
||||
if (!on && all('has-caption').length) fs.push('M: with captionUnderPreview OFF (the default) no frame may be has-caption')
|
||||
if (on && (all('cap').length !== 1 || textOf(all('cap')[0]).trim() !== 'Mone')) fs.push(`M: with captionUnderPreview ON the drawn card should be captioned "Mone", got ${JSON.stringify(all('cap').map((c) => textOf(c)))}`)
|
||||
if (on && all('has-caption').length !== 1) fs.push(`M: with captionUnderPreview ON the drawn card's frame should be has-caption, got ${all('has-caption').length}`)
|
||||
w.app.unmount()
|
||||
}
|
||||
return fs
|
||||
}
|
||||
;(await armM()).forEach(fail)
|
||||
|
||||
if (SELF_TEST) {
|
||||
const real = readFileSync(SECTION, 'utf8')
|
||||
const mutate = (from, to) => { if (!real.includes(from)) throw new Error(`self-test: PosterSection.vue no longer contains ${JSON.stringify(from)}; update the mutant`); return real.replace(from, to) }
|
||||
const mutants = [
|
||||
['a caption shown with the option off', mutate('captionUnderPreview.value === true && ', '')],
|
||||
['a caption never shown', mutate('<div v-if="captioned(item)" class="cap">', '<div v-if="false" class="cap">')],
|
||||
['a has-caption frame with the option off', mutate(":class=\"{ 'has-caption': captioned(item) }\"", ":class=\"{ 'has-caption': true }\"")],
|
||||
['a caption on a card still pending', mutate('\n && !(previewSrc.value && !generated.value[item.id])', '')],
|
||||
]
|
||||
let missed = 0
|
||||
for (const [name, src] of mutants) {
|
||||
SOURCE_OVERRIDES.set(SECTION, src); await reloadWall()
|
||||
const fs = await armM()
|
||||
console.log(fs.length ? ` ✓ caught: ${name} -> ${fs[0]}` : ` ✗ MISSED: ${name}`)
|
||||
if (!fs.length) missed++
|
||||
}
|
||||
SOURCE_OVERRIDES.delete(SECTION); await reloadWall()
|
||||
if ((await armM()).length) { console.log(' ✗ arm M fails on the real PosterSection.vue after the mutants'); missed++ }
|
||||
console.log(missed ? `POSTER-WALL-HOST-SEAMS SELF-TEST: FAIL (${missed})` : `POSTER-WALL-HOST-SEAMS SELF-TEST: PASS — ${mutants.length} mutants of PosterSection.vue each fail arm M`)
|
||||
if (missed) process.exitCode = 1
|
||||
}
|
||||
|
||||
// L: the gate loads a component that renders a child .vue (internal/fixtures/poster-wall-host-seams/)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@ defineProps({
|
|||
const {
|
||||
captionUnderPreview, collapsible, columnsControl, headerAction, hidePrayerListWhenSoon, instanceId, previewSrc, sectionHeads, unit, unsupported, PER_ROW_AUTO, T, canPrintRows, countOf, deckKey, deckOpenFor, emit, exportPoster, generated, gridStyle, hud, isCollapsed, lastShown, missing, onPreviewError, packetVariants, perRowChoices, perRowShown, pickedStock, previewIsControl, previewUrl, printDeckAt, printPacketAs, sectionCount, setPreviewEl, showOnMap, stockLabel, stockOptions, t, toggleDeck, toggleSection,
|
||||
} = inject(POSTER_WALL_CTX)
|
||||
// captionUnderPreview: a DRAWN card (not withheld, not missing, not pending) keeps its title under the picture. One
|
||||
// helper for the frame's class and the caption node, so the two never disagree.
|
||||
// (In script the injected names are refs: the props through toRefs, missing / generated as ref({}).)
|
||||
const captioned = (item) => captionUnderPreview.value === true && !unsupported.value(item) && !missing.value[item.id]
|
||||
&& !(previewSrc.value && !generated.value[item.id])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -82,7 +87,7 @@ const {
|
|||
<span class="hlabel">{{ t(headerAction.labelKey) }}</span>
|
||||
</button>
|
||||
|
||||
<div class="main" :class="{ 'has-caption': captionUnderPreview && !unsupported(item) && !missing[item.id] && !(previewSrc && !generated[item.id]) }">
|
||||
<div class="main" :class="{ 'has-caption': captioned(item) }">
|
||||
<!-- Unsupported comes FIRST: there is no poster to try to load. -->
|
||||
<div v-if="unsupported(item)" class="ph is-soon">
|
||||
<div class="ph-label">{{ item.label }}</div>
|
||||
|
|
@ -123,7 +128,7 @@ const {
|
|||
<div class="ph-note">{{ T('poster.preview_unavailable', 'preview unavailable') }}</div>
|
||||
</div>
|
||||
<!-- captionUnderPreview (opt-in): a DRAWN card keeps its title under the picture, inside the frame. -->
|
||||
<div v-if="captionUnderPreview && !unsupported(item) && !missing[item.id] && !(previewSrc && !generated[item.id])" class="cap">{{ item.label }}</div>
|
||||
<div v-if="captioned(item)" class="cap">{{ item.label }}</div>
|
||||
|
||||
<!-- ★ THE PRINT ROW LIVES INSIDE .main, NOT BESIDE IT.
|
||||
It is `position:absolute; top:100%`, which resolves against the nearest
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@
|
|||
"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 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-sections check:poster-sections:self-test check:poster-design-catalogue check:poster-design-catalogue:self-test 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",
|
||||
"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-wall-host-seams:self-test check:poster-sections check:poster-sections:self-test check:poster-design-catalogue check:poster-design-catalogue:self-test 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",
|
||||
|
|
@ -418,6 +418,7 @@
|
|||
"check:board-map-options": "node internal/check-board-map-options.mjs",
|
||||
"check:panel-sheets": "node internal/check-panel-sheets.mjs",
|
||||
"check:poster-wall-host-seams": "node internal/check-poster-wall-host-seams.mjs",
|
||||
"check:poster-wall-host-seams:self-test": "node internal/check-poster-wall-host-seams.mjs --self-test",
|
||||
"check:shell-header:self-test": "node internal/check-shell-header.mjs --self-test",
|
||||
"check:shell-header": "node internal/check-shell-header.mjs",
|
||||
"check:side-lanes": "node plugin-designs/country-posters/scripts/check-side-lanes.mjs",
|
||||
|
|
@ -700,6 +701,7 @@
|
|||
"check:control-inset": "bottomControlInset MEASURES the map's real bottom control band, so the pan reserves the band the controls actually occupy. check:popup-pan-controls proves the ARITHMETIC; this proves the band is real. If the inset silently returned 0 -- a selector typo, an engine renaming its control classes, a control moving outside the container -- panPlanFor would get bottomInset 0, behave exactly as it did before the fix, and check:popup-pan-controls would still pass every case, because its arithmetic is still correct: the bug back with both gates green. 8 cases against a real DOM, running the SHIPPED bottomControlInset read out of the SFC (it is not exported), never a copy: the band equals what the DOM independently says (not merely non-zero); no controls reserves 0; a zero-sized control reserves nothing; the taller of two corners wins; a wrapped attribution reserves more than one line; a control outside the container is ignored; a null container returns 0; and the MAPBOX corners count too, since the component takes a provider prop and every other case renders MapLibre names. Needs a browser: every assertion rests on getBoundingClientRect. 75 = no slot, never a pass.",
|
||||
"check:panel-sheets": "the shared panel geometry (library/components/poster/panelSheets.js) for posters printed on several sheets: 60x96 = 3 × 60×36 stacked and 66x66 = 2 × 36×66 side by side, 6 in joins, and a single sheet; the sheets tile the finished poster, glue slices tile and bleed slices repeat exactly the join, a bleed join prints a guide not a solid band, a screen preview always shows the red band, and the Bless 5x8ft takes its bands and marks from the module. Two-sided (4 planted defects). Headless.",
|
||||
"check:poster-wall-host-seams": "PosterWall's two host seams, and that with neither set every wall behaves as before. headerAction: default null is the Show-on-map TOGGLE (aria-pressed, one window event per click, is-last, no emit); { labelKey, ariaKey } is a plain button that emits header-action with the item, no window event, no aria-pressed, and reads the label alone for an item with no count. previewSrc: default null is the stored previews/<id>.webp and no IntersectionObserver; set, a card renders only once in view of the wall's own scroller, one render at a time, cached per page, and null / a throw / a timeout ends on the preview-unavailable placeholder. Image errors: a default wall retries the tree's own copy before the placeholder; a generated preview goes straight to it. Section heads: a section with people reads \"<n> people groups · <items>\" exactly as before; one with none reads its items alone. sectionItemCountKey lets a wall count one section in its own words ({ key, n }); unnamed sections keep the default. The gate client-compiles PosterWall and any .vue it imports (a child section component); arm L proves a child .vue loads and renders (fixtures in internal/fixtures/poster-wall-host-seams/). Arm M: captionUnderPreview off (the default) leaves a drawn card as only its picture; on, a pending card is uncaptioned and the drawn card is captioned with its label. Headless: PosterWall compiled as a client component on a recording renderer; real onClick and a recording IntersectionObserver. 19 mutants each FAIL.",
|
||||
"check:poster-wall-host-seams:self-test": "four mutants of PosterSection.vue (a caption with the option off, a caption never shown, a has-caption frame with the option off, a caption on a pending card), each compiled in memory through the gate's loader, must each make arm M fail; the real file passes after.",
|
||||
"check:shell-header": "a header-off shell has ONE way to its tabs and ONE language menu, in the right place for the width: VerticalTabsLayout renders the phone bar (☰ + #phone-end) only when narrow AND header-off, #rail-end only when wide AND header-off, and no corner circle; ShellLanguageMenu's pick dispatches exactly one cancelable, composed, bubbling doxa-language-request (detail.language), does not switch when a host cancels, and writes ?lang= only on a tree-owned page; every switcher language has a catalogue and the current language is always listed. Headless (Vite SSR). Run the `check:shell-header:self-test` script for the NEGATIVE test (11 planted defects)",
|
||||
"check:side-lanes": "the large side-lane sheets (60x96, 66x66, 36x60) through the real poster-render.js, on a SEEDED worst case (855 groups, stacked duplicates, tight clusters, real and placeholder photos): every name drawn exactly once, leader crossings 0 (re-measured from the SVG, and equal to the renderer's own count), no overflow rows, photos only where a real one exists and the format allows them.",
|
||||
"check:side-lanes:self-test": "a poster-render.js copy WITHOUT the lane clamp must make check:side-lanes' crossing arm FAIL on the same seeded data (it does: 36 crossings), so the arm is shown able to fail.",
|
||||
|
|
|
|||
Loading…
Reference in a new issue