In the dashboard, "every resource in this language" now filters the tables below it

The composite puts the map above its own tables, and the brief's third requirement is
that clicking a pin filters that table. It did not. "Every resource in German, Standard"
was an <a href="../data-tables/index.html?...">, which is exactly right when the map is
alone — and inside the dashboard it navigated AWAY, to the standalone tables page. The
filter applied correctly, 6 of 6,498, and the map was gone. Measured: the URL changed
from gospel-dashboard/index.html to data-tables/index.html.

The two halves now talk to each other. The dashboard provides a shared filter; the map
injects it and, when present, turns that link into a button that sets the filter and
scrolls the tables into view; the tables inject it and apply it in place, switching to
the links tab because a language filter means nothing on the others.

PROVIDED, NOT PROPPED, because both children must still work standalone. A child that
finds nothing injected keeps its old behaviour exactly — verified rather than assumed:
on resources-map/index.html the control is still an <a> pointing at
data-tables/index.html?tab=links&iso=deu.

Composed: the control is a BUTTON, the URL stays on gospel-dashboard/index.html, the
table banner reads "Showing 6 of 6,498 resources for German, Standard", and 6 rows with
66 cells render. Arrow changed from → to ↓ because the destination is now down the page
rather than away from it.

My first probe read "12 of 25" here and was wrong. Both halves share ONE shadow root in
the composite, so the map's own "Showing 8 of 25" matched the same pattern as the
table's banner. The probe now matches the table's specific wording and asserts rendered
rows and cells beside it. Same class of mistake as the three already recorded in
AGENT-BRIEF — a pattern that is unambiguous on one page can be ambiguous in a composite.

All five profiles still mount clean; gospel-dashboard 68,459 chars, 54 links, 0 errors.
This commit is contained in:
doxa-maps-88655c70 2026-09-24 14:47:24 +00:00
parent 0c64fdd856
commit c63d92542c
10 changed files with 86 additions and 35 deletions

View file

@ -77,6 +77,21 @@ function clearFilter() {
const active = ref(TABS.some((t) => t.id === params.get('tab')) ? params.get('tab') : 'links')
const current = computed(() => TABS.find((t) => t.id === active.value) || TABS[0])
// ═══ COMPOSED MODE ═══ when these tables are stacked under the map in the dashboard, the
// map asks them to filter in place instead of linking to this page. Null on the standalone
// page, where the URL remains the only source of a filter and nothing here changes.
const composedFilter = inject('composedFilter', null)
if (composedFilter) {
watch(composedFilter, (f) => {
if (!f) return
fIso.value = String(f.iso || '').trim().toLowerCase()
fCountry.value = String(f.country || '').trim().toUpperCase()
fLabel.value = String(f.label || '').trim()
// The links tab is the one that carries URLs; a filter is meaningless on the others.
if (TABS.some((t) => t.id === 'links')) active.value = 'links'
})
}
// Fetched at runtime — see the note in cities-internet/data-tables.vue. These two
// tables alone are 2.8MB of JSON; inlining them blocked first paint.
const cache = ref({})

View file

@ -30,6 +30,20 @@ import StackLayout from '@map/layouts/StackLayout.vue'
import ResourcesMap from './resources-map.vue'
import DataTables from './data-tables.vue'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { ref, provide } from 'vue'
// ═══ THE TWO HALVES TALK TO EACH OTHER ═══════════════════════════════════════════════
// "Every resource in <language>" is a LINK to the standalone tables page, which is right
// when the map is alone. Inside this composite the tables are already on screen, directly
// below, so following that link navigated AWAY from the dashboard — the filter applied
// correctly (6 of 6,498 for German) but the map was gone and the composite destroyed.
// Measured: the URL changed from gospel-dashboard/index.html to data-tables/index.html.
//
// Provided rather than propped because the two children are separate profiles that must
// each still work standalone: a child that finds nothing injected keeps its old behaviour
// exactly, so nothing changes for resources-map or data-tables on their own pages.
const composedFilter = ref(null)
provide('composedFilter', composedFilter)
const sections = [
{ key: 'map', title: '' },

View file

@ -709,6 +709,22 @@ const tableLink = (q) => {
return `../data-tables/index.html?${p.toString()}`
}
// When this map is composed ABOVE its own tables, "see all of these" must not be a
// navigation — the tables are already on screen and leaving the page throws the map away.
// Null on the standalone page, where the link is exactly right.
const composedFilter = inject('composedFilter', null)
const composed = computed(() => !!composedFilter)
function filterComposedTables(q) {
if (!composedFilter) return
composedFilter.value = { ...q, at: Date.now() }
// Bring the tables into view; without this the filter applies somewhere the reader
// cannot see and the click reads as having done nothing.
const host = document.querySelector('gospel-resources-dashboard, gospel-resources-map')
const root = host && host.shadowRoot
const tables = root && root.querySelector('.gd-tables')
if (tables && tables.scrollIntoView) tables.scrollIntoView({ behavior: 'smooth', block: 'start' })
}
useShadowStyles(
'.gm{position:absolute;inset:0;overflow:hidden}' +
'.gm-map{position:absolute;inset:0}' +
@ -1210,8 +1226,14 @@ useShadowStyles(
</p>
</div>
<a class="gm-tablink" :href="tableLink({ iso: selectedGroup.iso639_3 || '', label: selectedGroup.language || '' })"
v-if="selectedGroup.iso639_3">
<!-- A link when standalone, a filter when composed. Same words either way, because
the promise is the same; only the destination differs. -->
<button v-if="selectedGroup.iso639_3 && composed" type="button" class="gm-tablink"
@click="filterComposedTables({ iso: selectedGroup.iso639_3, label: selectedGroup.language || '' })">
Every resource in {{ selectedGroup.language }} ↓
</button>
<a v-else-if="selectedGroup.iso639_3" class="gm-tablink"
:href="tableLink({ iso: selectedGroup.iso639_3 || '', label: selectedGroup.language || '' })">
Every resource in {{ selectedGroup.language }} →
</a>

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-/WMe9wIDZq3ZUN39lgfiXaCkMZJw8a6zk1G8Tb6QsTJgMQzhjEG0gDp9MGa9xuS1" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-vNmX1DM5q5xc3UV7jhe+smRxDkndQf6XWL2N+EFcrJACMDfGOSsIiyCxwXsOeU9v" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-/WMe9wIDZq3ZUN39lgfiXaCkMZJw8a6zk1G8Tb6QsTJgMQzhjEG0gDp9MGa9xuS1" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-vNmX1DM5q5xc3UV7jhe+smRxDkndQf6XWL2N+EFcrJACMDfGOSsIiyCxwXsOeU9v" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -40,7 +40,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-/WMe9wIDZq3ZUN39lgfiXaCkMZJw8a6zk1G8Tb6QsTJgMQzhjEG0gDp9MGa9xuS1" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-vNmX1DM5q5xc3UV7jhe+smRxDkndQf6XWL2N+EFcrJACMDfGOSsIiyCxwXsOeU9v" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

File diff suppressed because one or more lines are too long

View file

@ -26,7 +26,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-/WMe9wIDZq3ZUN39lgfiXaCkMZJw8a6zk1G8Tb6QsTJgMQzhjEG0gDp9MGa9xuS1" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-vNmX1DM5q5xc3UV7jhe+smRxDkndQf6XWL2N+EFcrJACMDfGOSsIiyCxwXsOeU9v" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -29,7 +29,7 @@
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
<!-- Bundle IIFE — registers the <gospel-resources-map> custom element. -->
<script src="../gospel-resources.js" integrity="sha384-/WMe9wIDZq3ZUN39lgfiXaCkMZJw8a6zk1G8Tb6QsTJgMQzhjEG0gDp9MGa9xuS1" crossorigin="anonymous"></script>
<script src="../gospel-resources.js" integrity="sha384-vNmX1DM5q5xc3UV7jhe+smRxDkndQf6XWL2N+EFcrJACMDfGOSsIiyCxwXsOeU9v" crossorigin="anonymous"></script>
<script>
// Mapbox token resolution — the A3 ladder, in precedence order:

View file

@ -591,7 +591,7 @@
"bundle": "gospel-resources",
"scriptUrl": "./gospel-resources/gospel-resources.js",
"output": "doxa-maps/gospel-resources/gospel-resources.js",
"integrity": "sha384-/WMe9wIDZq3ZUN39lgfiXaCkMZJw8a6zk1G8Tb6QsTJgMQzhjEG0gDp9MGa9xuS1",
"integrity": "sha384-vNmX1DM5q5xc3UV7jhe+smRxDkndQf6XWL2N+EFcrJACMDfGOSsIiyCxwXsOeU9v",
"elements": [
"gospel-resources-map"
],
@ -611,7 +611,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/charts/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-/WMe9wIDZq3ZUN39lgfiXaCkMZJw8a6zk1G8Tb6QsTJgMQzhjEG0gDp9MGa9xuS1\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-vNmX1DM5q5xc3UV7jhe+smRxDkndQf6XWL2N+EFcrJACMDfGOSsIiyCxwXsOeU9v\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"charts\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -625,7 +625,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/data-tables/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-/WMe9wIDZq3ZUN39lgfiXaCkMZJw8a6zk1G8Tb6QsTJgMQzhjEG0gDp9MGa9xuS1\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-vNmX1DM5q5xc3UV7jhe+smRxDkndQf6XWL2N+EFcrJACMDfGOSsIiyCxwXsOeU9v\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"data-tables\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -639,7 +639,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/gospel-dashboard/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-/WMe9wIDZq3ZUN39lgfiXaCkMZJw8a6zk1G8Tb6QsTJgMQzhjEG0gDp9MGa9xuS1\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"gospel-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-vNmX1DM5q5xc3UV7jhe+smRxDkndQf6XWL2N+EFcrJACMDfGOSsIiyCxwXsOeU9v\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"gospel-dashboard\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -653,7 +653,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/resources-list/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-/WMe9wIDZq3ZUN39lgfiXaCkMZJw8a6zk1G8Tb6QsTJgMQzhjEG0gDp9MGa9xuS1\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-vNmX1DM5q5xc3UV7jhe+smRxDkndQf6XWL2N+EFcrJACMDfGOSsIiyCxwXsOeU9v\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-list\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
},
{
@ -667,7 +667,7 @@
"scriptUrl": "./gospel-resources/gospel-resources.js",
"embed": {
"iframe": "<iframe src=\"./gospel-resources/resources-map/index.html?utm_source=YOUR_SOURCE\" width=\"100%\" height=\"600\" style=\"border:none;border-radius:12px;\" allowfullscreen></iframe>",
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-/WMe9wIDZq3ZUN39lgfiXaCkMZJw8a6zk1G8Tb6QsTJgMQzhjEG0gDp9MGa9xuS1\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./gospel-resources/gospel-resources.js\" integrity=\"sha384-vNmX1DM5q5xc3UV7jhe+smRxDkndQf6XWL2N+EFcrJACMDfGOSsIiyCxwXsOeU9v\" crossorigin=\"anonymous\"></script>\n<gospel-resources-map profile-config='{\"profile\":\"resources-map\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></gospel-resources-map>"
}
}
]