diff --git a/library/scripts/posters/series.json b/library/scripts/posters/series.json index c309f765e..a515b4f0b 100644 --- a/library/scripts/posters/series.json +++ b/library/scripts/posters/series.json @@ -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", diff --git a/plugin-designs/country-posters/countries-map.vue b/plugin-designs/country-posters/countries-map.vue index 26c9231eb..aee3f89d3 100644 --- a/plugin-designs/country-posters/countries-map.vue +++ b/plugin-designs/country-posters/countries-map.vue @@ -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 '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 diff --git a/plugin-designs/country-posters/country-poster-wall.vue b/plugin-designs/country-posters/country-poster-wall.vue index d3160fdde..eeb263c2f 100644 --- a/plugin-designs/country-posters/country-poster-wall.vue +++ b/plugin-designs/country-posters/country-poster-wall.vue @@ -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. diff --git a/plugin-designs/country-posters/country-posters-mfe.vue b/plugin-designs/country-posters/country-posters-mfe.vue index 87a5dfbc4..3662bc10f 100644 --- a/plugin-designs/country-posters/country-posters-mfe.vue +++ b/plugin-designs/country-posters/country-posters-mfe.vue @@ -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. // diff --git a/plugin-designs/country-posters/index.js b/plugin-designs/country-posters/index.js index f0b163a6f..db0f31ef2 100644 --- a/plugin-designs/country-posters/index.js +++ b/plugin-designs/country-posters/index.js @@ -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.