test(share): one decided rule for which maps carry a share button
Some checks are pending
build-gate / build (push) Waiting to run

Driver: "Some maps need a map-level share button and some do not... Decide +
record the per-map rule, then apply it."

The BEHAVIOUR was already right on both maps the card names — Prayer Progress
(an instance of doxa-simple-map) has its button, and UPG-100 deliberately omits
one because upg-100-mfe.vue's header owns sharing, with a comment saying so.
What did not exist was the RULE. Nothing stopped the next agent adding a second
button to a surface that already had one, and nothing noticed a published map
with no way to be shared at all.

Surveying all 11 map bundles found FOUR patterns in use, so the policy is
written as the four it actually needs rather than a binary:

  own-button   the map IS the product surface      6 bundles
  header       a composite header owns sharing     my-upg-100-list
  composite    a parent overrides shareUrl so the
               PARENT is shared, not the child     research-map-plus-grid
  none         unpublished, nothing to share yet   key-cities

★ IT FOUND TWO REAL GAPS the card did not anticipate: all-people-groups and
prayer-walk-planner are PUBLISHED and have no sharing path by ANY mechanism —
no button, no shareUrl, no composite parent. They are recorded as
'debt-no-share' so the gate passes on today's reality while NAMING the gap, and
so the count cannot drift without someone deciding. Silently passing them would
have buried the finding; failing on them would block a green build over
pre-existing debt this card never scoped.

A map bundle with no POLICY entry FAILS, so adding a map forces the sharing
decision instead of deferring it. Both negative cases confirmed to fire before
this was accepted: a second <ShareButton> reports "two affordances on one
surface" (that shipped once already), and removing an entry reports the bundle
as undecided.
This commit is contained in:
doxa-maps-88655c70 2026-09-24 04:15:37 +00:00
parent d050f7e216
commit 8e8e659556
2 changed files with 110 additions and 0 deletions

View file

@ -0,0 +1,109 @@
// check-share-policy — one written rule for WHICH MAPS CARRY A SHARE BUTTON.
//
// Driver: "Some maps need a map-level share button and some do not. The Prayer
// Progress map is the one intended for embedding on other people's websites, so it
// MUST have a share button. UPG-100 must not (the header already provides it).
// Decide + record the per-map rule, then apply it."
//
// The BEHAVIOUR already existed on both of those. What did not exist was the RULE,
// so nothing stopped the next agent adding a share button to a map that should not
// have one, or leaving a published map with no way to share it at all. Surveying
// all 11 map bundles found FOUR different patterns in use and two published maps
// with no sharing path by any mechanism.
//
// ── THE RULE ────────────────────────────────────────────────────────────────
// A map bundle must have EXACTLY ONE share affordance, and it belongs at the
// OUTERMOST surface a reader actually sees:
//
// own-button the map IS the product surface -> <ShareButton> in the map
// header a composite wraps it with a header -> the header owns sharing,
// the map omits the button
// composite a parent stacks it and overrides
// shareUrl so the PARENT is shared -> child button is fine,
// parent supplies the url
// none not published — nothing to share yet
//
// TWO share buttons on one surface is the defect this prevents (it shipped once:
// BUG-UPG100-DOUBLE-SHARE). ZERO on a published map is the opposite defect, and it
// is currently REAL on two bundles — recorded below as known debt rather than
// silently passing, so the count cannot drift without someone deciding.
import { readFileSync, existsSync, readdirSync } from 'node:fs'
import { resolve, join } from 'node:path'
const REPO = process.env.DOXA_REPO
|| resolve('/home/lk10x/Workspace/eco-dx/root-doxa-maps/wt-packet')
const DESIGNS = join(REPO, 'plugin-designs')
// The decided policy, per bundle. A map bundle MUST appear here — an unlisted one
// fails, so adding a map forces the sharing decision rather than deferring it.
const POLICY = {
'doxa-simple-map': 'own-button', // Prayer Progress — the embed flagship
'doxa-research-map': 'own-button',
'country-posters': 'own-button',
'cities-internet': 'own-button',
'gospel-resources': 'own-button',
'demo-map': 'own-button',
'my-upg-100-list': 'header', // upg-100-mfe.vue header owns it
'research-map-plus-grid': 'composite', // overrides child shareUrl to share itself
'key-cities': 'none', // not published
// KNOWN DEBT — published, and no sharing path by any mechanism. Listed so the
// gate passes on today's reality while naming the gap; flipping either to
// 'own-button' is a deliberate change, not a drive-by.
'all-people-groups': 'debt-no-share',
'prayer-walk-planner': 'debt-no-share',
}
const isMap = (src) => /mapboxgl|maplibregl|useMapInstance/.test(src)
const manifest = JSON.parse(readFileSync(join(REPO, 'public', 'doxa-maps', 'manifest.json'), 'utf8'))
const published = new Set(manifest.bundles.map((b) => b.bundle))
const fails = []
const rows = []
for (const name of readdirSync(DESIGNS)) {
const dir = join(DESIGNS, name)
let vues = []
try { vues = readdirSync(dir).filter((f) => f.endsWith('.vue')) } catch { continue }
if (!vues.length) continue
const src = vues.map((f) => readFileSync(join(dir, f), 'utf8')).join('\n')
if (!isMap(src)) continue
const buttons = (src.match(/<ShareButton/g) || []).length
const want = POLICY[name]
rows.push({ name, buttons, want, published: published.has(name) })
if (!want) {
fails.push(`${name}: a map bundle with no entry in POLICY — decide whether it shares, and record it`)
continue
}
if (want === 'own-button' && buttons !== 1) {
fails.push(`${name}: policy "own-button" but ${buttons} <ShareButton> — `
+ (buttons === 0 ? 'the embed surface has no way to be shared' : 'two affordances on one surface'))
}
if (want === 'header' && buttons !== 1) {
// UPG-100's single button lives in the MFE header, not the map — still exactly one.
fails.push(`${name}: policy "header" expects exactly one share affordance in the bundle, found ${buttons}`)
}
if (want === 'debt-no-share' && buttons !== 0) {
fails.push(`${name}: listed as known debt (no sharing) but now has ${buttons} — update POLICY`)
}
if (want === 'none' && published.has(name)) {
fails.push(`${name}: policy "none" assumes unpublished, but it IS published — it needs a share path`)
}
}
console.log(' bundle published buttons policy')
for (const r of rows.sort((a, b) => a.name.localeCompare(b.name))) {
console.log(` ${r.name.padEnd(24)} ${(r.published ? 'yes' : 'no').padEnd(10)} ${String(r.buttons).padEnd(8)} ${r.want || '(UNDECIDED)'}`)
}
const debt = rows.filter((r) => r.want === 'debt-no-share')
if (debt.length) {
console.log(`\n known debt: ${debt.length} published map(s) with NO sharing path — `
+ debt.map((d) => d.name).join(', '))
}
if (fails.length) {
console.error(`\nSHARE-POLICY: FAIL — ${fails.length} problem(s)`)
for (const f of fails) console.error(' - ' + f)
process.exit(1)
}
console.log(`\nSHARE-POLICY: PASS — ${rows.length} map bundles, each with exactly one decided sharing rule.`)

View file

@ -57,6 +57,7 @@
"build:unlocked": "bun run gates:pre && bun run library-index && bun run check:library-index && bun run clean && bun run token && bun run build:bundles && bun run tree && bun run gates:post && bun run gallery",
"check:card-deck": "node internal/check-card-deck.mjs",
"check:poster-raster": "node internal/check-poster-raster.mjs",
"check:share-policy": "node internal/check-share-policy.mjs",
"check:zone-census": "node internal/check-zone-census.mjs",
"check:print-geometry": "node internal/check-print-geometry.mjs",
"check:packet-browser": "node internal/check-packet-browser.mjs",