diff --git a/internal/check-selection-kinds.mjs b/internal/check-selection-kinds.mjs index 6022e3a53..9b0833545 100644 --- a/internal/check-selection-kinds.mjs +++ b/internal/check-selection-kinds.mjs @@ -166,6 +166,27 @@ for (const fn of ['onLegendExport', 'makeSelectionPacket', 'makeSelectionPoster' } } +// ── 4. A TAPPED FEATURE RESOLVES AT ITS REAL LEVEL, FROM ITS REAL PROPERTIES ────────────────────── +// Sections 1-2 start from hand-made KEYS ('adm0:IND'), so they could not see the resolver itself +// misfile a country: once the world ADM0 boundaries carried a shapeName, "shapeName → ADM1" turned +// every tapped country into a province (key 'adm1:Sudan', count null, 0 UUPG), and the Prayer List +// refused it (reported from a phone, 2026-09-30). These are the properties the map ACTUALLY hands the +// click handler (read off the live tapestry), so a resolver that keys on the wrong field fails here. +{ + const { resolveTerritory, COUNTRY_COUNTS, ADM1_COUNTS } = await import(resolve(ROOT, 'plugin-designs/my-upg-100-list/data/territory-counts.js')) + const country = resolveTerritory({ shapeGroup: 'SDN', shapeName: 'Sudan', iso3: 'SDN', zoneKey: 'sudan', zoneLabel: 'Sudan' }) + ok(country && country.kind === 'adm0' && country.key === 'adm0:SDN', + `a tapped COUNTRY (shapeName + iso3, no listId) resolved as ${JSON.stringify(country)}, not adm0:SDN — every country tap would count 0 UUPG`) + ok(country && country.count === COUNTRY_COUNTS.SDN && country.count > 0, + `a tapped country's count is ${country && country.count}, not its COUNTRY_COUNTS entry (${COUNTRY_COUNTS.SDN})`) + const state = resolveTerritory({ iso3: 'IND', shapeGroup: 'IND', listId: 'asia-india-madhya-pradesh', shapeName: 'Madhya Pradesh', zoneKey: 'asia-india-madhya-pradesh' }) + ok(state && state.kind === 'adm1' && state.key === 'adm1:Madhya Pradesh' && state.count === ADM1_COUNTS['Madhya Pradesh'], + `a tapped India STATE (listId + shapeName + iso3) resolved as ${JSON.stringify(state)}, not adm1 with its ADM1_COUNTS entry`) + const nepal = resolveTerritory({ shapeGroup: 'NPL', shapeName: 'Nepal', iso3: 'NPL', zoneKey: 'asia-india-uttarakhand' }) + ok(nepal && nepal.kind === 'adm0' && nepal.key === 'adm0:NPL', + `a country drawn inside an Asia zone (zone.adm0, no listId) resolved as ${JSON.stringify(nepal)}, not adm0:NPL`) +} + if (fails.length) { console.error('SELECTION-KINDS: FAIL') for (const f of fails) console.error(` ✗ ${f}`) diff --git a/plugin-designs/my-upg-100-list/data/territory-counts.js b/plugin-designs/my-upg-100-list/data/territory-counts.js index 5a5db682e..d880ff964 100644 --- a/plugin-designs/my-upg-100-list/data/territory-counts.js +++ b/plugin-designs/my-upg-100-list/data/territory-counts.js @@ -128,10 +128,18 @@ export function buildCountryNameMap(records) { * resolveTerritory(props, opts) -> { key, name, count, kind } | null * * Given a clicked tapestry feature's `properties`, return a stable identity for - * batch-selection. Resolution order mirrors zone-geometry.js stamp(): - * 1. ADM1: props.shapeName present → name = shapeName, count = ADM1_COUNTS. - * 2. ADM0: props.iso3 present → name = nameMap[iso3] (live label, else - * ISO3_NAME fallback, else the raw iso3), count = COUNTRY_COUNTS[iso3]. + * batch-selection. The LEVEL comes from where zone-geometry.js drew the feature from: + * 1. ADM1: an India state / China province. zone-geometry takes those ONLY from the + * gb-IND/CHN-ADM1 pools, and only when props.listId === the zone's id, so every + * ADM1 feature carries listId → name = shapeName, count = ADM1_COUNTS. (A legacy + * feature with a shapeName and no iso3 is also ADM1.) + * 2. ADM0: a country, props.iso3 → name = nameMap[iso3] (live label, else ISO3_NAME + * fallback, else the raw iso3), count = COUNTRY_COUNTS[iso3]. + * ★ NOT "shapeName present → ADM1". The world ADM0 boundaries now carry a shapeName too + * (Sudan: { shapeGroup:'SDN', shapeName:'Sudan', iso3:'SDN' }), so that rule filed EVERY + * tapped country as a province: key 'adm1:Sudan', count null, a HUD total of 0 UUPG, and a + * Prayer List that refused the country the reader had just selected ("Select your own + * territory always finds 0 people groups", reported from a phone, 2026-09-30). * * `key` is the de-dup / toggle identity ('adm1:' or 'adm0:') so * a country and a same-named admin never collide, and re-clicking toggles cleanly. @@ -145,8 +153,8 @@ export function resolveTerritory(props, opts = {}) { if (!props || typeof props !== 'object') return null; const nameMap = opts.nameMap || ISO3_NAME; - // (1) ADM1 — India state / China province - if (props.shapeName) { + // (1) ADM1 — India state / China province (listId marks the ADM1 pools; see above) + if (props.shapeName && (props.listId || !props.iso3)) { const name = String(props.shapeName); const count = Object.prototype.hasOwnProperty.call(ADM1_COUNTS, name) ? ADM1_COUNTS[name] : null;