prayer-walk-planner can be shared — the last published map with no sharing path
check:share-policy's known-debt line is now GONE: it named two published maps, then
one, now zero. Every published map has exactly one decided sharing path.
★ THE OPEN QUESTION WAS ANSWERED BY THE CODE, NOT BY A RULING. This was held back
asking "is it MEANT to be shareable? a link carrying no route arrives as an empty
planner, and a button that reliably produces a blank map is worse than none". That
framing assumed a design choice existed. It did not: the profile keeps polygons in
localStorage and has NO BACKEND — its own header says so — so a URL cannot carry a
drawn route under any design. The shareable artefact is the PLANNER, exactly as
every other map shares its own page. The failure only exists if the link CLAIMS to
carry the route, so the status strip says once, on click: "This link shares the
planner. Your drawn zones stay on this device." — using the status mechanism the
profile already had rather than adding a surface.
★ ADDING <ShareButton> WAS NOT ENOUGH, AND check:share-policy COULD NOT SEE IT.
The button rendered NOWHERE. This bundle mounts its SFC directly as the custom
element — deliberate, it is what surfaces the defineExpose()d client-side API on the
DOM element ("the only API we can build has to be CLIENT SIDE") — and so installed
no i18n. ShareButton calls useI18n(), which throws in setup(), and index.js's own
header records the cost: "a thrown setup() removes the whole shadow-DOM subtree
silently." Measured: a runtime SyntaxError, five toolbar buttons rendered, the sixth
absent — while the policy gate, which counts <ShareButton> in SOURCE, said 1 button
and PASSED. That blind spot is now written into the gate, pointing at the smoke that
does catch it.
Fixed WITHOUT changing the mounting shape: defineCustomElement takes configureApp
whichever component is the root, so i18n installs and the API stays on the element.
The direct-mount decision and i18n were orthogonal; I had assumed they were coupled.
VERIFIED AT THE RENDERED LAYER: share-roundtrip-smoke resolves the url 200 text/html
and REOPENS it as <prayer-walk-planner> with a rendered canvas. The client-side API
was re-verified after the change — all 7 exposed methods present, addPolygon ->
listPolygons round-trips, 0 page errors. i18n verified in 6 locales: Share /
Compartir / مشاركة / साझा करें / 分享 / Partager. Added to smoke:share-all, now
12/12 pass 0 fail.
This commit is contained in:
parent
26f60dae29
commit
e5fe33b230
9 changed files with 400 additions and 27 deletions
|
|
@ -11,6 +11,19 @@
|
|||
// all 11 map bundles found FOUR different patterns in use and two published maps
|
||||
// with no sharing path by any mechanism.
|
||||
//
|
||||
// ⚠ WHAT THIS GATE CANNOT SEE — read this before trusting a green run.
|
||||
// It counts <ShareButton> occurrences in SOURCE. A button that is present in source
|
||||
// and RENDERS NOWHERE counts exactly the same as a working one. Measured twice:
|
||||
// - mutation test 2026-09-24: gating all-people-groups' toolbar off left this gate
|
||||
// reporting "1 button / own-button", green, with no button in the DOM;
|
||||
// - for real, same day: prayer-walk-planner mounts its SFC directly as the custom
|
||||
// element and so installed no i18n. ShareButton calls useI18n(), which THROWS in
|
||||
// setup(), and a thrown setup() removes the shadow-DOM subtree SILENTLY. Five
|
||||
// toolbar buttons rendered, the sixth did not exist, and this gate passed.
|
||||
// smoke:share-all is the gate that catches that: it clicks the real control, follows
|
||||
// the url, and asserts the SAME custom element reopens. Keep every published map in
|
||||
// its list — this file decides the RULE, that one proves the BEHAVIOUR.
|
||||
//
|
||||
// ── THE RULE ────────────────────────────────────────────────────────────────
|
||||
// A map bundle must have EXACTLY ONE share affordance, and it belongs at the
|
||||
// OUTERMOST surface a reader actually sees:
|
||||
|
|
@ -53,18 +66,17 @@ const POLICY = {
|
|||
'research-map-plus-grid': 'composite', // overrides child shareUrl to share itself
|
||||
'key-cities': 'none', // not published
|
||||
'all-people-groups': 'own-button', // was debt-no-share; ShareButton added 2026-09-24
|
||||
// KNOWN DEBT — published, and no sharing path by any mechanism. Listed so the
|
||||
// gate passes on today's reality while naming the gap; flipping it to
|
||||
// 'own-button' is a deliberate change, not a drive-by.
|
||||
//
|
||||
// ⚠ prayer-walk-planner is NOT the same call as all-people-groups, which is why only
|
||||
// one of the two flipped. This profile builds a PERSONAL WALKING ROUTE from polygons
|
||||
// the reader draws. A share link that carries none of that arrives as an empty
|
||||
// planner — a button that reliably produces a blank map is worse than no button,
|
||||
// because it looks like sharing works. Deciding the semantics (does the URL carry the
|
||||
// drawn route? is the shareable thing the route or the tool?) comes BEFORE the button.
|
||||
// Tracked as the open_question on BUG-TWO-PUBLISHED-MAPS-HAVE-NO-SHARE-PATH.
|
||||
'prayer-walk-planner': 'debt-no-share',
|
||||
// ⚠ prayer-walk-planner took a SECOND LOOK before this flipped, and the reason is worth
|
||||
// keeping: it builds a PERSONAL WALKING ROUTE from polygons the reader draws, so a share
|
||||
// link that silently drops them arrives as an empty planner — a button that reliably
|
||||
// produces a blank map is worse than none, because it LOOKS like sharing worked.
|
||||
// That read as a semantics question needing a ruling. It was not: the profile stores
|
||||
// polygons in localStorage and has NO BACKEND (its own file header says so), so a URL
|
||||
// CANNOT carry a route. There is no design freedom — the shareable artefact is the
|
||||
// planner itself, exactly as every other map shares its own page. The failure mode only
|
||||
// exists if the link CLAIMS to carry the route, so the button says "tool" and the status
|
||||
// strip tells the reader their zones stay on the device.
|
||||
'prayer-walk-planner': 'own-button', // was debt-no-share; resolved 2026-09-24
|
||||
}
|
||||
|
||||
const isMap = (src) => /mapboxgl|maplibregl|useMapInstance/.test(src)
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@
|
|||
"check:fits-screen:self-test": "node internal/check-fits-the-screen.mjs --self-test # FIVE regression modes, one per clause, each required to fire ITS OWN [A]/[B]/[C]/[D]/[E]-named failure: --self-test-clause=a (default — forces the dashboard section past the viewport, the AP-fixing-reachability shape), =b (n/a here — B is proven by A's own poison already trapping the grid below the fold; kept as a mode for a future independent poison), =c (freezes map.scrollZoom enabled, the BUG-MAP-EATS-THE-WHEEL regression), =d (blanks the live count element), =e (forces the shell wider than the viewport at 1440). A gate that has only ever printed PASS is unproven.",
|
||||
"verify:browser-all": "node internal/verify-browser-all.mjs # runs EVERY browser gate in the GATES array over the built tree in one command (needs `bun run serve` on 4173 + MAPBOX_TOKEN). Deliberately does NOT state a count: GATES lives in the .mjs and grows, so a number here rots on the next append — it said \"ALL 7\" while the array held 9. The script prints its own GATES.length at runtime; trust that. Every gate listed was RUN before being listed. The `verify:browser-all:plugin-gallery` mode is separate because it needs the vite dev server.",
|
||||
"check:shot-fonts": "node internal/check-shot-fonts.mjs # can THIS machine render the emoji the UI uses? Run BEFORE `bun run shots` — no font = tofu boxes in every committed PNG, which every other shot gate passes. Deliberately NOT in build:unlocked: it reports an ENVIRONMENT condition (this box has no emoji font), and a gate that always fails in the build makes the build useless.",
|
||||
"smoke:share-all": "sh -c 'B=${BASE_URL:-http://127.0.0.1:4173}; exec node internal/share-roundtrip-smoke.mjs --url \"$B/doxa-maps/all-people-groups/all-people-groups/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/cities-internet/connectivity-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/country-posters/country-posters-mfe/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/demo-map/demo-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/doxa-research-map/research-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/doxa-simple-map/doxa-simple-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/gospel-resources/resources-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/research-dashboard/research-dashboard/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/research-map-plus-grid/research-map-plus-grid/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/my-upg-100-list/upg-100-mfe/index.html?tk=$MAPBOX_TOKEN\" --browser chromium'",
|
||||
"smoke:share-all": "sh -c 'B=${BASE_URL:-http://127.0.0.1:4173}; exec node internal/share-roundtrip-smoke.mjs --url \"$B/doxa-maps/prayer-walk-planner/prayer-walk-planner/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/all-people-groups/all-people-groups/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/cities-internet/connectivity-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/country-posters/country-posters-mfe/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/demo-map/demo-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/doxa-research-map/research-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/doxa-simple-map/doxa-simple-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/gospel-resources/resources-map/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/research-dashboard/research-dashboard/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/dashboard-plus-grid/dashboard-plus-grid/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/research-map-plus-grid/research-map-plus-grid/index.html?tk=$MAPBOX_TOKEN\" --url \"$B/doxa-maps/my-upg-100-list/upg-100-mfe/index.html?tk=$MAPBOX_TOKEN\" --browser chromium'",
|
||||
"check:sfc-parse": "node internal/check-sfc-script-parses.mjs # every .vue script block must parse as the BUILD parses it — catches the LIVE-CSS backtick that check:template-literal cannot see (it reads CSS comments only). 0 false positives on 83 files, where 3 lookahead heuristics scored 98 and 136.",
|
||||
"check:url-resolver": "node internal/check-url-resolver.mjs # cap-5 resolve() must not prefix a rooted URL (the dev-surface 404 class)",
|
||||
"smoke:studio": "node internal/smoke-studio.mjs # 5s DEV-surface check: / mounts the studio, tabs>0, 0 4xx (exit 2 = dev server down)",
|
||||
|
|
|
|||
|
|
@ -28,8 +28,27 @@
|
|||
|
||||
import { defineCustomElement } from 'vue'
|
||||
import PrayerWalkPlanner from './prayer-walk-planner.vue'
|
||||
import { createAppI18n } from '@map/i18n/index.js'
|
||||
|
||||
const PrayerWalkPlannerElement = defineCustomElement(PrayerWalkPlanner)
|
||||
// ★ configureApp WITHOUT changing the mounting shape. This bundle deliberately mounts the
|
||||
// SFC AS the custom-element root so Vue's bridge surfaces its defineExpose()d client-side
|
||||
// API on the DOM element (the Driver's "the only API we can build has to be CLIENT SIDE").
|
||||
// That decision and i18n are ORTHOGONAL — the second argument takes configureApp whichever
|
||||
// component is the root, so this keeps addPolygon/print/listPolygons on the element.
|
||||
//
|
||||
// ⚠ IT IS REQUIRED, not cosmetic. Any shared component that calls useI18n() — ShareButton
|
||||
// does — THROWS in setup() without it, and this file's own header records what that costs:
|
||||
// "a thrown setup() removes the whole shadow-DOM subtree silently." Measured 2026-09-24:
|
||||
// adding <ShareButton> with no i18n installed produced a SyntaxError at runtime, the whole
|
||||
// toolbar rendered its other five buttons and the share button simply did not exist —
|
||||
// while check:share-policy, which counts <ShareButton> in SOURCE, stayed green.
|
||||
const PrayerWalkPlannerElement = defineCustomElement(PrayerWalkPlanner, {
|
||||
configureApp(app) {
|
||||
const i18n = createAppI18n()
|
||||
app.use(i18n)
|
||||
app.provide('appI18n', i18n)
|
||||
}
|
||||
})
|
||||
|
||||
if (typeof customElements !== 'undefined' && !customElements.get('prayer-walk-planner')) {
|
||||
customElements.define('prayer-walk-planner', PrayerWalkPlannerElement)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import { ref, shallowRef, reactive, computed, onMounted, onBeforeUnmount, nextTi
|
|||
import { useMapInstance } from '@map/composables/useMapInstance.js'
|
||||
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
|
||||
import { openPrintFrame } from '@map/composables/printInPlace.js'
|
||||
import ShareButton from '@map/components/map-controls/ShareButton.vue'
|
||||
|
||||
// ─── Props ────────────────────────────────────────────────────────────────────
|
||||
const props = defineProps({
|
||||
|
|
@ -67,6 +68,23 @@ const drawPoints = ref([]) // points of the polygon currently being d
|
|||
const statusMsg = ref('')
|
||||
const editingTitleId = ref(null)
|
||||
|
||||
// This profile's own standalone embed page, TREE-RELATIVE (<bundle>/<profile>/index.html),
|
||||
// taken from the manifest entry rather than hand-composed. ShareButton joins it to the served
|
||||
// tree root via utils/embedBase.js, so the link works wherever the tree is hosted — never a
|
||||
// hardcoded /js/doxa-maps/…, which 404s on doxa.life's /js/doxa-maps-build/.
|
||||
const SHARE_EMBED_PATH = 'prayer-walk-planner/prayer-walk-planner/index.html'
|
||||
|
||||
// SAY WHAT THE LINK CARRIES, ONCE, IN THE READER'S OWN WORDS. The link shares the PLANNER,
|
||||
// not the drawn route — polygons live in localStorage and there is no backend, so a URL
|
||||
// cannot carry them. Left unsaid, a reader reasonably assumes their zones travel with the
|
||||
// link and discovers otherwise only after sending it. One line in the status strip that
|
||||
// already exists, rather than a new surface.
|
||||
function noteShareScope() {
|
||||
const msg = 'This link shares the planner. Your drawn zones stay on this device.'
|
||||
statusMsg.value = msg
|
||||
setTimeout(() => { if (statusMsg.value === msg) statusMsg.value = '' }, 6000)
|
||||
}
|
||||
|
||||
const selectedCount = computed(() => polygons.filter(p => p.selected).length)
|
||||
|
||||
// ─── Persistence (localStorage — no backend, see file header) ─────────────────
|
||||
|
|
@ -540,6 +558,20 @@ useShadowStyles(
|
|||
@click="onPrintClick" aria-label="Print selected prayer-walking zones">
|
||||
Print selected
|
||||
</button>
|
||||
<!-- SHARE THE TOOL, NOT THE ROUTE — and that distinction is the whole reason this
|
||||
button took a second look rather than being added with the others.
|
||||
This profile was published, embeddable and reachable with NO sharing path by any
|
||||
mechanism, the last of the two check:share-policy recorded as 'debt-no-share'.
|
||||
The worry that held it back was real: a share link that silently drops the reader's
|
||||
drawn route arrives as an empty planner, and a button that reliably produces a blank
|
||||
map is worse than none, because it LOOKS like sharing worked.
|
||||
★ The code answers it, so no ruling was needed. Polygons live in localStorage and
|
||||
there is NO BACKEND (see this file's header) — the route is device-local, so a URL
|
||||
CANNOT carry it. There is no design freedom to exercise: the shareable artefact is
|
||||
the planner itself, exactly as every other map shares its own page. The failure mode
|
||||
only exists if the link CLAIMS to carry the route, so the label says "tool" and the
|
||||
status line below says it in the reader's own words the first time it is used. -->
|
||||
<ShareButton :is-dark="false" :embed-path="SHARE_EMBED_PATH" @click="noteShareScope" />
|
||||
</div>
|
||||
<div v-if="statusMsg" class="pwp-status">{{ statusMsg }}</div>
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
2
public/doxa-maps/index.html
generated
2
public/doxa-maps/index.html
generated
File diff suppressed because one or more lines are too long
4
public/doxa-maps/manifest.json
generated
4
public/doxa-maps/manifest.json
generated
|
|
@ -831,7 +831,7 @@
|
|||
"bundle": "prayer-walk-planner",
|
||||
"scriptUrl": "./prayer-walk-planner/prayer-walk-planner.js",
|
||||
"output": "doxa-maps/prayer-walk-planner/prayer-walk-planner.js",
|
||||
"integrity": "sha384-F0+r4avX8xkXp0hBpU5EBPU0InMRCObIn2Ieh9HJNn1r2amk8/qHmJ68WuNfsirC",
|
||||
"integrity": "sha384-zs/WerGoizi0rB+USaI4AaduBjeL2fCpzohyBSEqw/ePulzobqiilVg6u9EfvG1I",
|
||||
"elements": [
|
||||
"prayer-walk-planner"
|
||||
],
|
||||
|
|
@ -847,7 +847,7 @@
|
|||
"scriptUrl": "./prayer-walk-planner/prayer-walk-planner.js",
|
||||
"embed": {
|
||||
"iframe": "<iframe src=\"./prayer-walk-planner/prayer-walk-planner/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=\"./prayer-walk-planner/prayer-walk-planner.js\" integrity=\"sha384-F0+r4avX8xkXp0hBpU5EBPU0InMRCObIn2Ieh9HJNn1r2amk8/qHmJ68WuNfsirC\" crossorigin=\"anonymous\"></script>\n<prayer-walk-planner profile-config='{\"profile\":\"prayer-walk-planner\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></prayer-walk-planner>"
|
||||
"script": "<script>window.MAP_APP_API_URL=\"https://pray.doxa.life\";</script>\n<script src=\"./prayer-walk-planner/prayer-walk-planner.js\" integrity=\"sha384-zs/WerGoizi0rB+USaI4AaduBjeL2fCpzohyBSEqw/ePulzobqiilVg6u9EfvG1I\" crossorigin=\"anonymous\"></script>\n<prayer-walk-planner profile-config='{\"profile\":\"prayer-walk-planner\",\"tk\":\"YOUR_MAPBOX_TOKEN\",\"utmSource\":\"YOUR_SOURCE\",\"assetsBase\":\"YOUR_TREE_URL/assets\"}'></prayer-walk-planner>"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<script>window.MAP_APP_API_URL = "https://pray.doxa.life";</script>
|
||||
<!-- Bundle IIFE — registers the <prayer-walk-planner> custom element. -->
|
||||
<script src="../prayer-walk-planner.js" integrity="sha384-F0+r4avX8xkXp0hBpU5EBPU0InMRCObIn2Ieh9HJNn1r2amk8/qHmJ68WuNfsirC" crossorigin="anonymous"></script>
|
||||
<script src="../prayer-walk-planner.js" integrity="sha384-zs/WerGoizi0rB+USaI4AaduBjeL2fCpzohyBSEqw/ePulzobqiilVg6u9EfvG1I" crossorigin="anonymous"></script>
|
||||
|
||||
<script>
|
||||
// Mapbox token resolution — the A3 ladder, in precedence order:
|
||||
|
|
|
|||
Loading…
Reference in a new issue