Both are zero-domain in the sense the spec meant: they import only `vue` and read rows through accessors. Bodies are what research-dashboard shipped apart from the two parameters the spec named and one policy object, and the old paths re-export for one cycle. ★ THE FIRST STEP FOUND TWO GATES THAT READ THESE FILES BY PATH, which is why it is now the first step. Landing 1 nearly disarmed check-facet-cascade the same way. internal/check-csv-export-shape.mjs does not import useCsvExport; it READS the source and extracts esc() out of it. Left at the plugin path it would have read the one-line shim, found no esc(), and failed with "could not extract esc()" — a gate accusing a working exporter because its subject moved. check-filter-multivalue.mjs — MINE — lists the same file in its PANELS array. Left at the plugin path it would have read the shim, found no predicate to object to, and PASSED VACUOUSLY. That is worse: a gate going quiet about the thing it guards. Both move in this commit. THE COORDINATE IS A PARAMETER; WHAT MAKES A COORDINATE VALID IS NOT. `coords` replaces only the READ of row.longitude / row.latitude, which is pray-tools' spelling and not a universal one. Every guard stays shared: Number() on API strings, 0 accepted per axis, the exact 0,0 null-island sentinel rejected, the out-of-range check. A kit that let each caller re-implement that validation would hand the next dashboard the same four bugs to rediscover. ★ AND THE OPTION WAS DEAD WHEN I FIRST WROTE IT. `coords` was accepted in the signature and never passed to selectionBounds, so a caller could set it, type-check clean, build clean, and watch it do nothing. This tree has paid for that shape before — a defineProps on a component the custom element never binds — and it is worse than a missing feature because it READS as supported. Nobody files a bug against a knob they assume they misused. check:kit-options-reachable now asserts, across all four shared composables, that every option accepted is used. THE CAMERA'S EIGHT CONSTANTS ARE ONE DECISION, so CAMERA_POLICY exports them together. The failure that prevents is a second dashboard re-picking them one at a time and arriving at a camera that feels different from Big Picture's for no reason anybody wrote down. It is read-only and deliberately not yet overridable: making each a per-caller option means threading eight of them through every use, and no consumer has asked for a different camera. Exporting the decision so it can be cited is the half with a use today; saying so is better than shipping eight options nobody passes. THE EXPORTER GAINS exportLabel and sources. "Research Dashboard" was this dashboard's name, and the provenance line said nothing about where the data came from — which is what a provenance line is for. Both default to today's output, to the byte. ★ AND THE NEW GATE HAD THE COMMENT HOLE TWICE IN ONE SITTING. First it searched the raw body, and the comment beside the fixed call site says "`coords` IS PASSED HERE" — so the mutant that removed the argument left it green. Then, stripping comments only per-part, it split the signature's comment PROSE on commas and reported that useCsvExport accepts `and`. Comments are text; every text search has to say so, in both directions. That is the fourth variant of one mistake this week, and all four were caught by running the mutant rather than by reading. Library index regenerated in the same commit, 103 items. Source only. STUDIO: needs a rebuild of research-dashboard and dashboard-plus-grid. The internal/ hunk is one line in check-csv-export-shape.mjs. |
||
|---|---|---|
| .. | ||
| all-people-groups | ||
| big-picture | ||
| cities-internet | ||
| country-posters | ||
| dashboard-plus-grid | ||
| demo-map | ||
| doxa-maps-spa | ||
| doxa-research-map | ||
| doxa-simple-map | ||
| doxa-vision-card | ||
| golden-path-check | ||
| gospel-resources | ||
| key-cities | ||
| my-upg-100-list | ||
| people-groups-grid | ||
| plugin-studio | ||
| pplr-data-maps | ||
| prayer-walk-planner | ||
| regions-dashboard | ||
| research-dashboard | ||
| research-map-plus-grid | ||
| stack-demo | ||
| template-bundle | ||
| upg-neighbor | ||
| wall-of-people-groups | ||
| .gitkeep | ||
| README.md | ||
plugin-designs/ — where YOUR maps live
The shared template lives in ../library/ (aliased @map) — you never edit it per-map.
You build your map by making a folder here, plugin-designs/<your-map>/, and composing the
template's building blocks. Your folder is yours (colors, API, i18n); the template is shared
(Apache-2.0, propagated to every map on the next build).
Shared vs. yours — the balance
library/ (@map) ── SHARED, reusable, parameterized ──► legend · map · map-controls
│ (every map reuses these)
│ imported by
▼
plugin-designs/<your-map>/ ── YOURS ──► the map's "main" class (the .vue profile = the Vue
│ instance for that map)
└── src/ ── YOURS ──► this map's OWN non-reusable components, colors, API, utils
One bundle can hold as many maps as you want — add another .vue profile and rebuild.
- Reusable →
library/. The legend, the map, the map-controls are shared, parameterized components every map reuses. Fix once, every map benefits. Never edit it to customize one map. - This map's own →
plugin-designs/<your-map>/. It holds two things: the map's "main" class (each.vueprofile is the Vue instance for that map) and that map's own non-reusable components (insrc/). Nothing here leaks to another map. - Many maps per bundle. The bundler bundles one map or many — add another flat
.vueand rebuild.
Production or experimental — every profile declares itself
Every app profile is one of two things, and experimental profiles are unlimited and encouraged — create as many as you need:
- Experimental, not built: prefix the folder with
_— the build skips it; it just sits in code. Zero cost, zero risk. - Experimental, built: builds normally, but it belongs in the Experimental section of the power app's layout (nav section) — never embedded on partner surfaces.
- Production: a real product — main sections of the power app + individually embeddable.
(The power app = the SPA layout that composes all profiles into one application.)
The pattern in one picture
plugin-designs/<your-map>/ ← one FOLDER = one BUNDLE, named after the folder
├── index.js ← entry: registers the custom element, finds the profiles
├── overview.vue ← one .vue = one PROFILE (flat in the folder — no profiles/ subfolder)
└── detail.vue ← another profile in the SAME bundle
builds to ─────────────► public/dx-maps/<your-map>/<your-map>.js (one file)
- Folder name = bundle name. Folders starting with
_or.are skipped (handy for WIP). - Each
.vue= one profile (one map screen). One bundle can hold many. - Pick the profile at embed time with the
profilefield — every profile shares one bundle:
<script src="/dx-maps/<your-map>/<your-map>.js"></script>
<your-map profile-config='{"profile":"overview","tk":"pk.eyJ..."}'></your-map>
<your-map profile-config='{"profile":"detail","tk":"pk.eyJ..."}'></your-map>
Add a map — three steps
# 1. create the folder + one profile
mkdir -p plugin-designs/<your-map>
# index.js ← copy doxa-simple-map/index.js, rename the tag
# <name>.vue ← your screen, built from @map/* blocks (flat in the folder)
# 2. build
npm run build # → public/dx-maps/<your-map>/<your-map>.js
# (just this one: BUNDLE=<your-map> npm run build:bundle)
# 3. embed the script tag + element (dev staging at `npm run dev` shows a paste-ready snippet)
Your three things (everything else is shared)
- Colors — how pins are colored. Pick one the template ships (
getColorStrategy('language-family')), or drop your own inplugin-designs/<your-map>/src/colors/(start fromtemplate-bundle/src/colors/example-mode.js). Never edit@map/colors/to recolor your map. - API — your map reads its data from the
profile-configon the page, so the same bundle points at different data with no rebuild.DataSourceManageracceptscsv(bundled file),api(one JSON URL), orrest-api(a live REST endpoint, the default). Prefer the real API over inlined JSON. - i18n — the template ships 11 base locales in
@map/i18n/locales/. UseuseI18n()for strings andRTL_LOCALESfor right-to-left; add or override strings from your own profile.
The one hosting variable
The only thing that changes between hosting modes is the <script src>: a relative path when
your own site serves the file, or an absolute CDN URL when a partner site serves it. The bundle
is identical either way. See ../documentation/CONTRIBUTING.md for the two CDN modes and partner-site embedding.
Profile patterns (mix freely)
- Individual — one
.vueper bundle (simplest). - Parameterized — one profile, behavior driven by
profile-configprops (e.g.tabs[]). - Nested — one profile that renders others (workbench-style, like
doxa-research-map).
See also
doxa-simple-map/index.js— fully commented reference bundle entrytemplate-bundle/— minimal two-profile skeleton to copy../library/colors/README.md— the color-strategy contract../README.md— repo overview & build pipeline ·../documentation/CONTRIBUTING.md— build vs dev, CDN modes