From ea9211a5f954101b3f354b7fa97acb8a541aedc6 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Wed, 30 Sep 2026 21:12:22 +0000 Subject: [PATCH 1/4] seriesFor: India (sheet null) is its own 1-of-1 series seriesFor skipped any country without a sheet size, so India's packet page 1 from the Regions tab printed "map 1 of 0", while the Country Posters wall's own inline copy printed "map 1 of 1". A null sheet is now a tier like any other. A country absent from the list (a host's code with no poster) still gets no series, so the renderer keeps its default rather than a made-up position. --- library/packet/countrySheet.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/library/packet/countrySheet.js b/library/packet/countrySheet.js index c0243e71f..65b89377a 100644 --- a/library/packet/countrySheet.js +++ b/library/packet/countrySheet.js @@ -1,7 +1,8 @@ /** * countrySheet.js — a country's map sheet, GENERATED in the browser at the click. * - * SHARED (@map/packet/countrySheet.js). Its caller is the regions dashboard's regions-list. + * SHARED (@map/packet/countrySheet.js). Its callers are the regions dashboard's regions-list and the + * Country Posters wall (country-poster-wall.vue), which used to define its own inline copy. * * ⚠ THE MOVE HERE WAS NEVER FINISHED, and it cost a real defect. This module's comment used to claim * two consumers -- the country-poster wall and -- and a CENSUS of the tree found @@ -14,7 +15,8 @@ * `country` without a region -- so countryRegionColor(undefined) returned the old flat teal and * warned. The same country then printed HUED from Country Posters and TEAL from the Regions tab, * which is exactly the "two different posters for one country" the hue change exists to refuse. - * The copy is deleted and regions-list imports this. Do not re-copy it: a duplicate of this file is + * The copy is deleted, and regions-list and the wall both import this (check:country-sheet-one-path + * refuses a caller that draws a country sheet any other way). Do not re-copy it: a duplicate of this file is * indistinguishable from it until one of them is edited, and then it is a silent divergence. * * The print standard is generate, store only previews: no country sheet is shipped, and none should @@ -52,12 +54,16 @@ * not introduced by the one-definition change: the plugin-local copy regions-list used before it * passed no series either. The wall was never affected because it computes its own. * - * Returns undefined when the item has no sheet tier, so a row without a poster keeps the renderer's - * own default rather than a fabricated position. + * Returns undefined when the item is not in the list (a host's country with no poster), so that row + * keeps the renderer's own default rather than a fabricated position. + * + * A null sheet is a tier too: India (sheet null, too many groups for any paper size) is its own + * 1-of-1 series. Skipping it printed "map 1 of 0" on India's packet page 1 from the Regions tab + * while the wall's own copy printed "map 1 of 1" -- the two paths disagreeing again. */ export function seriesFor(countries, item) { - if (!item || !item.sheet || !Array.isArray(countries)) return undefined - const tier = countries.filter((x) => x.sheet === item.sheet) + if (!item || !Array.isArray(countries)) return undefined + const tier = countries.filter((x) => (x.sheet ?? null) === (item.sheet ?? null)) const index = tier.findIndex((x) => x.id === item.id) + 1 if (!index) return undefined return { totalGroups: tier.reduce((a, x) => a + (x.count || 0), 0), totalLists: tier.length, index } From ffbb07e4a4636bb88f2d390ef23a879f0ef927f4 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Wed, 30 Sep 2026 21:12:23 +0000 Subject: [PATCH 2/4] Country Posters wall draws its sheets through makeCountrySheet The wall had its own inline wrapper around generateCountrySheet, a second copy of @map/packet/countrySheet.js, which the Regions tab uses. Two copies of one wrapper drifted twice (a teal Regions print, then India's series). The wall now calls makeCountrySheet with the same country list, still handing it the data's own English record rather than the localized card. Behaviour: identical sheets for every country with records; a country whose records come back empty now yields no sheet (the wall falls through to its stored preview), as the Regions tab already did. check:country-sheet-one-path (in gates:pre, with a self-test) parses every plugin source: a direct generateCountrySheet call fails, every makeCountrySheet call must pass the series list, both known callers must be found, and seriesFor gives named answers over the real data (India 1 of 1, each 3x3 country N of 173, an absent code no series). It fails on development c4655072a for all three defects. --- package.json | 6 +- .../country-posters/country-poster-wall.vue | 26 ++- .../scripts/check-country-sheet-one-path.mjs | 153 ++++++++++++++++++ 3 files changed, 170 insertions(+), 15 deletions(-) create mode 100644 plugin-designs/country-posters/scripts/check-country-sheet-one-path.mjs diff --git a/package.json b/package.json index 6da27b941..da5017638 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,8 @@ "check:composite-token-url": "node internal/check-composite-token-url.mjs", "check:composites-mount": "node internal/check-composites-mount.mjs", "check:country-names": "node internal/check-country-names.mjs", + "check:country-sheet-one-path": "node plugin-designs/country-posters/scripts/check-country-sheet-one-path.mjs", + "check:country-sheet-one-path:self-test": "node plugin-designs/country-posters/scripts/check-country-sheet-one-path.mjs --self-test", "check:coverage-consistent": "node internal/check-gospel-coverage-consistent.mjs", "check:coverage-consistent:chained": "VERIFY_MAX_AGE_DAYS=36500 node internal/check-gospel-coverage-consistent.mjs", "check:coverage-consistent:poison": "node internal/check-gospel-coverage-consistent.mjs --poison", @@ -344,7 +346,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:done-evidence check:script-flags check:backlog-status 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:backlog-refs 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", - "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: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: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-new-driver-references 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: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:dxlife 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", + "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: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: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-new-driver-references 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: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:dxlife 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", "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", @@ -446,6 +448,8 @@ "check:composite-token-url": "a composite embedded BARE (no outer ProfileLoader) must honour a tokenUrl-only config: it reads tokenUrl, forwards it, and asks the token service only when no token is given. The expectation is derived from ProfileLoader, not restated. Headless; in gates:pre.", "check:composites-mount": "every capability-4/5 composite's OWN setup() and mounted hooks run without throwing, in en and ar, headless (no browser): vite loads each composite as its bundle entry defines it (configureApp, and ProfileLoader's provides where the entry mounts ProfileLoader), and @vue/runtime-core mounts it on a no-op renderer. Exists because 08f89f55 shipped two composites calling getRuntimeLocale/detectLocale/RTL_LOCALES with no import: the build was green, setup threw a ReferenceError in every locale, and Big Picture and Simple Research rendered nothing. Proves setup runs; cannot prove the children render.", "check:country-names": "every ISO 3166-1 alpha-3 our data uses must resolve to a localized country name. Guards the JOIN, not Intl: library/data/iso3-to-alpha2.json drifting from library/data/countries.json makes a country render as a bare code, which still looks like a value. Checks en/ar/zh (ar is our only RTL locale; zh makes an untranslated Latin code unmistakable) and asserts BOTH directions of drift. Carries two negative arms — an unassigned code and a malformed one — and FAILS if either resolves, because a coverage count cannot otherwise tell a hit from a lookup that returns its input.", + "check:country-sheet-one-path": "a country sheet is drawn through ONE path, @map/packet/countrySheet.js, so one country cannot print two posters. Parses (comments cannot count) every plugin source naming generateCountrySheet/makeCountrySheet: a direct generateCountrySheet in code FAILS, every makeCountrySheet call must pass `countries` (the series list), and both known callers (the Country Posters wall, regions-list) must be found. Also runs seriesFor over the real country-posters.json with named answers: India 1 of 1, every 3x3 country N of the 3x3 tier with no repeat, a code absent from the data no series.", + "check:country-sheet-one-path:self-test": "plants each defect check:country-sheet-one-path guards (a direct generateCountrySheet call, makeCountrySheet without countries, the old guard that skipped India, a position given to an absent code) and requires each to FAIL; a comment naming generateCountrySheet must NOT.", "check:coverage-consistent": "the gospel-resources surfaces must agree: no resource on an ISO RESERVED code (und/mul/mis/zxx), every resourced code NAMED, table reach recounted off the pins, no invented URL, tiers reconcile. Static, sub-second. Self-test: `bun run check:coverage-consistent:poison`, all 12 clauses (A-L) must fail. Clause L is the one that is not about consistency: it fails when the link-verification record goes stale, because 'link checked' is a claim about the present tense. Written after the same drift defect was found by hand three times.", "check:csv-shape": "the export must OPEN as a table: header on line 0, every row matching. Run the `check:csv-shape:self-test` script for the NEGATIVE test (forces the provenance comment back to line 0 IN MEMORY and requires this gate to REJECT it) — the header used to claim 'NEGATIVE-TESTED' with no way to re-run it.", "check:dashboard-filter-a11y": "the dashboard's cross-filter is operable and audible: every filter chip is a