Country posters print their list, deck and packet, with the map drawn at the click

The regions list showed a poster per country and could print the poster alone.
Supplying a records provider is what makes the Prayer List, Card Deck and Prayer
Packet buttons appear at all -- PosterWall hides them when a wall cannot supply
rows, rather than failing at click time -- and this wall never supplied any.

Records come from the shared provider, not a copy of anyone's fetch. It caches
one request per language and every country in every region shares it, so opening
five regions is still one request, and the same rows feed the sheet, so the
printed map and the printed list cannot disagree about which groups are in a
country.

Page 1 is GENERATED, because there is nothing to load. No country sheet ships:
data/posters holds 28 .html and all 28 are zones, and emit-countries.mjs writes
only country-posters.json, whose `sheet` field is a paper size rather than a
filename. So the packet's default lift asked for posters/country-<iso3>.html and
404ed on all 175. country-sheet-print.js draws it instead, in the same shape as
the zone sheet next door so the two surfaces cannot draw the same sheet two
ways, and returns '' when there is no country or no rows so the caller falls
through rather than printing an empty country.

There is ONE loading path for the generator, not two: this calls
generateCountrySheet, and posterSheets resolves the served, sha384-pinned
poster-render.js itself. Nothing here fetches or imports the generator directly.

MEASURED against the development baseline, both built here: 1044 -> 1061 KB,
+17 KB. An earlier measurement of this same wiring on the pre-pinned base read
+208 KB; almost all of that was the printing subsystem that is now served rather
than bundled, so the cost fell by about 190 KB without this file changing.
createPosterGenerator does not appear in the bundle.

item-count-key replaces nothing: the noun pair still names the section, and the
count now goes through the plural-aware key.
This commit is contained in:
driver 2026-09-29 07:27:50 +00:00
parent d9e9aa448c
commit e4aac1cd65
8 changed files with 196 additions and 124 deletions

View file

@ -67,6 +67,8 @@ import { DXLIFE_TOKENS } from './dxlife-tokens.js'
import PosterWall from '@map/components/poster/PosterWall.vue'
import RegionSectionCard from '@map/components/regions/RegionSectionCard.vue'
import posterData from '@map/data/country-posters.json'
import { createPeopleGroupRecords } from '@map/lists/peopleGroupRecords.js'
import { makeCountrySheet } from './src/country-sheet-print.js'
import { resolveAssetsBase } from '@map/utils/embedBase.js'
import { onBeforeUnmount } from 'vue'
import { cyrillicHeading } from '@map/styles/portedGlobals.js'
@ -277,6 +279,25 @@ const POSTER_EVENT = 'doxa-regions:show-country-on-map'
// poster events.
const instanceId = computed(() => cfg.value.instanceId || 'regions-list')
const posterAssetsBase = computed(() => resolveAssetsBase(cfg.value))
// ★ THE PRINT ROWS, AND THE SHEET DRAWN FROM THEM. Supplying `records` is what makes the Prayer
// List, Card Deck and Prayer Packet buttons appear on each country (PosterWall hides them when a
// wall cannot supply rows, rather than failing at click time). One fetch per language, cached in
// the provider and shared by every country in every region, so opening five regions is still one
// request. The same provider feeds the sheet, so the printed map and the printed list can never
// disagree about which groups are in a country.
const countryRecords = createPeopleGroupRecords({
apiBase: () => prayBaseUrl.value,
lang: () => languageCode.value,
profileConfig: injectedCfg,
})
// Page 1 of a country packet, and the country poster, drawn at the click. Nothing is stored: no
// country sheet ships, and the packet's default lift would ask for posters/country-<iso3>.html and
// 404 on all 175.
const countrySheet = makeCountrySheet({
records: countryRecords,
assetsBase: () => posterAssetsBase.value,
})
const openPosters = ref(new Set())
function togglePosters(region) {
@ -527,6 +548,10 @@ useShadowStyles(
unit="UPG"
:item-noun="t('Country')"
:item-noun-plural="t('Countries')"
item-count-key="regions.countryCount"
:records="countryRecords ? (item) => countryRecords({ type: 'country', values: [item.iso3] }) : null"
:sheet="countrySheet"
:card-stocks="true"
:collapsible="false"
:embedded="true"
:section-heads="false"

View file

@ -0,0 +1,47 @@
/**
* country-sheet-print.js — a country's map sheet, GENERATED in the browser at the click.
*
* The print standard is generate, store only previews: no country sheet is shipped, and none should
* be. MEASURED before this existed: data/posters holds 28 .html sheets and all 28 are ZONES, while
* emit-countries.mjs writes only country-posters.json, whose `sheet` field is a PAPER SIZE ('3x3')
* rather than a filename. So a packet's page-1 lift for a country asked for posters/country-<iso3>.html
* and got a 404 for all 175 of them. This draws it instead.
*
* Same shape as my-upg-100-list's zone sheet (data/zone-sheet-print.js), deliberately, so the two
* surfaces cannot draw the "same" sheet two ways:
*
* const sheet = makeCountrySheet({ records, assetsBase: () => base })
* usePosterPrints({ …, sheet }) // (item, format) -> html
* <PosterWall :sheet="sheet" />
*
* The country case is simpler than the zone case: generateCountrySheet resolves the zone and its
* geometry from the country entry itself, so there is no assignment step to keep in sync.
*/
/**
* @param {object} o
* records (selector) => Promise<rows[]> the shared provider; this asks it for ONE country
* assetsBase () => string where boundaries/ is served from
* @returns {(item, format) => Promise<string>} the sheet's HTML, or '' when it cannot be drawn
*/
export function makeCountrySheet({ records, assetsBase }) {
return async function countrySheet(item, format) {
const iso3 = item && item.iso3
if (!iso3) return '' // a selection or a row without a country: no sheet
// Lazy, like the zone sheet: the generator and the boundary loader are only pulled when a
// reader actually prints, so they stay out of every wall's initial download.
const { generateCountrySheet } = await import('@map/packet/posterSheets.js')
const [{ ISO3_NAME }, rows] = await Promise.all([
import('@map/data/iso3-name.js'),
records({ type: 'country', values: [iso3] }),
])
if (!rows || !rows.length) return '' // nothing to pin: fall through rather than draw an empty country
return generateCountrySheet({
country: { id: item.id, iso3, label: item.label, count: item.count },
records: rows,
assetsBase: assetsBase(),
ISO3_NAME,
format,
})
}
}

View file

@ -1108,7 +1108,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-Gebk9HH2qADRSOSKhxgGSciKWLXgbBh2JNFsB3N007sWLoMHxY9CwRYAxVFUvozk",
"integrity": "sha384-1kmiK1VZqbW7bUbqab71ewuOPVx8D7kIANyEmqFQeWncJDUqKsoaXSJOYRe+TLL0",
"elements": [
"regions-dashboard"
],
@ -1131,7 +1131,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-Gebk9HH2qADRSOSKhxgGSciKWLXgbBh2JNFsB3N007sWLoMHxY9CwRYAxVFUvozk\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-1kmiK1VZqbW7bUbqab71ewuOPVx8D7kIANyEmqFQeWncJDUqKsoaXSJOYRe+TLL0\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1147,7 +1147,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-Gebk9HH2qADRSOSKhxgGSciKWLXgbBh2JNFsB3N007sWLoMHxY9CwRYAxVFUvozk\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-1kmiK1VZqbW7bUbqab71ewuOPVx8D7kIANyEmqFQeWncJDUqKsoaXSJOYRe+TLL0\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1163,7 +1163,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-Gebk9HH2qADRSOSKhxgGSciKWLXgbBh2JNFsB3N007sWLoMHxY9CwRYAxVFUvozk\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-1kmiK1VZqbW7bUbqab71ewuOPVx8D7kIANyEmqFQeWncJDUqKsoaXSJOYRe+TLL0\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]

View file

@ -1108,7 +1108,7 @@
"bundle": "regions-dashboard",
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"output": "dx-maps/regions-dashboard/regions-dashboard.js",
"integrity": "sha384-Gebk9HH2qADRSOSKhxgGSciKWLXgbBh2JNFsB3N007sWLoMHxY9CwRYAxVFUvozk",
"integrity": "sha384-1kmiK1VZqbW7bUbqab71ewuOPVx8D7kIANyEmqFQeWncJDUqKsoaXSJOYRe+TLL0",
"elements": [
"regions-dashboard"
],
@ -1131,7 +1131,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/region-dashboards/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-Gebk9HH2qADRSOSKhxgGSciKWLXgbBh2JNFsB3N007sWLoMHxY9CwRYAxVFUvozk\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<!-- Mapbox engine, fetched early. Keep it only with a real Mapbox token in \"tk\": without one (or with \"provider\":\"maplibre\") the map draws with MapLibre and this line must go -->\n<link rel=\"preload\" as=\"script\" href=\"https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js\" integrity=\"sha384-tO0clamXONKwbiz7O6xJpUeyJbTSPugC+xM9clvfFvKTH3i8SmCawHGdf5quMWmZ\" crossorigin=\"anonymous\">\n<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-1kmiK1VZqbW7bUbqab71ewuOPVx8D7kIANyEmqFQeWncJDUqKsoaXSJOYRe+TLL0\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"region-dashboards\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1147,7 +1147,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-Gebk9HH2qADRSOSKhxgGSciKWLXgbBh2JNFsB3N007sWLoMHxY9CwRYAxVFUvozk\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-1kmiK1VZqbW7bUbqab71ewuOPVx8D7kIANyEmqFQeWncJDUqKsoaXSJOYRe+TLL0\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-list\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
},
{
@ -1163,7 +1163,7 @@
"scriptUrl": "./regions-dashboard/regions-dashboard.js",
"embed": {
"iframe": "<iframe src=\"./regions-dashboard/regions-mfe/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-Gebk9HH2qADRSOSKhxgGSciKWLXgbBh2JNFsB3N007sWLoMHxY9CwRYAxVFUvozk\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./regions-dashboard/regions-dashboard.js\" integrity=\"sha384-1kmiK1VZqbW7bUbqab71ewuOPVx8D7kIANyEmqFQeWncJDUqKsoaXSJOYRe+TLL0\" crossorigin=\"anonymous\"></script>\n<!-- provider: \"maplibre\" is the default; change it to \"mapbox\" and fill tk to use Mapbox. -->\n<regions-dashboard profile-config='{\"profile\":\"regions-mfe\",\"provider\":\"maplibre\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></regions-dashboard>"
}
}
]

View file

@ -65,7 +65,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-Gebk9HH2qADRSOSKhxgGSciKWLXgbBh2JNFsB3N007sWLoMHxY9CwRYAxVFUvozk" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-1kmiK1VZqbW7bUbqab71ewuOPVx8D7kIANyEmqFQeWncJDUqKsoaXSJOYRe+TLL0" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -61,7 +61,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-Gebk9HH2qADRSOSKhxgGSciKWLXgbBh2JNFsB3N007sWLoMHxY9CwRYAxVFUvozk" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-1kmiK1VZqbW7bUbqab71ewuOPVx8D7kIANyEmqFQeWncJDUqKsoaXSJOYRe+TLL0" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -72,7 +72,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <regions-dashboard> custom element. -->
<script src="../regions-dashboard.js" integrity="sha384-Gebk9HH2qADRSOSKhxgGSciKWLXgbBh2JNFsB3N007sWLoMHxY9CwRYAxVFUvozk" crossorigin="anonymous"></script>
<script src="../regions-dashboard.js" integrity="sha384-1kmiK1VZqbW7bUbqab71ewuOPVx8D7kIANyEmqFQeWncJDUqKsoaXSJOYRe+TLL0" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: