The legend tree accepts a generated title, and keeps its label when there is none

The seam between zone-title.js (84062744) and everything that renders a zone name. buildLegendNodes
already took an opts object, so this is one option — `opts.title(zone)` — consumed at the three places a
leaf takes its name from data: the 13 regional rows, the 12 asia rows, the 2 uncovered rows.

★ NULL MEANS "KEEP YOURS", NOT "NO TITLE", and that is the whole reason this is safe to land while the
Driver's decision on the other 18 zones is still open. 18 of 28 zones decline — their labels are
sub-national ("Malabar Coast") or taxonomic (IMB's wagf blocks), and Intl has no subdivisions — so
falling back to the hand-written label is the NORMAL path here, not an error path. A generated title
replaces a label only where one could actually be composed.

⚠ THIS FILE HAS THREE CONSUMERS, not one: the legend, library/scripts/posters/generate.mjs and
publish-html.mjs both enumerate zones through leafZones(buildLegendNodes(...)), and packet.mjs joined
them in 3ef164c0. So a behaviour change with no option passed would have moved the poster and packet
output too. PROVEN UNCHANGED rather than assumed:
  - no opts.title                      -> 28 leaves, labels byte-identical to before
  - opts.title returning null for all  -> byte-identical again (the decline path is not a special case)
  - opts.title generating for one zone -> exactly that one label changes, nothing else
  - the packet generator still builds  -> Tibet, 43 groups / 7 pages
  - publish-html's enumeration         -> still 28 zone ids

Not yet called with a real resolver anywhere; wiring the legend, the poster header and the packet page-1
title is the next step, and it waits on the caller having records at the reader's locale to build the
country-label index from.
This commit is contained in:
Doxa Maps 2026-09-27 02:54:25 +00:00
parent 84062744ee
commit 45b07836cb

View file

@ -85,6 +85,16 @@ function leafPopulation(row, records) {
export function buildLegendNodes(clusters, opts = {}) {
const deafCount = opts.deafCount ?? 123;
const records = Array.isArray(opts.records) ? opts.records : null;
// ★ OPTIONAL GENERATED TITLE (Driver, 2026-09-27). `opts.title(zone)` returns a localized title
// composed from already-translated parts, or NULL for a zone whose title cannot be generated — see
// data/zone-title.js. Passing nothing keeps every hand-written English label exactly as it was, which
// is why this is an option rather than a rewrite: the legend, the poster and the packet adopt it
// independently, and a caller that has no records yet (records:null) still builds a correct tree.
// ⚠ NULL MEANS "KEEP YOURS", NOT "NO TITLE". 18 of 28 zones decline (their labels are sub-national or
// taxonomic, and Intl has no subdivisions), so the fallback is the normal path, not the error path.
const titleOf = typeof opts.title === 'function'
? (zone, fallback) => opts.title(zone) || fallback
: (_zone, fallback) => fallback;
const lists = clusters.lists || [];
const asia = clusters.asia_lists || [];
// slice 09b-6 §1(a): wire the 7 uncovered "Frontier Asia" zones (Nepal,
@ -137,7 +147,7 @@ export function buildLegendNodes(clusters, opts = {}) {
{
id: l.id,
zoneKey: l.id,
label: l.label,
label: titleOf(l, l.label),
// Carry the province enumeration the region-name label replaced.
...(l.provinceLabel ? { provinceLabel: l.provinceLabel } : {}),
color: colorOf(l.id),
@ -171,7 +181,7 @@ export function buildLegendNodes(clusters, opts = {}) {
{
id: r.id,
zoneKey: r.id,
label: r.label,
label: titleOf(r, r.label),
// The province enumeration the region-name label replaced. Carried through
// like adm0/provinces above — a node rebuilt field-by-field silently drops
// anything not named, which is exactly how adm0 and population were lost.
@ -228,7 +238,7 @@ export function buildLegendNodes(clusters, opts = {}) {
{
id: u.id,
zoneKey: u.id,
label: u.label,
label: titleOf(u, u.label),
// Carry the province enumeration the region-name label replaced.
...(u.provinceLabel ? { provinceLabel: u.provinceLabel } : {}),
color: colorOf(u.id),