From a7c9e9557f399d4f6ee63998ad88492c50798106 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sun, 27 Sep 2026 01:46:53 +0000 Subject: [PATCH] The prayer map's legends see the pins it draws rega, row 46 at 835ec1ac: on the prayer map neither the shared-point sentence nor the standing pin note appeared. research-map and countries-map provide normalizedPeopleGroups to their legends; the prayer map did not, so its legends fell back to dataStore's cache, which holds the RAW API rows: field names the co-location index cannot read, and no GeoJSON geometry for the standing-note detector to find. useMapData now provides the normalized rows it draws, so every map built on it offers them. LegendMobile and SemanticTreeLegend read them first (the cache stays the fallback), and the detector accepts a row with a name and a position as well as a GeoJSON point. check:approx-pin-note asserts the provide and the normalized-row match; the provide check fails on the previous useMapData. --- internal/check-approx-pin-note.mjs | 8 ++++++++ library/components/legends/LegendMobile.vue | 2 ++ library/components/legends/SemanticTreeLegend.vue | 12 +++++++----- library/composables/useMapData.js | 8 +++++++- library/utils/colocation.js | 2 +- 5 files changed, 25 insertions(+), 7 deletions(-) diff --git a/internal/check-approx-pin-note.mjs b/internal/check-approx-pin-note.mjs index 88f0eedee..12c0e3443 100644 --- a/internal/check-approx-pin-note.mjs +++ b/internal/check-approx-pin-note.mjs @@ -37,6 +37,14 @@ check('a rendered (tile-rounded) feature still finds its co-located groups', col const twin = buildColocationIndex([...rows, { name: 'Koraga', latitude: 40, longitude: 40 }, { name: 'Other', latitude: 40, longitude: 40 }]) const renderedFar = { type: 'Feature', properties: { name: 'Koraga' }, geometry: { type: 'Point', coordinates: [40.0007, 39.9994] } } check('a repeated name resolves to the nearest roster point', colocatedWith(twin, renderedFar).join() === 'Other', colocatedWith(twin, renderedFar)) +// The rows a map DRAWS are normalized people groups (name + latitude/longitude, no geometry), and every +// map built on useMapData must offer them to its legends: the prayer map did not, so its legends read the +// raw API cache and neither the shared-point sentence nor the standing note appeared (rega, row 46 at 835ec1ac). +const drawn = [{ name: 'A', latitude: 12.85, longitude: 75.25 }, { name: 'B', latitude: '12.85', longitude: '75.25' }] +const clickedA = { type: 'Feature', properties: { name: 'A' }, geometry: { type: 'Point', coordinates: [75.2504, 12.8497] } } +check('normalized rows (no geometry) index and match a clicked pin', colocatedWith(buildColocationIndex(drawn), clickedA).join() === 'B', colocatedWith(buildColocationIndex(drawn), clickedA)) +const umd = readFileSync(join(ROOT, 'library/composables/useMapData.js'), 'utf8').replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '') +check('useMapData provides the rows it draws to the legends', /provide\(\s*'normalizedPeopleGroups'\s*,\s*normalizedPeopleGroups\s*\)/.test(umd), 'no provide in useMapData') const { createServer } = await import('vite') const { default: vue } = await import('@vitejs/plugin-vue') diff --git a/library/components/legends/LegendMobile.vue b/library/components/legends/LegendMobile.vue index 8ab353806..38ff2aa91 100644 --- a/library/components/legends/LegendMobile.vue +++ b/library/components/legends/LegendMobile.vue @@ -526,7 +526,9 @@ const showPullTab = computed(() => uiStore.showPullTab) const legendMode = computed(() => uiStore.legendMode) const selectedPeopleGroup = computed(() => uiStore.selectedPeopleGroup) // APPROXPIN: the other groups on the selected pin's coordinate, from the roster THIS map draws. +const drawnPeopleGroups = inject('normalizedPeopleGroups', null) const colocation = computed(() => { + if (drawnPeopleGroups?.value?.length) return buildColocationIndex(drawnPeopleGroups.value) const rows = [] for (const id in (dataStore?.sources || {})) { const src = dataStore.sources[id]; if (src?.features?.length) rows.push(...src.features) } return buildColocationIndex(rows) diff --git a/library/components/legends/SemanticTreeLegend.vue b/library/components/legends/SemanticTreeLegend.vue index bcc089ab2..174e2d0d1 100644 --- a/library/components/legends/SemanticTreeLegend.vue +++ b/library/components/legends/SemanticTreeLegend.vue @@ -24,6 +24,7 @@ import { useShadowStyles } from '../../composables/useShadowStyles.js' import { useInstance } from '../../composables/usePplrInstance.js' import LegendProgressBar from './LegendProgressBar.vue' import ApproxPinNote from './ApproxPinNote.vue' +import { pointOf, nameOf } from '../../utils/colocation.js' // .stl-export-backdrop is an invisible click-catcher ("captures the next click"), so when it is // clipped nothing LOOKS wrong — but inside the cap-5 shell contain:layout on .vtl-content limits // it to the content pane (measured 1024x793 @ (256,107) at 1280x900), so a click on the rail or @@ -126,14 +127,15 @@ const progressData = computed(() => props.progress ?? unref(injectedProgress) ?? const displayTitle = computed(() => progressData.value ? t('legend.progress.title') : props.title) const stlIsDark = computed(() => (instance?.theme.value || 'dark') === 'dark') const pinDataStore = inject('dataStore', null) +const drawnPeopleGroups = inject('normalizedPeopleGroups', null) +// A people-group pin is a row (or GeoJSON feature) with a name AND a position. The rows the map draws are +// asked first; the dataStore cache is the fallback (it can hold raw API rows or GeoJSON features). +const isPin = (r) => !!(r && nameOf(r) && pointOf(r)) const showPinNote = computed(() => { if (props.pinNote !== null) return props.pinNote + if (drawnPeopleGroups?.value?.length) return drawnPeopleGroups.value.slice(0, 20).some(isPin) const sources = pinDataStore?.sources || {} - for (const id in sources) { - const f = sources[id]?.features?.[0] - const p = f?.properties || {} - if (f?.geometry?.type === 'Point' && (p.name || p.peopleName || p.PeopNameInCountry)) return true - } + for (const id in sources) if ((sources[id]?.features || []).slice(0, 20).some(isPin)) return true return false }) // A click on a bar span selects the matching legend row through the SAME handler diff --git a/library/composables/useMapData.js b/library/composables/useMapData.js index 22866a635..928e79a19 100644 --- a/library/composables/useMapData.js +++ b/library/composables/useMapData.js @@ -3,7 +3,7 @@ // Vue 3 Composition API - ES Module with NPM packages // Import Vue functions from NPM package -import { ref, computed } from 'vue'; +import { ref, computed, provide, getCurrentInstance } from 'vue'; import { applyLocalPrayer } from './usePrayedLocally.js'; // Static lookup: language label (e.g. "Telugu") → language family ("Dravidian"). // Used to derive `languageFamily` for pray-tools rows where the upstream returns @@ -136,6 +136,12 @@ export function useMapData(options) { // Data arrays (will be marked as raw for performance) const peopleGroups = ref([]); const normalizedPeopleGroups = ref([]); + // THE ROWS THE MAP DRAWS, offered to the legends below it. research-map and countries-map provided + // this themselves; the prayer map did not, so its legends fell back to the raw API cache, whose field + // names the pin-note co-location index cannot read, and the shared-point sentence and the standing + // pin note never showed there (rega, row 46). Providing it here covers every map built on useMapData; + // a component that also provides it passes this same ref. + if (getCurrentInstance()) provide('normalizedPeopleGroups', normalizedPeopleGroups); const regionsData = ref(null); const languageFamiliesData = ref([]); const doxaRegionsData = ref([]); diff --git a/library/utils/colocation.js b/library/utils/colocation.js index 659c9b81e..c68b64210 100644 --- a/library/utils/colocation.js +++ b/library/utils/colocation.js @@ -17,7 +17,7 @@ export function pointOf(r) { const lat = c ? c[1] : (p.latitude ?? p.lat), lng = c ? c[0] : (p.longitude ?? p.lng ?? p.lon) return Number.isFinite(Number(lat)) && Number.isFinite(Number(lng)) && lat !== '' && lng !== '' ? [Number(lat), Number(lng)] : null } -const nameOf = (r) => { const p = (r && (r.properties || r)) || {}; return p.name || p.peopleName || p.PeopNameInCountry || '' } +export const nameOf = (r) => { const p = (r && (r.properties || r)) || {}; return p.name || p.peopleName || p.PeopNameInCountry || '' } /** Build once per roster: Map, plus `byName` (name -> [[lat, lng, key]]). */ export function buildColocationIndex(rows) {