diff --git a/plugin-designs/gospel-internet/board.vue b/plugin-designs/gospel-internet/board.vue
index b1105c883..1e2195659 100644
--- a/plugin-designs/gospel-internet/board.vue
+++ b/plugin-designs/gospel-internet/board.vue
@@ -23,6 +23,8 @@
*/
import { ref, computed, onMounted, onBeforeUnmount, watch, getCurrentInstance } from 'vue'
import { useDashboardLayout } from '@map/composables/useDashboardLayout.js'
+import { useShareableView } from '@map/composables/useShareableView.js'
+import { SHARE_FACETS } from './src/model.js'
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { listLabel } from '@map/lists/peopleGroupList.js'
import { useBoard } from './src/useBoard.js'
@@ -66,6 +68,11 @@ onBeforeUnmount(() => { if (selTimer) clearTimeout(selTimer) })
// board opens already in its state: no flash of the default view, and the first render mounts the panel
// the preset asks for (the language tree included) where a headless mount check can see it.
board.readUrl()
+// SHARE LINKS FROM THE KIT (useShareableView, query mode): the filters and the typed search live in the
+// URL under the board's published names (?access= ?band= ?country= …, the alias map keeps links already
+// shared working), restored on every load, written ONLY on our own leaves (urlState:'query') so a
+// partner's address bar is never rewritten. facetKeys is explicit: its default reads nothing on a cold load.
+useShareableView(bus, { mode: 'query', write: board.profileConfig.value?.urlState === 'query', facetKeys: SHARE_FACETS })
// ── responsive: one panel at a time under 760px (ResizeObserver on the shell, not @container: a
// container query cannot target the container itself, which is unreliable inside a shadow root) ──────
diff --git a/plugin-designs/gospel-internet/scripts/check-gospel-internet-model.mjs b/plugin-designs/gospel-internet/scripts/check-gospel-internet-model.mjs
index 3f2025ccc..00c15b66d 100644
--- a/plugin-designs/gospel-internet/scripts/check-gospel-internet-model.mjs
+++ b/plugin-designs/gospel-internet/scripts/check-gospel-internet-model.mjs
@@ -141,6 +141,19 @@ for (const n of ix.nodes) if (!n.p) { const x = scZero.get(n.id); gy += x.g + x.
ok(gy === 0, `with only zero-access groups in scope, no family counts a green or yellow language (${gy})`)
bus.clearFilters()
+// 12. share links through the kit (useShareableView): the board's published names map to its bus keys,
+// and a link already shared restores (single values, and multi-values written with '|')
+const SV = await import(resolve(REPO, 'library/composables/useShareableView.js'))
+const r1 = SV.parseQuery('?access=bridge&country=IND&wayin=deaf&app=Telegram%20Messenger', M.SHARE_FACETS)
+ok(r1.filters.access_level === 'bridge' && r1.filters.country === 'IND' && r1.filters.way_in === 'deaf' && r1.filters.messaging === 'Telegram Messenger',
+ 'kit parser maps ?access= ?wayin= ?app= to the bus keys (old single-value links restore)')
+const r2 = SV.parseQuery('?band=yes|partial', M.SHARE_FACETS)
+ok(Array.isArray(r2.filters.band) && r2.filters.band.join(',') === 'yes,partial', `an old multi-value link (band=yes|partial) restores as two values (got ${JSON.stringify(r2.filters.band)})`)
+const w = SV.serializeQuery({ access_level: 'bridge', band: ['yes', 'partial'] }, '', '?view=zero&utm_source=x', { facetKeys: M.SHARE_FACETS })
+ok(w.includes('access=bridge') && w.includes('view=zero') && w.includes('utm_source=x') && !w.includes('access_level'), `the kit writes the published names and keeps the board's own params (${w})`)
+const mine = M.buildQuery({ view: 'zero', filterSet: { band: 'yes' }, keep: '?band=yes,partial', filters: false })
+ok(mine.includes('band=yes%2Cpartial') && mine.includes('view=zero'), 'the board\'s own writer leaves the filter params to the kit (filters:false)')
+
// 11. translation readiness: current against its snapshot, every roster language present, the no-NT set is
// the tree's, the 31 codes Glottolog lacks read as UNKNOWN (null) not "none", and the signal count is a
// recount of the five named facts (no hidden weighting)
diff --git a/plugin-designs/gospel-internet/src/model.js b/plugin-designs/gospel-internet/src/model.js
index f16731a9d..122fbde53 100644
--- a/plugin-designs/gospel-internet/src/model.js
+++ b/plugin-designs/gospel-internet/src/model.js
@@ -82,6 +82,9 @@ export const URL_KEYS = {
app: 'messaging',
}
export const VIEWS = ['board', 'zero', 'province', 'platforms']
+/** The board's URL filter names -> bus keys, for the kit's useShareableView (query mode, alias map).
+ * The published names are kept so links already shared keep restoring. */
+export const SHARE_FACETS = Object.fromEntries(Object.entries(URL_KEYS))
/** Parse `?view=&access=…&group=` into { view, filters, group }. Unknown views fall back to board. */
export function parseQuery(search) {
@@ -109,13 +112,15 @@ export function parseQuery(search) {
}
/** The inverse: filterSet + view + group -> a query string (no leading '?'), stable key order. */
-export function buildQuery({ view, filterSet, group, keep, panel, tree } = {}) {
+export function buildQuery({ view, filterSet, group, keep, panel, tree, filters = true } = {}) {
const q = new URLSearchParams(keep || '')
- for (const name of [...Object.keys(URL_KEYS), 'view', 'group', 'panel', 'tree']) q.delete(name)
+ // filters:false = the FILTER params belong to another writer (the kit's useShareableView): neither
+ // delete nor write them, so the two writers never fight over one address bar
+ for (const name of [...(filters ? Object.keys(URL_KEYS) : []), 'view', 'group', 'panel', 'tree']) q.delete(name)
if (view && view !== 'board') q.set('view', view)
if (panel === 'tree') q.set('panel', 'tree')
if (tree) q.set('tree', tree)
- for (const [name, key] of Object.entries(URL_KEYS)) {
+ if (filters) for (const [name, key] of Object.entries(URL_KEYS)) {
if (tree && key === 'language') continue
const v = filterSet?.[key]
if (v === undefined || v === null || v === '') continue
diff --git a/plugin-designs/gospel-internet/src/useBoard.js b/plugin-designs/gospel-internet/src/useBoard.js
index feb2c6b70..a7a4022b0 100644
--- a/plugin-designs/gospel-internet/src/useBoard.js
+++ b/plugin-designs/gospel-internet/src/useBoard.js
@@ -164,9 +164,12 @@ export function useBoard() {
const q = M.parseQuery(location.search)
const hostView = cfg().view
setView(q.view !== 'board' ? q.view : (M.VIEWS.includes(hostView) ? hostView : 'board'))
- // Explicit facets in the URL (or the host's profile-config `filters`) narrow the preset further.
+ // The host's profile-config `filters` narrow the preset; URL FILTER params are read by the kit's
+ // useShareableView (board.vue), except Gospel Resources' ?codes= / ?code=, which are this board's.
const hostFilters = cfg().filters && typeof cfg().filters === 'object' ? cfg().filters : {}
- for (const [k, v] of Object.entries({ ...hostFilters, ...q.filters })) if (k in M.FACET_ACC) bus.setFilter(k, v)
+ for (const [k, v] of Object.entries(hostFilters)) if (k in M.FACET_ACC) bus.setFilter(k, v)
+ const codes = [...new URLSearchParams(location.search).getAll('codes'), ...new URLSearchParams(location.search).getAll('code')].length ? q.filters.language : null
+ if (codes) bus.setFilter('language', codes)
const group = q.group || cfg().group || ''
if (group) pendingGroup = group
const panel = q.panel === 'tree' || cfg().panel === 'tree' ? 'tree' : 'map'
@@ -186,7 +189,7 @@ export function useBoard() {
function syncUrl() {
if (!writeUrl.value || typeof history === 'undefined') return
const qs = M.buildQuery({ view: view.value, filterSet: bus.filterSet, group: openSlug.value, keep: location.search,
- panel: centre.value, tree: treeNode.value })
+ panel: centre.value, tree: treeNode.value, filters: false })
const next = location.pathname + (qs ? '?' + qs : '') + location.hash
if (next !== location.pathname + location.search + location.hash) {
try { history.replaceState(history.state, '', next) } catch (_) { /* sandboxed */ }
diff --git a/public/dx-maps/doxa-maps-spa/manifest.json b/public/dx-maps/doxa-maps-spa/manifest.json
index 8936e0a69..b2cf2c0f6 100644
--- a/public/dx-maps/doxa-maps-spa/manifest.json
+++ b/public/dx-maps/doxa-maps-spa/manifest.json
@@ -604,7 +604,7 @@
"bundle": "gospel-internet",
"scriptUrl": "./gospel-internet/gospel-internet.js",
"output": "dx-maps/gospel-internet/gospel-internet.js",
- "integrity": "sha384-wRWCJhXf3QrvCUO5i22k46dpJFvQWnI8dmA1jLl2l4YkiomWTVYPjTfXlKz3HRez",
+ "integrity": "sha384-3be8zy4914UlmNZka99aGQPzEbp80c2Zr4ES+kvPMwW7SEzWNfxYEJgqgpbHr18d",
"elements": [
"gospel-internet-board"
],
@@ -623,7 +623,7 @@
"scriptUrl": "./gospel-internet/gospel-internet.js",
"embed": {
"iframe": "",
- "script": "\n\n\n\n"
+ "script": "\n\n\n\n"
},
"instances": [
{
diff --git a/public/dx-maps/gospel-internet/board/board/index.html b/public/dx-maps/gospel-internet/board/board/index.html
index 1b56433fc..675cb5873 100644
--- a/public/dx-maps/gospel-internet/board/board/index.html
+++ b/public/dx-maps/gospel-internet/board/board/index.html
@@ -36,7 +36,7 @@
-
+
-
+
-
+
-
+
-
+
-
+
-
+
\n\n"
+ "script": "\n\n\n\n"
},
"instances": [
{