Privacy: neutral comments in Country Posters (no quotes, date tags or agent names)

Comments only; behaviour unchanged. Each rewritten line keeps its technical fact and drops the attribution:
- design-note and brief quotations restated as plain design statements (country-poster-wall.vue, index.js,
  countries-map.vue);
- "(qa: <date> …)" tags and "user feedback / user reported" attributions removed (countries-map.vue);
- fleet agent names replaced by what was measured (countries-map.vue, country-posters-mfe.vue);
- series.json's India-state note: the approval date is gone; the wording stays the editable default.
This commit is contained in:
Doxa Maps 2026-10-01 00:51:05 +00:00
parent 01c390a929
commit 202a0244e2
5 changed files with 29 additions and 47 deletions

View file

@ -160,8 +160,8 @@
"A state poster is a country poster one level down: the Country Posters wall prints one for each of India's",
"states and union territories that has people groups listed (34 of 36; data/people-groups/india-states.json).",
"The country series' text cannot be reused: it would call Gujarāt's poster one of the ~170 'Country Maps',",
"one sheet for each country. The term is 'state and union territory', never 'province'. Approved as the",
"default wording 2026-09-30 and shown as editable; a change is a strings-only follow-up."
"one sheet for each country. The term is 'state and union territory', never 'province'. This is the default",
"wording and stays editable; a change is a strings-only follow-up."
],
"strings": {
"map_page_kicker": "PRAY FOR THIS STATE",

View file

@ -20,11 +20,8 @@
* region polygons, the region colors, the scroll-edge accessibility strips, the
* geocoder, the clustering, the detail panel and the 3-tier tree for free.
*
* > design note 2026-08-01: "we want to reuse all of the region polygons, all of the
* > region colors, the same exact legend, the same exact region colors, the same
* > exact code for the region's application profile. But we parameterize it
* > slightly to make the default tab that opens up as the country's tab. That's
* > the only real change."
* It reuses the region polygons, the region colours, the same legend and the same code as the regions
* application profile, parameterized only so the default tab is the countries tab.
*
* THE THREE DIFFERENCES (search "COUNTRIES-MAP DIFFERENCE" to find them):
* 1. The WAGF Regions tab is RELABELED "Countries" — same doxaRegion strategy,
@ -200,7 +197,7 @@ useShadowStyles(`
selector the column got no position or width at all under MapLibre. */
.mapboxgl-ctrl-top-left, .maplibregl-ctrl-top-left { top:12px!important;inset-inline-start:8px!important;width:380px!important;max-width:380px!important;z-index:1200!important; }
.mapboxgl-ctrl-top-left .mapboxgl-ctrl, .maplibregl-ctrl-top-left .maplibregl-ctrl { margin:0!important;float:none!important; }
/* Pill geometry + PPLR-matched focus ring (qa: 2026-05-02). border-radius
/* Pill geometry + PPLR-matched focus ring. border-radius
999px matches PPLR's .pgc-input-wrap; focus-within ring uses PPLR's
accent rgba(59,70,61,0.18) so the search bar reads as one column with
the SemanticTreeLegend below it. Input font-size:13px / placeholder
@ -276,8 +273,8 @@ useShadowStyles(`
/* isolation:isolate forces .rm-root to form its own stacking context so
internal z-indexes (geocoder 1200, legend 1000, toolbar 10) compare only
against each other and do NOT leak out to compete with page-level
elements like the site nav / hamburger menu (qa: 2026-05-04 user
reported the geocoder pill rendering above the page menu). Without
elements like the site nav / hamburger menu (the geocoder pill once
rendered above the page menu). Without
this, the host element doesn't establish a stacking context and the
internal 1200 wins against any page elements with z-index < 1200. */
.rm-root { position:absolute;inset:0;display:flex;flex-direction:column;overflow:hidden;background:#ffffff;color:#1f2937;isolation:isolate; }
@ -299,7 +296,7 @@ useShadowStyles(`
top-left corner than the standard 16px gutter; end side stays 16px.
Mobile @media block below resets both sides back to 16px since the
mobile slot uses a smaller radius and the existing 16px is correct
there (qa: 2026-05-04 user feedback — desktop only). Active-tab
there (desktop only). Active-tab
scrollIntoView still uses 16px scroll-padding which is fine — the
tab is already visible, just nudged by the static padding. */
padding-inline-start:36px;
@ -465,14 +462,8 @@ const FEATURES = {
// (a colorStrategy + a legend type + its own tree). The country app needs exactly
// ONE of them — the WAGF Regions profile — and none of the other six.
//
// > design note 2026-08-01: "I don't need every single tab from the research map for
// > the countries app. The country's application only needs the region's
// > application profile… the research map has nested application profiles, and not
// > all of them are needed. The only one we need is the regions tab application
// > profile. The countries map should not display any tabs. It should just open by
// > default to the regions tab reused, with the slight change to the application
// > profile. That slight change is that the legend opens by default to the
// > countries [tier]."
// The countries app needs only the regions application profile, not every research-map tab: no tab bar,
// the regions tab reused, its legend opening on the countries tier.
//
// So ALL_TABS holds that ONE profile, and showTabBar is false — a tab bar with a
// single tab is chrome that does nothing. Everything else about the profile is
@ -832,16 +823,13 @@ const {
// Default the SemanticTreeLegend internal tab per outer-tab (legend type):
// • language-families → 1 (Language) — users know "Language" better than "Language Family"
// • affinity-blocks → 0 (Bloc) — users know affinity blocks better than clusters
// (qa: 2026-05-08 user feedback)
// Reset on every outer-tab change so switching between Languages and Affinity
// Blocks lands on the right inner tab regardless of where the previous tab left it.
// COUNTRIES-MAP DIFFERENCE #3 — boot the legend on the COUNTRY tier.
// REGIONS_TABS = [region(0), block(1), country(2)], so 2 is the Country tier.
// research-map boots doxa-regions on Region(0); this profile lands users on the
// country-level view they came for.
// design note 2026-08-01: "when you open the map it doesn't open to the regions inside
// of the semantic tree legend, but it opens to the country's semantic level in the
// legend… by default, when you open the map, it opens on the countries."
// The map opens on the COUNTRY level of the semantic-tree legend, not on its regions.
pplrInstance.activeTab.value = 2
function _defaultInnerTabForLegendType(t) {
if (t === 'doxa-regions') return 2 // Country — the countries-map default tier
@ -939,7 +927,7 @@ const legendTitle = computed(() => {
// @select to the same handler as the desktop instance — without this,
// mobile row-clicks updated only instance.selection (shared via inject)
// and never reached mapStore.selectFamily → applyDimFilter, so the
// dim-on-select / restore-on-deselect path never ran (qa: 2026-05-03 iter-18).
// dim-on-select / restore-on-deselect path never ran.
provide('onSemanticTreeSelect', (node) => onSemanticTreeSelect(node))
// The SAME export handler the desktop legend gets via @export, reaching the mobile sheet the
// same way @select does. Without it <LegendMobile>'s embedded SemanticTreeLegend rendered with
@ -959,7 +947,7 @@ const EXPERIMENTAL_EXPORTS = EXPORT_FEATURE_ENABLED && experimentalExportsOn(pro
provide('legendExportCards', EXPERIMENTAL_EXPORTS)
// Resolves the active tab's strategy → color for a given feature, used by
// useSelectedPin.js so the selection-pin matches the underlying dot color
// (qa: 2026-05-06 user feedback). research-mfe tab strategyKeys already
// research-mfe tab strategyKeys already
// match COLOR_MODES values directly, so no mapping needed.
provide('getActivePinColor', (properties) => {
const strat = getColorStrategy(activeTab.value?.colorStrategy ?? 'languageFamily')
@ -1103,7 +1091,7 @@ function onSemanticTreeSelect(node) {
}
// ── Pin opacity: ONE writer (the research-map pattern, applyPinOpacity) ─────────
// matchExpr dims to it (matched 1, others 0); null clears the dim. Two things it does that the 14 raw
// writes it replaces did not (upg100's drift classification; rega's profile on research-map):
// writes it replaces did not (a drift classification and a performance profile on research-map):
// · READ BEFORE WRITE. One family click reached onSemanticTreeSelect AND the selectedFamily watcher ->
// applyDimFilter, rewriting the same value 4+ times, each a per-feature re-upload. Compared as text
// against the LIVE value (never a remembered one, which would go stale on a style reload).
@ -1222,7 +1210,7 @@ async function openPacket(variant = 'full') {
import('@map/composables/poster/useMapPoster.js'),
])
// ★ THE PACKET IS THE SELECTION, not the first 50 loaded groups (rega: "Asia" printed Sudan).
// ★ THE PACKET IS THE SELECTION, not the first 50 loaded groups (a packet for "Asia" once 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.
@ -1358,8 +1346,8 @@ const isDark = computed(() => uiStore.isDarkTheme)
const isDarkBasemap = computed(() =>
THEME_TOGGLE ? uiStore.isDarkTheme : FIXED_BASEMAP.lightPreset === 'night')
// Theme-aware data-pin outline (qa: 2026-05-06 user feedback — Joshua Project
// uses a thin near-black outline that's barely visible; reverse-engineered).
// Theme-aware data-pin outline (Joshua Project uses a thin near-black outline
// that's barely visible; reverse-engineered).
// In dark mode the outline goes white so pins stay visible against dark
// basemaps. Boot style is light-v11, so the initial circle-stroke-color
// in addLanguageFamilyLayer is already 'rgba(0,0,0,0.45)' — this watcher
@ -1411,7 +1399,7 @@ async function ensureRegionsLoaded() {
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
// compile (measured 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
@ -1504,7 +1492,7 @@ function switchTab(tabId) {
// LegendRows emits a window event when a row is clicked; we fly the map there.
// ─── "Show on map" FROM THE POSTER WALL ──────────────────────────────────────────────────────────
// ★ THIS LINK DID NOT EXIST. country-posters-mfe's onShowOnMap() switched to the Map view and
// returned, so the camera stayed at world view and nothing was selected -- measured by rega. The
// returned, so the camera stayed at world view and nothing was selected -- as measured. The
// shell now records the requested country as a `select-country` ATTRIBUTE on this pane element and
// also announces it by event; both arrive here.
//
@ -1518,7 +1506,7 @@ let _selectCountryObserver = null
/** The ONE apply path. Returns true when the request was satisfied. */
function applySelectCountry(countryId) {
// ★ DESELECT RESTORES THE VIEW. rega measured that a second press left the camera on India:
// ★ DESELECT RESTORES THE VIEW. a second press measurably left the camera on India:
// this branch used to clear the flag and return, so toggling OFF did nothing by construction.
// PosterWall's button IS a toggle (it tracks lastShown and sends listId: null on the second
// press), so a control the user can turn on but not off reads as broken even when the first
@ -2082,7 +2070,7 @@ function applyContainerScale(width) {
el.style.setProperty('--map-font-md', '13px')
el.style.setProperty('--map-font-sm', '11px')
// 380px matches SemanticTreeLegend's .stl-panel + the geocoder pill
// (qa: 2026-05-02 — pin-detail card was 400px and looked WIDER than the
// (the pin-detail card was 400px and looked WIDER than the
// language-family legend below it; unify to 380px so the swap reads
// as same-card different-content).
el.style.setProperty('--map-legend-width', '380px')
@ -2108,7 +2096,7 @@ onMounted(async () => {
// any code branching on isMobile (LegendDesktop's internal LegendMobile
// mount, mobile-only feature gates, etc.) runs the desktop path.
// The CSS @media path on .rm-legend-mobile-slot still works either way,
// but isMobile-driven branches don't. (qa: 2026-05-02 mobile audit.)
// but isMobile-driven branches don't.
uiStore.init?.()
// COUNTRIES-MAP DIFFERENCE #2 (boot half) — research-map forces tabs[0] (Prayer)
// here on mount, which silently overrode the ref's initial value. THIS is the

View file

@ -15,11 +15,8 @@
* rendering lives in the shared @map/components/poster/PosterWall.vue — this
* profile supplies only its data and its taxonomy.
*
* > design note 2026-08-01: "the poster wall should be semantically organized by the
* > regions… there's going to be way too many countries, so we'll need to be able
* > to collapse the regions and see how many countries there are in each of the
* > regions, because people don't want to just look at, like, scroll through
* > hundreds of countries."
* The wall is organized by REGION, and each region collapses and shows how many countries it holds, so a
* reader never has to scroll through hundreds of countries.
*
* WHY COLLAPSE IS NOT A NICETY — measured from the live data:
* Asia 28 countries / 1309 groups
@ -30,8 +27,7 @@
* collapsing, the wall is a scroll of mostly single-entry cards.
*
* SORTING IS THE ANSWER TO SPARSE SHEETS, not a template variant.
* > Brief: "if it has a one-people-group on it, we still want a one-people-group
* > map generated… those get sorted to the bottom of the list."
* A country with a single people group still gets its own map; it simply sorts to the bottom.
* The shared wall always sorts sections AND cards by descending count, so the
* dense countries lead and the long tail sinks. There is no floor.
*/
@ -205,7 +201,7 @@ const groupBy = (c) => {
const collapsedBy = (sec) => sec.total < 60
// The not-yet reason travels WITH the record, so the exact number the wall prints
// can never drift from the data. Brief: "it should say the exact number in our data."
// can never drift from the data: the wall states the exact number the data holds.
// The reason is shown on the card and in the print HUD, so it speaks the reader's language: the data's
// unsupportedReason is an English note (and a flag), not display text. The card already states the
// count above it, so the sentence needs no number and no plural agreement.

View file

@ -136,7 +136,7 @@ function paneElement(pane) {
// `inert` on every pane except the active one: the same contract as upg-100-mfe (4642e78d). The panes
// are stacked in one scroll-snap column, so the hidden pane is still full-size geometry; inert tells
// PosterWall (67e9ede5) the wall is out of view so it forgets its shown card, and a second
// "Show on map" after Map -> back SHOWS the country again instead of toggling it off (rega row 79).
// "Show on map" after Map -> back SHOWS the country again instead of toggling it off.
// inert, not display:none: hiding would tear down the live map.
function markActivePane(activeId) {
if (!column || !activeId) return
@ -170,7 +170,7 @@ function onShowOnMap(e) {
if (d.instanceId && d.instanceId !== instanceId.value) return
go('map')
// ★ THE FORWARDING THAT WAS NEVER WRITTEN. This handler used to end at go('map'), so the tab
// changed and the map was never told anything at all -- rega measured exactly that: "the camera
// changed and the map was never told anything at all -- measured exactly: "the camera
// stays at world view, nothing selected". It is not a timing bug and no amount of waiting fixes
// it; the link simply did not exist.
//

View file

@ -20,9 +20,7 @@ import { defineMapElement } from '@map/elements/defineMapElement.js'
* country-poster-wall.vue the poster grid, grouped by region, collapsible
*
* WHY A SEPARATE BUNDLE rather than two more profiles in my-upg-100-list:
* > design note 2026-08-01: "I don't mind creating multiple application profiles, one
* > for each application. I do mind that we really need to make sure every
* > component that we're [re]usable is added to the library."
* Several application profiles are fine; every REUSABLE component belongs in the library.
* PROFILES duplicate; COMPONENTS get promoted to library/ and parameterized. The
* wall here is @map/components/poster/PosterWall.vue — the same component the
* 100-list renders — differing only in data, taxonomy and copy.