diff --git a/plugin-designs/country-posters/countries-map.meta.json b/plugin-designs/country-posters/countries-map.meta.json
new file mode 100644
index 00000000..70ec7e8a
--- /dev/null
+++ b/plugin-designs/country-posters/countries-map.meta.json
@@ -0,0 +1,5 @@
+{
+ "type": "single",
+ "label": "Country Posters — countries map",
+ "note": "The map pane of the Country Posters element (country-posters-mfe composes it); also embeddable alone."
+}
diff --git a/plugin-designs/country-posters/countries-map.vue b/plugin-designs/country-posters/countries-map.vue
index 3c08da7f..96108c68 100644
--- a/plugin-designs/country-posters/countries-map.vue
+++ b/plugin-designs/country-posters/countries-map.vue
@@ -1252,7 +1252,8 @@ async function openPacket() {
//
// library/composables/usePosterPrints.js ALREADY has printPacket: the same pages, authored as HTML
// with @page rules, printed in place. This map was running a private second implementation of a
- // shared product feature. Routing to it deletes the duplicate, drops jspdf from this bundle, and
+ // shared product feature. Routing to it deletes the duplicate (jspdf itself stays: useMapPoster.toPdf
+ // still brings it in for the poster, and the bundle grew with the HTML renderer), and
// the packet gains the pages the shared renderer has that the local one never did (the life
// calendar, the funnel, the prayer wheel — see packet-render.buildPacket's page order).
const [{ usePosterPrints }, { defaultPosterSpec }, { useMapPoster }] = await Promise.all([
@@ -1261,12 +1262,30 @@ async function openPacket() {
import('@map/composables/poster/useMapPoster.js'),
])
- // Same 50-group cap as before: an overnight safety limit, not a design choice.
- let groups = mapData.normalizedPeopleGroups.value || []
- if (groups.length > 50) {
- console.warn(`[country-posters] packet export truncated ${groups.length} → 50 people groups`)
- groups = groups.slice(0, 50)
- }
+ // ★ THE PACKET IS THE SELECTION, not the first 50 loaded groups (rega: "Asia" printed Sudan).
+ // library/maps/pin-selection.js reads the pins as DRAWN (their properties are derived: filled-in
+ // language family, canonical region keys), so a row filter would pick a plausible WRONG subset.
+ // This map has no dim expression, so the pins layer's own filter is the whole selection.
+ const all = mapData.normalizedPeopleGroups.value || []
+ let groups = all
+ try {
+ const { selectedPinIds } = await import('@map/maps/pin-selection.js')
+ const m = map.value
+ const filt = m && m.getLayer('language-family-pins') ? m.getFilter('language-family-pins') : null
+ const ids = m ? selectedPinIds(m, { expressions: [filt].filter(Boolean) }) : null
+ if (ids && ids.size) {
+ const picked = all.filter((g) => ids.has(String(g.uniqueId ?? g.id ?? '')))
+ // ids but no rows is an ID MISMATCH, not an empty selection: print everything and say so,
+ // rather than a packet about nothing.
+ if (picked.length) groups = picked
+ else console.warn('[country-posters] packet: the selected pins matched no rows (id mismatch); printing the full list')
+ } else if (ids) {
+ groups = [] // a genuinely empty selection: the printer's own "no records" error says so
+ }
+ } catch (e) { console.warn('[country-posters] packet selection unavailable; printing the full list:', e?.message || e) }
+ // Same 50-group cap as before: an overnight safety limit, not a design choice. The cover says so.
+ const selectedTotal = groups.length
+ if (groups.length > 50) groups = groups.slice(0, 50)
// ★ AN EXPLICIT ADAPTER, because the two halves name the same fields differently and a mismatch
// here is SILENT — the packet would print with blank names and empty photos rather than failing.
@@ -1313,7 +1332,7 @@ async function openPacket() {
const zone = {
id: String(activeTab.value?.id || 'research-map'),
- label: legendTitle.value || tabLabel(activeTab.value) || 'Doxa Map',
+ label: (legendTitle.value || tabLabel(activeTab.value) || 'Doxa Map') + (selectedTotal > 50 ? ` — 50 of ${selectedTotal}` : ''),
zoneKey: String(activeTab.value?.id || 'research-map'),
}
const prints = usePosterPrints({
@@ -1407,6 +1426,23 @@ async function ensureRegionsLoaded() {
const regionsData = await mapData.loadRegionsData?.()
if (regionsData && map.value) {
mapLayers.addRegionsLayer?.(regionsData, 'doxa-regions')
+ // PRE-WARM the shader variant here, while a large GeoJSON is loading anyway. highlightCountryByIso
+ // turns these three paints DATA-DRIVEN on a click; the first time that costs a one-off shader
+ // compile (rega on research-map: 5.5 s MapLibre / 11.1 s Mapbox under SwiftShader). Painting each
+ // current value through a case with two identical branches keeps the look and moves the compile
+ // off the gesture. All three, or the other two still compile on the click (simpleresearch).
+ // "Already data-driven" means the value READS THE FEATURE somewhere, not merely that it is an
+ // array: a zoom-only interpolate is an array and still compiles a variant on the first gesture
+ // (simpleresearch). Anything keyed on a feature is left exactly as the layer wrote it.
+ const readsFeature = (e) => Array.isArray(e) && (e[0] === 'get' || e[0] === 'has' || e[0] === 'feature-state' || e.some(readsFeature))
+ for (const [layer, prop] of [['regions-fill', 'fill-opacity'], ['regions-border', 'line-color'], ['regions-border', 'line-width']]) {
+ try {
+ const v = map.value.getLayer(layer) && map.value.getPaintProperty(layer, prop)
+ if (v != null && !readsFeature(v)) {
+ map.value.setPaintProperty(layer, prop, ['case', ['in', ['get', 'iso_3166_1_alpha_3'], ['literal', []]], v, v])
+ }
+ } catch (_) { /* a style without the layer: nothing to warm */ }
+ }
// Hide regions on non-Doxa-Regions tabs so they don't show through.
if (map.value.getLayer('regions-fill') && activeTab.value?.id !== 'doxa-regions') {
map.value.setLayoutProperty('regions-fill', 'visibility', 'none')
diff --git a/plugin-designs/country-posters/country-poster-wall.meta.json b/plugin-designs/country-posters/country-poster-wall.meta.json
new file mode 100644
index 00000000..ae9b999f
--- /dev/null
+++ b/plugin-designs/country-posters/country-poster-wall.meta.json
@@ -0,0 +1,5 @@
+{
+ "type": "single",
+ "label": "Country Posters — poster wall",
+ "note": "The printable poster wall pane of the Country Posters element (country-posters-mfe composes it); also embeddable alone."
+}
diff --git a/plugin-designs/doxa-simple-map/doxa-simple-map.meta.json b/plugin-designs/doxa-simple-map/doxa-simple-map.meta.json
index 6d3ae69b..0da75c4a 100644
--- a/plugin-designs/doxa-simple-map/doxa-simple-map.meta.json
+++ b/plugin-designs/doxa-simple-map/doxa-simple-map.meta.json
@@ -1,4 +1,5 @@
{
+ "label": "Prayer Progress — prayer map",
"stage": "production",
"_comment": "Prayer Progress is a production map: the Plugin Studio and the gallery list it outside Experimental (stage is read into manifest.json by generate-tree)."
}
diff --git a/public/dx-maps/country-posters/countries-map/index.html b/public/dx-maps/country-posters/countries-map/index.html
index 455416dd..64fbfd7e 100644
--- a/public/dx-maps/country-posters/countries-map/index.html
+++ b/public/dx-maps/country-posters/countries-map/index.html
@@ -36,7 +36,7 @@
-
+
-
+
-
+
\n\n"
+ "script": "\n\n\n\n"
}
},
{
"profile": "country-poster-wall",
"stage": "experimental",
+ "label": "Country Posters — poster wall",
"file": "country-poster-wall.vue",
"description": "One printable card per country, grouped into COLLAPSIBLE regional sections so the",
"element": "country-posters-map",
@@ -204,7 +206,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "",
- "script": "\n\n"
+ "script": "\n\n"
}
},
{
@@ -220,7 +222,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "",
- "script": "\n\n\n\n"
+ "script": "\n\n\n\n"
}
}
]
@@ -431,6 +433,7 @@
{
"profile": "doxa-simple-map",
"stage": "production",
+ "label": "Prayer Progress — prayer map",
"file": "doxa-simple-map.vue",
"description": "The simple world map, shipped as three presets off ONE prop table —",
"element": "doxa-map",
diff --git a/public/dx-maps/manifest.json b/public/dx-maps/manifest.json
index 588f643b..8ae2c29a 100644
--- a/public/dx-maps/manifest.json
+++ b/public/dx-maps/manifest.json
@@ -167,7 +167,7 @@
"bundle": "country-posters",
"scriptUrl": "./country-posters/country-posters.js",
"output": "dx-maps/country-posters/country-posters.js",
- "integrity": "sha384-toNru0aS5EWD+nQu5sHLNTA4yymbYAGM0bok5vTAqb1wDamtqeFg+qTS0C0ExrTY",
+ "integrity": "sha384-CvAn6ZBYlPM0nIXmcfYBAR2ziHHVumz+Im40Oe69o1ta0irtH7sxhnEc2J2oxnWJ",
"elements": [
"country-posters-map"
],
@@ -180,6 +180,7 @@
{
"profile": "countries-map",
"stage": "experimental",
+ "label": "Country Posters — countries map",
"file": "countries-map.vue",
"description": "The researcher's workbench retargeted from people groups to COUNTRIES — region",
"element": "country-posters-map",
@@ -189,12 +190,13 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "",
- "script": "\n\n\n\n"
+ "script": "\n\n\n\n"
}
},
{
"profile": "country-poster-wall",
"stage": "experimental",
+ "label": "Country Posters — poster wall",
"file": "country-poster-wall.vue",
"description": "One printable card per country, grouped into COLLAPSIBLE regional sections so the",
"element": "country-posters-map",
@@ -204,7 +206,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "",
- "script": "\n\n"
+ "script": "\n\n"
}
},
{
@@ -220,7 +222,7 @@
"scriptUrl": "./country-posters/country-posters.js",
"embed": {
"iframe": "",
- "script": "\n\n\n\n"
+ "script": "\n\n\n\n"
}
}
]
@@ -431,6 +433,7 @@
{
"profile": "doxa-simple-map",
"stage": "production",
+ "label": "Prayer Progress — prayer map",
"file": "doxa-simple-map.vue",
"description": "The simple world map, shipped as three presets off ONE prop table —",
"element": "doxa-map",