Name the UPG-100 legend's tabs, instead of shipping the placeholder

The legend read "GENERATION 1 / 2 / 3" above a list of world regions. A UUPG
list has no notion of a generation, so the label described nothing and invited
the reading that the tabs filter the map by some cohort. They do not.

The cause is a fallback, not a typo. SemanticTreeLegend derives its tab strip
from the `tabs` prop and, where an entry is missing, substitutes
`{ id: 'gen'+(i+1), label: 'Generation '+(i+1) }` so the strip has something to
render. This bundle passed no `tabs` at all, so all three were placeholders.
country-posters and doxa-research-map both pass real tab sets; doxa-simple-map
passes an explicit null. This was the only bundle relying on the fallback.

What a tab actually is: a DEPTH LEVEL of the tree, which is why selecting a node
jumps the strip to that node's depth. The three levels were read out of
data/legend-tree.js rather than guessed — regions are assembled by grouping
leaves under regionFor(), zones are the middle nodes (Frontier Asia (uncovered),
India, Deaf (affinity bloc A017)), and the leaves are the individual groups each
dot belongs to. Hence Region / Zone / People Group, each with an `info` note in
the shape AFFINITY_TABS and REGIONS_TABS already use.

The ids are bundle-scoped, and that was measured rather than chosen on style.
tabLabel() resolves `tabs.<id>` against the shared i18n catalog FIRST and only
falls back to the English label when no entry exists, so a tab id is a global
namespace. The first version used id:'region' and the legend rendered "WAGF
Region" — library/i18n/locales/en/tabs.json already maps that key to the
country-posters taxonomy. id:'group' collides the same way and would have been
right here only by coincidence, since the catalog means the ROP-2.5 sense. The
prefixed ids own no catalog entry, so the labels in this file are what render.

Desktop only, deliberately. LegendMobile computes
`hideTabs = nodes ? !tabs : _isFlatTabMobile`, so with nodes supplied and no
tabs the mobile sheet hides the strip entirely — it never showed the
placeholder. Passing tabs there would reveal a strip that is currently hidden,
which is a design change rather than a bug fix.

Verified by driving the built bundle in Chromium and reading the three chips
back: Region / Zone / People Group, each carrying its info tooltip, where the
same probe returned "WAGF Regioni / Zonei / People Groupi" before the id fix.
Then the README shot was regenerated and read as an image — the strip renders
REGION / ZONE / PEOPLE GROUP and the column header now says REGION where it said
GENERATION 1. Element count moved 311 -> 314, the three info buttons.

Eleven gates pass, including sfc-parse, template-literal, sri, composite,
one-palette and synced-css.

Found while verifying BUG-UPG100-DOTS-HARD-TO-SEE, which is genuinely fixed: the
dots carry a solid dark stroke that frames them against both the pale zone fills
and the white basemap, confirmed by reading the image rather than the card field.
This commit is contained in:
doxa-maps-88655c70 2026-09-23 06:52:18 +00:00
parent bd504063a1
commit 34ef223619
20 changed files with 72 additions and 38 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 903 KiB

After

Width:  |  Height:  |  Size: 903 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 974 KiB

After

Width:  |  Height:  |  Size: 974 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 922 KiB

After

Width:  |  Height:  |  Size: 925 KiB

View file

@ -248,6 +248,39 @@ const geocoderRef = ref(null) // <GeocoderComponent> exposed ref
// "Legend" alone does not say which one you are looking at.
const LEGEND_TITLE = 'My UPG 100 List — Legend'
// ★ NAME THE LEGEND'S DEPTH TABS. Without a `tabs` prop SemanticTreeLegend falls through to
// `src[i] ?? { id: 'gen'+(i+1), label: 'Generation '+(i+1) }` (SemanticTreeLegend.vue:157) — a
// placeholder that exists only so the tab strip has something to render. This bundle shipped with
// it, so the legend read "GENERATION 1 / 2 / 3" above a list of regions. A UUPG list has no notion
// of a generation, so the label described nothing and invited the reading that the tabs FILTER the
// map by some cohort. They do not: a tab is a DEPTH LEVEL of this tree, which is why selecting a
// node jumps to its depth (`activeTab = Math.min(sel.depth, …)`, same file:232).
//
// The three levels, read from data/legend-tree.js rather than guessed: regions are assembled at
// :302 by grouping leaves under regionFor(); zones are the middle nodes (Frontier Asia (uncovered)
// at :245, India at :199, Deaf (affinity bloc A017) at :159); leaves are the individual prayer
// zones each pin belongs to. Shape and `info` wording follow AFFINITY_TABS/REGIONS_TABS in
// country-posters/countries-map.vue:836, the established precedent — `info` renders both as the
// chip's title tooltip and as a popover (SemanticTreeLegend.vue:1125-1130).
//
// ★ THE IDS ARE DELIBERATELY BUNDLE-SCOPED, and that was MEASURED, not stylistic. tabLabel()
// (SemanticTreeLegend.vue:39) resolves `tabs.<id>` against the shared i18n catalog FIRST and only
// falls back to the English `label` when no entry exists — so a tab id is a GLOBAL namespace. The
// first version of this list used id:'region', and the legend rendered "WAGF Region" instead of
// "Region", because library/i18n/locales/en/tabs.json:17 already maps that key to the
// country-posters taxonomy. id:'group' collides the same way (→ "People Group", correct here only
// by coincidence — the catalog means the ROP-2.5 sense, not this bundle's). Prefixed ids own no
// catalog entry, so the labels below are what render. If these tabs are ever translated, add
// `upg100Region`/`upg100Zone`/`upg100Group` to tabs.json rather than reusing a generic key.
const LEGEND_TABS = [
{ id: 'upg100Region', label: 'Region',
info: 'Region — the broadest grouping, one row per world area (Africa, India, Middle East, Deaf, …). Counts are the UUPGs and population summed across every zone beneath it.' },
{ id: 'upg100Zone', label: 'Zone',
info: 'Zone — the prayer zones inside a region (e.g. Frontier Asia (uncovered) within Asia, or the Indian states within India). This is the level the map paints and colours.' },
{ id: 'upg100Group', label: 'People Group',
info: 'People Group — the individual unreached groups inside a zone, largest prayer need first. Each map dot is one of these.' },
]
const geocoderContext = 'people-groups'
const geocoderPlaceholder = 'Search people group, zone, or territory…'
let searchIndex = [] // [{ label, kind, target, _hay }] (gate handle)
@ -1732,6 +1765,7 @@ onBeforeUnmount(() => {
<SemanticTreeLegend
v-if="legendNodes.length"
:nodes="legendNodes"
:tabs="LEGEND_TABS"
:columns="['count', 'pop']"
:title="LEGEND_TITLE"
:export-enabled="true"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -607,7 +607,7 @@
"bundle": "my-upg-100-list",
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"output": "doxa-maps/my-upg-100-list/my-upg-100-list.js",
"integrity": "sha384-7UaDaEAfj2TcD5WUl/hz7psmErNi6TQ4DS+6pi4h5scI+j9ZVGYK2Oag1dlsA3Yh",
"integrity": "sha384-dNxJTe2UKQCaovLHNakWSo8fbGsaVedscY2Mr1Ca5adYvVbAWsb1Ei1b1X1bwEgs",
"elements": [
"my-upg-100-list-map"
],
@ -623,7 +623,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/poster-wall/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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-7UaDaEAfj2TcD5WUl/hz7psmErNi6TQ4DS+6pi4h5scI+j9ZVGYK2Oag1dlsA3Yh\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-dNxJTe2UKQCaovLHNakWSo8fbGsaVedscY2Mr1Ca5adYvVbAWsb1Ei1b1X1bwEgs\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"poster-wall\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -637,7 +637,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-7UaDaEAfj2TcD5WUl/hz7psmErNi6TQ4DS+6pi4h5scI+j9ZVGYK2Oag1dlsA3Yh\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-dNxJTe2UKQCaovLHNakWSo8fbGsaVedscY2Mr1Ca5adYvVbAWsb1Ei1b1X1bwEgs\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
},
{
@ -651,7 +651,7 @@
"scriptUrl": "./my-upg-100-list/my-upg-100-list.js",
"embed": {
"iframe": "<iframe src=\"./my-upg-100-list/upg-100-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=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-7UaDaEAfj2TcD5WUl/hz7psmErNi6TQ4DS+6pi4h5scI+j9ZVGYK2Oag1dlsA3Yh\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./my-upg-100-list/my-upg-100-list.js\" integrity=\"sha384-dNxJTe2UKQCaovLHNakWSo8fbGsaVedscY2Mr1Ca5adYvVbAWsb1Ei1b1X1bwEgs\" crossorigin=\"anonymous\"></script>\n<my-upg-100-list-map profile-config='{\"profile\":\"upg-100-mfe\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></my-upg-100-list-map>"
}
}
]

File diff suppressed because one or more lines are too long

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-7UaDaEAfj2TcD5WUl/hz7psmErNi6TQ4DS+6pi4h5scI+j9ZVGYK2Oag1dlsA3Yh" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-dNxJTe2UKQCaovLHNakWSo8fbGsaVedscY2Mr1Ca5adYvVbAWsb1Ei1b1X1bwEgs" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-7UaDaEAfj2TcD5WUl/hz7psmErNi6TQ4DS+6pi4h5scI+j9ZVGYK2Oag1dlsA3Yh" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-dNxJTe2UKQCaovLHNakWSo8fbGsaVedscY2Mr1Ca5adYvVbAWsb1Ei1b1X1bwEgs" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -40,7 +40,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <my-upg-100-list-map> custom element. -->
<script src="../my-upg-100-list.js" integrity="sha384-7UaDaEAfj2TcD5WUl/hz7psmErNi6TQ4DS+6pi4h5scI+j9ZVGYK2Oag1dlsA3Yh" crossorigin="anonymous"></script>
<script src="../my-upg-100-list.js" integrity="sha384-dNxJTe2UKQCaovLHNakWSo8fbGsaVedscY2Mr1Ca5adYvVbAWsb1Ei1b1X1bwEgs" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order: