Commit graph

812 commits

Author SHA1 Message Date
Doxa Maps
476989bab5 Merge remote-tracking branch 'origin/development' into regions/country-sheet-onedef
# Conflicts:
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
#	public/dx-maps/regions-dashboard/region-dashboards/index.html
#	public/dx-maps/regions-dashboard/regions-list/index.html
#	public/dx-maps/regions-dashboard/regions-mfe/index.html
2026-09-29 23:17:40 +00:00
Doxa Maps
c314e9421a Merge origin/development into studio/batch 2026-09-29 23:10:22 +00:00
Doxa Maps
aaa4b8b690 A poster tier run can be told the whole series size
China's sheet printed 'map 1 of 1' and 'one of a 0-map set'. The cause is that
generate.mjs derives the series facts from the zones THIS run built --
Object.assign(SERIES, seriesFacts(zones)) -- and the two sheet tiers are separate
runs. The 3x4 tier is China alone, so totalLists came out 1, and the body
sentence rounds it (Math.round(1/10)*10 = 0) into a 0-map set.

SERIES_TOTAL overrides the derived count so a tier run can state the size of the
whole series. Regenerated China with SERIES_TOTAL=174 SERIES_INDEX=174: it now
reads 'map 174 of 174' and 'one of a 170-map set', which is what the other 173
sheets' body text already says (174 and 173 both round to 170).

NOT a defect, and worth recording because it reads like one: the header's exact
count and the body's rounded one are the SAME number by design. 173 exact and
170 approximate are not two answers.

The 173 3x3 sheets still say 'of 173' where the set is 174. Regenerating them
plus a full reshoot is about 28 minutes against tonight's hard switch, the body
text is identical either way, and the difference is only visible with two sheets
side by side. Run BOTH tiers with SERIES_TOTAL=174 to close it.
2026-09-29 23:07:20 +00:00
Doxa Maps
baa60ba647 Merge remote-tracking branch 'origin/development' into regions/country-sheet-onedef
# Conflicts:
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
#	public/dx-maps/regions-dashboard/region-dashboards/index.html
#	public/dx-maps/regions-dashboard/regions-dashboard.js
#	public/dx-maps/regions-dashboard/regions-list/index.html
#	public/dx-maps/regions-dashboard/regions-mfe/index.html
2026-09-29 22:59:27 +00:00
Doxa Maps
3b0fb958e1 MapInfo: the one-time nudge is desktop-only, at a measured threshold
The "What can this map do?" pointer is anchored beside the ⓘ and extends ~213px
INWARD, across whatever the tab puts in its top strip. Worst overlap per width,
measured on Deep Research on the shared browser, on 99c22caf7:

     390   Regions "Region Dashboards" 1416px^2   UPG-100 1428px^2
     768   Cities legend 6560   Gospel legend 6560 + input 6240   Regions 4200
     900   Cities legend 2432   Gospel legend 2496 + input 2176   Regions 3696
    1024   Cities CLEAN         Gospel CLEAN                      Regions 2100
    1200   CLEAN                CLEAN                             CLEAN
    1440   CLEAN                CLEAN                             CLEAN

★ IT IS NOT A PHONE PROBLEM, which is how it was first reported and how I first
fixed it. It persists to 1024 and only clears near 1200, so the max-width:600px
version I wrote first would have left 768, 900 and 1024 broken on four tabs.
Two things found that: gospel counted PANELS (.gm-legend/.gm-sheet) as well as
controls -- a control-only probe misses a legend entirely -- and upg100 measured
the same shape on their own tab (768 over "Prayer Resources" and "Map", 1024 over
"Map", clear at 1200+), in en and ar.

★ ARABIC IS MEASURED, not argued. My first attempt used ?lang=ar, which does not
switch the locale -- the page stayed dir=ltr and I reported that run as invalid
rather than as an ar pass. Via DX Life's /ar/ route (dir=rtl confirmed) the
pointer is hidden and clean at 390, 768 and 1024, and at 1440 it mirrors to the
start side (x=104) and is clean there too.

★ TWO OTHER SHAPES WERE MEASURED FIRST AND BOTH WERE WORSE. Stacking it below the
button traded one collision for five: Gospel's "Resources found" chip (2010px^2)
plus the zoom +/- of Prayer Progress, Big Picture and Simple Research. Hiding
only its text shrank it to 30px but still left 675px^2 on "Region Dashboards" and
900px^2 on Gospel's search input.

⚠ NOT A PLACEMENT CHANGE, and an earlier draft of this got it wrong. It also
pinned the button's inline side at narrow widths; studio's pos.narrow values have
since landed and the buttons now sit at x=8, 12, 294 and 346 across the seven
tabs at 390. A pinned inset-inline-* here wins by specificity and would have
silently flattened every one of them. The button is placement's business; this
rule is only about the hint. 0 added lines declare --map-info-side; 0 touch
.mi-btn.

Its own block rather than the existing 600px one, which also full-screens the
dialog and has nothing to do with this.

VERIFIED AFTER THE CHANGE, en and ar: pointer hidden and 0 overlaps at 390, 768
and 1024 on Cities, Gospel, Regions and UPG-100; present and clean at 1200 and
1440.

NOT FIXED HERE: at 1440 Big Picture's pointer lies over the Religion filter by
1050px^2. Stacking it below the button there does not clear it -- it relocates it
to "Adopted" 561 + "1%" 425. The only single rule that clears that too is hiding
the pointer at every width, which deletes the affordance where it does fit. So
BP-1440 wants a per-tab placement value or a known-minor ruling; it is
pointer-events:none, so the filter still opens.
2026-09-29 22:45:49 +00:00
Doxa Maps
04cf5ea9e6 Merge exp-shepherd/pinhint-maplibre (PinHint clears MapLibre's credit) 2026-09-29 22:17:54 +00:00
Doxa Maps
99c22caf72 Merge origin/development into studio/examples 2026-09-29 22:14:22 +00:00
Doxa Maps
e40077a50c PinHint clears MapLibre's credit too, so the pill stops covering it on phones
syncAttribClearance() measured the strip it must clear from Mapbox's credit
(.mapboxgl-ctrl-attrib) and our own (.doxa-attribution), never from MapLibre's
(.maplibregl-ctrl-attrib). MapLibre is the default engine. With no match, the
clearance stayed at the 20px fallback. On a phone the MapLibre credit wraps to two
lines, and the "Tap a coloured dot" pill covered it: a licence-required credit
hidden on every map that mounts the hint.

Measured at 390x844 on DX Life's home page (Prayer Progress), on the shared
browser: MapLibre en and ar, pill over the credit by 35px with the clearance at
20px; Mapbox, clearance 72px and 0px overlap.

The selector now also takes .maplibregl-ctrl-bottom-right .maplibregl-ctrl-attrib
and the MapLibre form of our own credit line. The existing measure (the strip from
the credit's top to the box's bottom) and its ResizeObserver then apply unchanged.
Proved on the live DOM, inside the map's shadow root: on MapLibre the old selector
finds nothing and the new one finds the credit; on Mapbox both find it.

Library only. Every ProfileLoader consumer needs a rebuild to ship it.
2026-09-29 22:03:22 +00:00
Doxa Maps
190e7665a7 The packet's low pages fill the sheet: measured, then re-shaped
Measured in a browser (content's bounding box against each sheet, en/de/ru):
the knows-Christ grid ended 72% of the way down its sheet (478px of blank
paper below), and the two single-chart sets pages 60% and 69% (702px and
525px). The same 5,200 boxes now lay out 65 x 80 in 14.5px squares, which
spends the height on bigger boxes; the sets charts (SVG, so they scale) grow
from 960px to 1320px (bounded, nothing below it) and 1240px (centered, room
for its ring legend, which widens with it). Now 88%, 79% and 84% of the
height, with no page clipped in en, de or ru. The width classes are hung on
the row (set-solo-<kind>), not on the page, so no page class name enters the
shared stylesheet.
2026-09-29 21:34:14 +00:00
Doxa Maps
c0d9592149 make-previews wrote every WebP one directory too high
REPO was os.path.join(HERE, '..', '..'), and HERE is <root>/library/scripts/posters
-- so REPO resolved to <root>/library, not the repo root. Both outputs went to
directories nothing reads and nothing serves:

    library/plugin-designs/<profile>/assets/previews/   instead of plugin-designs/...
    library/data/posters/                              instead of data/posters/

★ IT REPORTED SUCCESS WHILE DOING NOTHING USEFUL, which is what makes it worth a
commit of its own. The script printed "[make-previews] N posters" and its two
destination lines, wrote genuine WebPs, and exited 0. Only the files were in a
tree no build stages, so the SERVED previews never changed.

MEASURED, on one poster, before and after:
  before  the regenerated thumbnail was byte-identical to the shipped one
          (same sha256, git status clean) -- 0 pixels changed
  after   sha differs, git sees both files modified, and 52.9% of the
          thumbnail's pixels differ (21.1% by more than 40/255, max delta 255)

Found by DRY-RUNNING this step on a single already-shot poster instead of
discovering it after a 174-sheet reshoot. Without the fix that reshoot would have
produced 174 correct WebPs in the wrong tree, the walls would have kept their old
teal thumbnails, and the landing would have looked complete: a teal thumbnail
over a hued print, which is the exact defect the reshoot exists to remove.

The three-level form is what the other scripts in this folder already use to
reach the root; this one was the odd one out.
2026-09-29 21:29:44 +00:00
Doxa Maps
63f6071c61 Merge origin/development into studio/examples (MapInfo's logical placement; generated files take development's side and are rebuilt next) 2026-09-29 21:27:40 +00:00
Doxa Maps
0341c83475 Big Picture's and Simple Research's popup text reads as one sentence after its label
MapInfo prints the takeaway after a bold label ("What you take away:"), and the two tabs' takeaways
opened with an imperative, so every locale stuttered ("What you take away: Take away the rows...").
All 22 takeaways are now noun phrases written to follow that locale's own label, and the two
Portuguese leads move to Brazilian Portuguese to match the catalogue. The claims are unchanged:
Big Picture yields a spreadsheet only; Simple Research yields a poster and a prayer packet. Text by
simpleresearch; 24 strings, only these keys.
2026-09-29 21:12:53 +00:00
Doxa Maps
8b8f1fda79 The info button sits at the END of the line, so Arabic mirrors it
The button and its pointer were placed with a physical right:12px, so on an
Arabic page, where a tab bar starts at the right, they covered the first
(active) tab. They now use inset-inline-end / inset-inline-start (LTR is
unchanged), and under dir=rtl the pointer's arrow glyph and its nudge are
flipped so it still points and moves toward the button. --map-info-top and
--map-info-side remain the per-tab overrides.
2026-09-29 21:08:13 +00:00
Doxa Maps
d38e066ce2 The info popup's stacked-pages hint is visible
The shadow card behind a packet's first page sits at z-index:-1; without a
stacking context of its own it was painted under the dialog's white
background and never seen. isolation:isolate on the stack keeps it just
behind the page.
2026-09-29 21:04:26 +00:00
Doxa Maps
614c3248d3 Card names hyphenate only where a word cannot fit
hyphens:auto on every name let the browser split ANY word at a line end, so
multi-word names could break mid-word ("Northern Conchu-/cos"), which is
worse on a people group's name than the unhyphenated break it replaced. The
previous commit is reverted; hyphenation now applies only to names holding a
14+ letter word (12 of 2,107), with hyphenate-limit-chars:10 4 4 so a short
word is never split: the only multi-word ones are "Deaf <X>", and Deaf
stays whole.
2026-09-29 21:04:26 +00:00
Doxa Maps
1000a7ab5c Revert "A card name that must break mid-word gets a hyphen"
This reverts commit 7cdf72bfd4.
2026-09-29 21:03:46 +00:00
Doxa Maps
a73b672cc6 Merge remote-tracking branch 'origin/development' into regions/country-sheet-onedef
# Conflicts:
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
#	public/dx-maps/regions-dashboard/region-dashboards/index.html
#	public/dx-maps/regions-dashboard/regions-dashboard.js
#	public/dx-maps/regions-dashboard/regions-list/index.html
#	public/dx-maps/regions-dashboard/regions-mfe/index.html
2026-09-29 21:02:52 +00:00
Doxa Maps
520973c1d7 One country-sheet definition: the Regions tab's poster was still teal
exp's second read of 1ab8524cf found a real defect on the demo path, and a census
of the tree found the cause was worse than the symptom.

THE SYMPTOM: the Regions tab printed a TEAL country poster and logged a warn,
while Country Posters printed the same country HUED. That is precisely the "two
different posters for one country" the hue change exists to refuse.

THE CAUSE: regions-list.vue:73 imported a plugin-local copy,
src/country-sheet-print.js, byte-identical to library/packet/countrySheet.js
except for the region key. The region forwarding was added to the LIBRARY file,
so countryRegionColor(undefined) returned the old flat teal and warned.

★ AND THE LIBRARY FILE HAD NO IMPORTERS AT ALL. Its own comment claimed two
consumers -- the country-poster wall and <doxa-country-map> -- and a census found
NEITHER was true: regions-list imported the local copy, and
country-poster-wall.vue:94 defines its own inline countrySheet that calls
generateCountrySheet directly (passing the whole record, which is why THAT path
was correctly hued). So the module was an orphan wearing a comment that said
otherwise, and the region key was added where nothing read it. A move that was
never finished, and the duplicate stayed invisible until one copy was edited.

regions-list now imports the library module, the copy is deleted, and the
comment says what is true. Not re-copied: a duplicate of that file is
indistinguishable from it until one side is edited, and then it is a silent
divergence.

CHAIN VERIFIED TO SOURCE, since the hue is only right if the region survives
every hop: POSTER_BY_ISO3 holds country-posters.json records, which all carry a
region; localisedPoster SPREADS the record in both its branches, so the key
survives a locale switch; postersFor passes it; makeCountrySheet forwards it.
ARTIFACT PROOF: the region forwarding in the rebuilt regions-dashboard bundle
goes from 1 occurrence to 2, and POSTER_REGIONS plus the full region-name list
are present in it.

The no-poster fallback branch in postersFor still builds an object without a
region, deliberately: it has no sheet to draw, and it already carries
unsupportedReason.

NOTE ON HISTORY, not rewritten: f6a3f85a2 is titled "Rebuild the three
consumers…" but it also carries SOURCE changes -- the ink .pin-num-bg in the
shared renderer and the warn-once in posterSheets.js. The title understates it.
It is pushed, so it stands; recorded here instead.
2026-09-29 20:55:27 +00:00
Doxa Maps
be3184207c Merge remote-tracking branch 'origin/development' into upg100/page-fill
# Conflicts:
#	public/dx-maps/country-posters/countries-map/index.html
#	public/dx-maps/country-posters/country-poster-wall/index.html
#	public/dx-maps/country-posters/country-posters-mfe/index.html
#	public/dx-maps/country-posters/country-posters.js
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
#	public/dx-maps/my-upg-100-list/my-upg-100-list.js
#	public/dx-maps/my-upg-100-list/poster-wall/index.html
#	public/dx-maps/my-upg-100-list/upg-100-list/index.html
#	public/dx-maps/my-upg-100-list/upg-100-mfe/index.html
#	public/dx-maps/regions-dashboard/region-dashboards/index.html
#	public/dx-maps/regions-dashboard/regions-dashboard.js
#	public/dx-maps/regions-dashboard/regions-list/index.html
#	public/dx-maps/regions-dashboard/regions-mfe/index.html
2026-09-29 20:54:55 +00:00
Doxa Maps
1ab8524cfb Merge remote-tracking branch 'origin/development' into regions/country-poster-hue 2026-09-29 20:45:42 +00:00
Doxa Maps
42537f11c9 The knows-Christ grid fills the page: the same 5,200 boxes, 80 x 65
52 columns exist only because a year has 52 weeks; on the knows-Christ page
a box is a share of people, and at 52 x 11px the grid used about half the
sheet's width. Without axes the same 5,200 boxes are laid out 80 x 65 (both
divide 5,200, so the rectangle is whole), the columns stretch to the frame
and the cells stay square, still shaded from the top in reading order. The
height this frees goes to a larger legend. The life calendar keeps its 52
week columns and its axes.
2026-09-29 20:41:29 +00:00
Doxa Maps
8bec9ac294 Merge origin/development into studio/examples (the SR caption; generated files take development's side and are rebuilt next) 2026-09-29 20:41:13 +00:00
Doxa Maps
92ceebbcf5 Merge remote-tracking branch 'origin/development' into upg100/page-fill 2026-09-29 20:40:44 +00:00
Doxa Maps
9cf5a68696 Big Picture's table headers come from mapInfo, so the gallery stops carrying all of 'dashboard'
The four headers are copied verbatim, in all eleven locales, from dashboard.json into mapInfo.col
(peopleGroup, country, region, population); no string is retranslated. Measured with areasForBundle:
doxa-maps-spa carried [buttons, dashboard, grid, mapInfo, messages] and now carries [buttons, grid,
mapInfo, messages]. The bundle drops from 420,172 to 371,064 bytes (136,998 to 124,005 gzipped).
2026-09-29 20:37:39 +00:00
Doxa Maps
1980c9000e Merge remote-tracking branch 'origin/development' into regions/country-poster-hue
# Conflicts:
#	public/dx-maps/country-posters/countries-map/index.html
#	public/dx-maps/country-posters/country-poster-wall/index.html
#	public/dx-maps/country-posters/country-posters-mfe/index.html
#	public/dx-maps/country-posters/country-posters.js
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/manifest.json
#	public/dx-maps/my-upg-100-list/my-upg-100-list.js
#	public/dx-maps/my-upg-100-list/poster-wall/index.html
#	public/dx-maps/my-upg-100-list/upg-100-list/index.html
#	public/dx-maps/my-upg-100-list/upg-100-mfe/index.html
#	public/dx-maps/regions-dashboard/region-dashboards/index.html
#	public/dx-maps/regions-dashboard/regions-dashboard.js
#	public/dx-maps/regions-dashboard/regions-list/index.html
#	public/dx-maps/regions-dashboard/regions-mfe/index.html
2026-09-29 20:37:20 +00:00
Doxa Maps
f6a3f85a2c Rebuild the three consumers of the shared country-hue rule
The rule lives in library/packet/posterSheets.js so the browser and the Node
generator cannot disagree, which puts it in every bundle that generates a country
sheet. landing-preconditions step 4 named country-map, country-posters and
regions-dashboard; each built with BUNDLE, then tree, then fix:sri.

No source of country-posters changed -- only its compiled bundle catches up with
the library it already imported.
2026-09-29 20:32:09 +00:00
Doxa Maps
fa7720cc93 Caption for Simple Research's example packet, in all eleven locales
mapInfo.tab.research-map-plus-grid.examplePacket ("Example packet: Middle
East Core"), the titleKey the gallery puts on that tab's pages example so
the packet reads as an example zone. The gallery bundle carries the mapInfo
area, so it is rebuilt.
2026-09-29 20:31:40 +00:00
Doxa Maps
7cdf72bfd4 A card name that must break mid-word gets a hyphen
hyphens:auto on the name, with the deck's <html lang> naming the language,
so a 16-letter name like Liechtensteiners that overflow-wrap has to split
breaks with a hyphen rather than silently mid-word.
2026-09-29 20:28:51 +00:00
Doxa Maps
7795eba447 The knows-Christ totals come from the unrounded share
Only the printed percentage rounds. A large list at 0.004% is tens of
thousands of people, which printed as 0 when the total was computed from
the rounded figure; a share below the last printed digit now reads
"<0.01%" beside its real total. check:evangelical-share asserts it.
2026-09-29 20:20:02 +00:00
Doxa Maps
d78e485643 "Who among them knows Christ" counts people, not weeks
The page reused the life calendar's grid and counted its shaded boxes in
weeks ("0.61% · 32 weeks"), under a week axis and a year axis, with a lede
calling it "one human life in weeks". On this page a box is an equal share
of the list's PEOPLE, so the same 5,200 boxes are now drawn with no week or
year axis, and the legend states each side in people: how many follow
Jesus and how many do not yet, each with its share (the two percentages
sum to 100 as printed), plus how many people one box stands for. Numbers
are formatted in the reader's locale.

The labels become "Followers of Jesus" / "Not yet following Jesus" and the
lede drops the weeks, in all eleven languages; the served strings file is
re-pinned. check:evangelical-share asserted the weeks figure; it now
asserts the people totals, the bare percentage and the absence of weeks
and axes, with the life page (which keeps its axes) as the negative
control. check:packet-variants found the grid by an exact class string
and now matches the class prefix.
2026-09-29 20:08:55 +00:00
Doxa Maps
a2da3fe97b Colour each country poster by its region, instead of one teal for all 173
★ THE REPORT WAS THAT THE COUNTRY POSTER'S CSS LOOKS WORSE THAN THE UPG-100
POSTER'S, AND IT IS NOT A CSS DIFFERENCE. Measured before editing anything: a
country sheet (Sudan) and a 100-list sheet (europe) generated from the same tree
have <style> blocks of 253 lines each, 18,475 vs 18,489 bytes, byte-identical
EXCEPT one custom property --

    country : --zc:#2bb
    upg100  : --zc:hsl(103, 72%, 56%)

poster-render.js has exactly ONE <style> block and both series render through it,
so typography, margins, header, legend and sheet fill are literally the same
rules. There was no second stylesheet and nothing to port; a CSS rewrite would
have been wasted work and a collision with the sheet-fill work in flight.

--zc drives the zone fill, the pin fill and the pin-number background -- the
three most visible things on a sheet -- and all 173 country posters set it to the
same hardcoded teal while each 100-list sheet carried its own hue. That is the
whole of the difference.

★ THE RULE LIVES IN library/packet/posterSheets.js BECAUSE BOTH PATHS MUST
AGREE, and getting that wrong is the most useful thing that happened here. A
first attempt defined the region-to-hue mapping in the Node generator alone and
broke check:poster-browser-equiv by exactly 26 bytes on country-npl -- the length
of 'hsl(45, 72%, 56%)' over '#2bb', twice, because --zc is emitted at two places.
The browser generates these sheets at click time from posterSheets.js, so a rule
one side knows and the other does not is two different posters for the same
country, which is precisely what that gate exists to refuse. One definition,
imported by the Node generator; countryZone takes the region, and countrySheet.js
forwards it so the print wall's path carries it too.

The formula mirrors leafColor in the 100-list's legend-tree.js -- an even hue
spread at 72%/56%, comma-HSL because Mapbox paint expressions reject the slash
syntax. It is restated rather than imported because library/ must not depend on
plugin-designs/; the dependency runs the other way. If that rule changes there,
this changes in the same commit.

★ BY REGION, NOT BY COUNTRY INDEX. 173 countries over 360 degrees is ~2 degrees
apart: indistinguishable, and meaningless on a poster seen ALONE with no sibling
to be told apart from. A region is a grouping the reader already has, so Sudan
and Chad reading as related is true rather than decorative.

★ THE LIST IS DECLARED, NOT DERIVED FROM WHAT A RUN CONTAINS. Indexing into the
regions present in one run would give a country one hue in a single-sheet render
and another in a full build, so re-rendering would change the colour of a poster
somebody had already printed. APPEND a new region; never reorder, which would
change the hue of every region after the insertion. An unrecognised region keeps
the old teal rather than being folded into 'Other' -- folding would silently give
two regions the same hue and hide that the list needs a line.

PROVED BY ARTIFACT, after the refactor:
  Sudan   BEFORE --zc:#2bb   AFTER hsl(180, 72%, 56%)   Middle East
  Chad    hsl(0, 72%, 56%)     Nigeria hsl(0, 72%, 56%)   both Africa -- same region, same hue
  Nepal   hsl(45, 72%, 56%)                               Asia -- differs
  check:poster-browser-equiv: country-npl browser 921548 = node 921548, byte-identical.

★ A LEGIBILITY PROBLEM THIS INHERITS AND DOES NOT CAUSE, reported rather than
silently absorbed: .pin-num is #FFFFFF over .pin-num-bg, filled with --zc. At
72%/56% white text fails WCAG 4.5:1 on SIX of the eight region hues (hue 0 reads
4.26, 45 1.91, 90 1.63, 135 1.74, 180 1.62, 315 3.79; only 225 and 270 pass).
It is PRE-EXISTING and already shipping on the 100-list's own sheets -- europe's
hsl(103) gives white 1.70:1 -- so matching the rule exactly is right for the
reported complaint and makes legibility no worse than it already is. The fix is a
line in poster-render.js's shared <style> block, which is not this change's to
make; it is with that file's owner.

Also folds in a pre-existing punctuation nit found in second read: the hi value
of "Loading results..." ended '..।' -- two ASCII dots plus U+0964 DANDA, a full
stop -- neither an ellipsis nor correct after one. Now '...', matching the
English. Keyset unchanged, so parity is untouched and still 0 debt.

gates:pre PASS 107 FAIL 0 NM 1 of 108.
2026-09-29 19:59:32 +00:00
Doxa Maps
e018ec47b3 A long people-group name is never cut off on a prayer card
The name now sits in a narrow box beside the photo, and the dataset has
16-letter single words (Pagativeshagallu, Liechtensteiners) that would have
overflowed it sideways and been clipped without a sign. The size steps by
the longest word as well as the length, and overflow-wrap:anywhere is the
last resort.
2026-09-29 19:56:47 +00:00
Doxa Maps
b6fe075e92 Prayer-card photos print sharp: sized from their pixels, not stretched
The people-group photos are small: every one sampled across the dataset is
at most 200 px wide, typically 200x250, and the API offers no larger
variant. The card stretched that across its full top band with
object-fit:cover, so a poker card printed a face at about 80 ppi and a
postcard at about 50: visibly blurred, on the one part of the card that
is a person.

The photo is now a framed portrait at the source's own 4:5 shape, sized
from its pixels at 180 ppi (about 1.1 x 1.4 in), on a warm band with the
name beside it (below it on a landscape card). Every stock prints a
200 px photo at about 190 ppi inside its frame. A bigger card gets more
room around the face, never a bigger, blurrier face.
2026-09-29 19:43:58 +00:00
Doxa Maps
cff06fb8e6 Merge origin/development into studio/deep-research 2026-09-29 19:34:32 +00:00
Doxa Maps
7a5c9bd9fa Merge remote-tracking branch 'origin/development' into regions/pane-config-land 2026-09-29 19:30:06 +00:00
Doxa Maps
525ce20a10 Merge origin/development into studio/deep-research (every tab's mapInfo text) 2026-09-29 19:29:04 +00:00
Doxa Maps
bd566e0d18 Merge remote-tracking branch 'origin/development' into regions/pane-config-land 2026-09-29 19:24:39 +00:00
Doxa Maps
c6f9e11427 The info popup keeps Tab inside it, as aria-modal promises
Tab from the last control wraps to the first, and Shift+Tab from the first
to the last, reading the focused element from the shadow root.
2026-09-29 19:22:10 +00:00
Doxa Maps
74d2d67f19 Every map's "what can this do for you?" text, in one catalogue area
Adds the lead and takeaway for six more maps to mapInfo, keyed by the
gallery's tab ids (cities-internet, gospel-resources, dashboard-plus-grid,
research-map-plus-grid, prayer-map, regions-dashboard), plus the Regions
note, in all eleven locales. Each map's owner wrote and checked its own
text; this commit only gathers it, because the gallery ships one area and
one lane editing its eleven files cannot collide with another.

Checked mechanically before writing: every locale present, keys free of
dots (the gallery resolves keys by splitting on them), no numerals, every
locale different from English, and every string in its own locale's
script, since a stray character from another script passes every other
catalogue gate.
2026-09-29 19:17:14 +00:00
Doxa Maps
a1d9a1e318 Merge origin/development into studio/deep-research (MapInfo as landed; upg100's files take development's side) 2026-09-29 19:16:45 +00:00
Doxa Maps
656503a28d Merge remote-tracking branch 'origin/development' into regions/pane-config-land
# Conflicts:
#	package.json
2026-09-29 19:12:46 +00:00
Doxa Maps
93f809ebcd A map explains itself: an info button, a one-time pointer and an example popup
MapInfo (library/components/info) answers "what can this map do for
you?" with real output the map makes, pre-generated so it opens at once.
The examples are an ordered list of kinds rather than a fixed set,
because maps make different things: an image (a poster), pages (a packet
that shows its first page and expands to every page), a deck (cards that
expand from a stack to a grid) and a table (a real excerpt of the
spreadsheet a map exports, with translatable headers). An example whose
image is missing drops out quietly: no broken-image icon, no empty frame.

The pointer at the button goes for good once that map's popup has been
opened and closed; a checkbox in the popup turns it off on every map.
Both are kept in localStorage behind a guard, so private browsing cannot
break the page. On a phone the popup takes the whole screen; Escape
closes it and focus returns to the button. The pointer's motion stops
under prefers-reduced-motion.

All of its text is one new catalogue area, 'mapInfo', in all eleven
locales: the component's own words, and each map's lead, takeaway and
note as mapInfo.tab.<tabId>.<field>. One area because a bundle ships only
the areas its source names, and the gallery that mounts a popup per map
names none of the maps' own areas. This commit carries the UPG-100
map's two sentences; the other maps' follow.
2026-09-29 19:08:02 +00:00
Doxa Maps
f293cf7e99 A map explains itself: an info button, a one-time pointer and an example popup
MapInfo (library/components/info) answers "what can this map do for
you?" with real output the map makes, pre-generated so it opens at once.
The examples are an ordered list of kinds rather than a fixed set,
because maps make different things: an image (a poster), pages (a packet
that shows its first page and expands to every page), a deck (cards that
expand from a stack to a grid) and a table (a real excerpt of the
spreadsheet a map exports, with translatable headers).

The pointer at the button goes for good once that map's popup has been
opened and closed; a checkbox in the popup turns it off on every map.
Both are kept in localStorage behind a guard, so private browsing cannot
break the page. On a phone the popup takes the whole screen; Escape
closes it and focus returns to the button. The pointer's motion stops
under prefers-reduced-motion.

Its own words are a new 'mapInfo' area in all eleven locales. Each map's
lead, takeaway and note arrive as keys in that map's own area, resolved
at render so a live language switch re-translates them; the UPG-100
map's two sentences are added to upgMap in all eleven locales.
2026-09-29 18:50:57 +00:00
Doxa Maps
9ccec24d29 Generalise the pane re-forward to attribute-configured panes, and gate it
sr asked to adopt usePaneConfig for research-map-plus-grid rather than write a
second fix, and found the one shape it did not cover: people-groups-grid takes
its filters as THREE separate attributes (wagf-region, religion-family,
primary-language) instead of one profile-config blob, so that composite has one
pane of each kind. The helper hardcoded 'profile-config' and gave the grid
nothing.

registerAttrs is the sibling for that shape. It shares the same entries list,
the same watchEffect, the same prune and the same write-only-on-change -- one
mechanism for all four composites rather than a fork per adopter. A helper with
a single caller has not been shown to generalise at all; this is the second
shape that showed it.

Two decisions worth recording. Each attribute is compared and written
independently, so a getter whose region changed does not rewrite the two that
did not and re-trigger the child. And the attribute NAME LIST is pinned at
registration: a later getter that omits a key CLEARS that attribute rather than
leaving a stale filter silently narrowing the grid, which is the same class of
lie as the frozen config this helper exists to fix.

The gate runs the shipped helper with real Vue, not a re-typed copy, because the
dependency-tracking hole found in review is invisible to any test that fakes
reactivity. 28 cases over both shapes. Its subject is the measurement on
development 92ca8b6e6: the outer element reached locale ar by +1.5s while every
nested host carried NO locale key at boot and still none at +15s, across four
composites -- so case 1b asserts that an ABSENT key is still a change.

Case 7 is the adopter's property rather than mine: a re-forward triggered by a
LOCALE change must produce the SAME primary-language string. gridLanguage() can
fall back to a display label, and the grid compares whole normalised values, so
a name fetched in one language matches no row in another. It does not bite today
because the surviving fallback is a value the grid itself produced, but the gate
asserts the class, not today's instance.

Mutation-tested, 7 mutations, all caught: plain array, no write-on-change guard,
skip-instead-of-prune, escaping getter throw, no per-attribute compare, omitted
key left stale, and the label-instead-of-code regression. The first two crashed
rather than naming a case, so cfgOf is null-safe now -- a gate that dies reports
"something went wrong" instead of naming the pane that stopped following.

Registered in package.json scripts and in gates:pre, verified by re-parsing the
file: one key, 104 checks. gates:pre PASS 103 FAIL 0 NM 1.
2026-09-29 18:34:49 +00:00
Doxa Maps
99816626af Every shadow stylesheet gets its own label, and a gate keeps it that way
useShadowStyles keys its <style> by the label and overwrites an existing
tag with the same key, so two components sharing a label keep only the
last one's CSS in a shared shadow root. The regions list lost its sheet
this way to the region card, which had kept the list's label when it was
extracted.

check:shadow-style-labels-unique (gates:pre) fails any label two calls
share, globally rather than per shadow root, so a copied or forked
component is caught when it is copied. It reads the real parsers,
classifies every reference to the function (a rebinding, .call, a
re-export or a namespace import fails instead of hiding a call),
cross-checks the AST against the token stream, and covers the other
ways two calls share a key: unlabelled calls under the dev server's
per-component key, and a fixed label over CSS that varies per caller.

countries-map.vue, a fork of research-map.vue, carried the same label;
it is now 'countries-map', with the country-posters bundle rebuilt and
the tree regenerated. The composable's JSDoc called the label a
debugging name; it now says it is the key.
2026-09-29 17:42:44 +00:00
Doxa Maps
62a41859c9 Guard the helper's unmount hook, and give a non-setup caller a way to dispose
onBeforeUnmount only binds inside setup(). Calling usePaneConfig anywhere else
produced a Vue warning and, worse, no automatic cleanup -- the watchEffect would
outlive its caller. Found while re-running studio's own two-pane falsifier
locally: the test constructs the helper in an effectScope with no component
instance, and Vue said so.

regions-mfe is unaffected either way, because it calls the helper at <script
setup> top level where the hook is valid. The hazard is for the next adopter.

Guarded with `if (getCurrentInstance())`, the pattern useMapData.js:144 already
uses, and `dispose` is now returned so a caller outside setup has something to
call rather than silently leaking the effect.

Studio's falsifier still passes after the change: registering two panes that read
different refs and changing only pane 2's source writes pane 2 and leaves pane 1
untouched.
2026-09-29 17:36:42 +00:00
Doxa Maps
6493872170 Track every pane's getter, prune detached ones, and say when one throws
Studio's 5B review found a dependency-tracking hole by testing the helper with
real Vue rather than reading it. entries was a plain array, so the watchEffect
tracked only what the FIRST registered getter read: with two panes reading
different refs, changing pane 2's source produced ZERO writes, and pane 2 caught
up only when pane 1's source happened to change. That is this helper's own bug
reappearing for any adopter whose panes read different sources. regions-mfe is
safe today because every getter reads the same cfg and spec -- which is exactly
why a reading could not find it.

entries is now shallowReactive, so register() re-runs the effect and every
getter is tracked. Also from the review:
  - stale entries are PRUNED, not skipped: an adopter that rebuilds its pane
    column would otherwise keep detached elements and re-run their getters on
    every pass forever;
  - a getter that throws is reported once per entry instead of being swallowed,
    since silence recreates the symptom this helper exists to fix;
  - the unused `tag` parameter is gone -- a documented check that does not exist
    misleads the next adopter;
  - the host argument now documents that it must be in the SAME tree as the
    panes, because Element.contains() does not cross a shadow boundary and
    passing the custom element would silently skip every write.

regions-dashboard rebuilt so public/ is servable at this sha for the browser
A/B. It is the only bundle that compiles the helper.
2026-09-29 17:32:33 +00:00
Doxa Maps
89bf1feb54 A composite's panes follow the outer profile-config, instead of freezing at mount
regions-mfe built each pane by creating the child element and writing its
profile-config once, inside onMounted, and nothing rewrote it. The pane is handed
a JSON STRING rather than a reactive object, so a later change to the OUTER
element's profile-config updated the composite's own cfg and reached nothing
below it. regions-dashboard is kept alive by v-show rather than remounted, so the
one code path that would have rewritten the attribute never runs again and the
panes stay in the old language for the whole session.

The leaf was never at fault: ProfileLoader's config is a computed over its prop,
lang is a computed over config, and a watchEffect drives appI18n on every change.
Every link is reactive -- it simply never receives a new prop.

library/composables/usePaneConfig.js owns the missing half: a composite registers
each pane element with a getter for its config, and a watchEffect re-forwards
whenever any reactive source that getter reads changes. It writes only when the
serialised config actually differs, because setting profile-config re-parses it
in the child and can remount what it drives -- an unconditional write per tick
would be a rerender loop. Stale elements (a rebuilt pane column) are skipped.

It is a library helper rather than three copies because regions-mfe, upg-100-mfe
and country-posters-mfe all assemble a config and set the attribute once; they
differ only in which keys they inherit, so each keeps its own
INHERITED_PAGE_KEYS and hands the result to the helper. This commit adopts it in
regions-mfe only; the other two are upg100's to adopt.

Two-sided, headlessly: a check that fails when a composite writes profile-config
on a child without re-forwarding names all THREE composites on development, and
on this branch regions-dashboard reports "re-forwarding=usePaneConfig" while the
other two still fail.

NOT VERIFIED IN A BROWSER YET. rega's LIVEDIAG is the falsifier: the nested
[profile-config] should show the OLD locale on development and the NEW one here.
2026-09-29 17:27:25 +00:00
Doxa Maps
57671ca465 Merge remote-tracking branch 'origin/development' into regions/handoff-integration
# Conflicts:
#	public/dx-maps/cities-internet/cities-internet.js
#	public/dx-maps/cities-internet/connectivity-list/index.html
#	public/dx-maps/cities-internet/connectivity-map/index.html
#	public/dx-maps/cities-internet/data-tables/index.html
#	public/dx-maps/country-map/country-map.js
#	public/dx-maps/country-map/country-map/index.html
#	public/dx-maps/country-posters/countries-map/index.html
#	public/dx-maps/country-posters/country-poster-wall/index.html
#	public/dx-maps/country-posters/country-posters-mfe/index.html
#	public/dx-maps/country-posters/country-posters.js
#	public/dx-maps/demo-map/demo-map.js
#	public/dx-maps/demo-map/demo-map/index.html
#	public/dx-maps/demo-map/demo-map/maplibre/index.html
#	public/dx-maps/doxa-maps-spa/manifest.json
#	public/dx-maps/doxa-research-map/doxa-research-map.js
#	public/dx-maps/doxa-research-map/research-map/index.html
#	public/dx-maps/doxa-research-map/research-map/maplibre/index.html
#	public/dx-maps/doxa-simple-map/doxa-simple-map.js
#	public/dx-maps/doxa-simple-map/doxa-simple-map/adoption/index.html
#	public/dx-maps/doxa-simple-map/doxa-simple-map/engagement/index.html
#	public/dx-maps/doxa-simple-map/doxa-simple-map/index.html
#	public/dx-maps/doxa-simple-map/doxa-simple-map/prayer/index.html
#	public/dx-maps/gospel-internet/board/board/index.html
#	public/dx-maps/gospel-internet/board/index.html
#	public/dx-maps/gospel-internet/board/india-china/index.html
#	public/dx-maps/gospel-internet/board/language-tree/index.html
#	public/dx-maps/gospel-internet/board/platforms/index.html
#	public/dx-maps/gospel-internet/board/project-700/index.html
#	public/dx-maps/gospel-internet/board/zero/index.html
#	public/dx-maps/gospel-internet/gospel-internet.js
#	public/dx-maps/manifest.json
#	public/dx-maps/regions-dashboard/region-dashboards/index.html
#	public/dx-maps/regions-dashboard/regions-dashboard.js
#	public/dx-maps/regions-dashboard/regions-list/index.html
#	public/dx-maps/regions-dashboard/regions-mfe/index.html
2026-09-29 16:33:55 +00:00
Doxa Maps
fe054b2c7f The region card stops overwriting the regions list's stylesheet
RegionSectionCard passed THREE arguments to useShadowStyles(css, label): the CSS,
then 'regions-list', then 'region-section-card'. The signature takes two, so
'regions-list' was the key and its own name was silently ignored.

useShadowStyles keys its <style> tag by label and, on a collision, OVERWRITES
the existing tag's text instead of adding one (useShadowStyles.js:60-61). This
card is a CHILD of regions-list, so the two share one shadow root: the cards
render after the fetch, mount second, find the list's tag under the shared key
and replace every .rl-* rule with the card's CSS.

Measured in the running page: .rl-root became position:static and .rl-scroll
overflow-y:visible, the shadow root held 3 style tags where a working tree holds
4, and 'rl-root{' appeared nowhere in it. The list therefore had no scroller of
its own, a wheel moved the pane column instead, and every region below the fold
was unreachable -- at 1440 Middle East, North America and Oceania; at 390
everything except Africa.

It was latent until the label became real: before the cyrillicHeading fix, a
dangling comma concatenated 'regions-list' INTO regions-list's own CSS, so the
list keyed by a content hash and the two never met. Fixing that rule turned a
silent collision into a visible break, which is why the live site is unaffected
and every sha after it is not.

Verified in the artifact, not the source: ,"regions-list") appears once in the
rebuilt bundle where it appeared twice, ,"region-section-card") now appears once,
and the .rl-* rules are still present.
2026-09-29 16:32:15 +00:00