diff --git a/docs/reference-library-index.md b/docs/reference-library-index.md new file mode 100644 index 000000000..9a04a09f8 --- /dev/null +++ b/docs/reference-library-index.md @@ -0,0 +1,208 @@ +# REFERENCE-library-index — reusable pieces you can import into an app-profile + +> **DERIVED FILE — do not hand-edit.** Regenerate: `node internal/generate-library-index.mjs` +> Generated: run `node internal/generate-library-index.mjs` to refresh + +**You are a programmer (or an agent) building a map.** This is the menu of REUSABLE, +parameterized pieces the shared `library/` offers. Import what you need with the `@map/…` +alias; **build your own custom code in your app-profile's `src/` folder.** Do NOT edit +anything listed here unless you know what you're doing — these are shared across every map, +and a change here can break other maps. + +- **Three zones:** `library/` (this menu — reuse, don't edit) · `plugin-designs//` (your sandbox — build anything) · `internal/` (the build machine — never touch). +- **To make a new map:** copy `plugin-designs/template-bundle/`, read its README, list your requirements, pick from below. + +### Components (reusable UI) + +- **PeopleGroupDetail** — (no description) + `import … from '@map/components/PeopleGroupDetail.vue'` +- **ResearchMapFilterPanel** — ResearchMapFilterPanel.vue — Multi-field filter UI for the research-map. + `import … from '@map/components/ResearchMapFilterPanel.vue'` +- **ResearchMapSideMenu** — ResearchMapSideMenu.vue — Side drawer specific to the research-map profile. + `import … from '@map/components/ResearchMapSideMenu.vue'` +- **SideMenuDrawer** — SideMenuDrawer.vue — Slide-in side panel for the map application. + `import … from '@map/components/SideMenuDrawer.vue'` +- **BarChart** — BarChart.vue — THE SHARED HORIZONTAL BAR CHART (hand-rolled SVG, zero dependencies). + `import … from '@map/components/charts/BarChart.vue'` +- **PinHint** — PinHint.vue — the conversion coach-mark (bottom-center of any map). + `import … from '@map/components/hints/PinHint.vue'` +- **LegendDesktop** — Async-import the heavier branches so the boot bundle stays lean. + `import … from '@map/components/legends/LegendDesktop.vue'` +- **LegendMobile** — useShadowStyles( + `import … from '@map/components/legends/LegendMobile.vue'` +- **LegendProgressBar** — LegendProgressBar — the prayer legend's header progress bar. + `import … from '@map/components/legends/LegendProgressBar.vue'` +- **LegendRows** — (no description) + `import … from '@map/components/legends/LegendRows.vue'` +- **LegendTools** — LegendTools.vue — Fly + Clusters floating toolbar + `import … from '@map/components/legends/LegendTools.vue'` +- **SemanticTreeLegend** — SemanticTreeLegend — reusable hierarchical legend for any one-to-many semantic tree. + `import … from '@map/components/legends/SemanticTreeLegend.vue'` +- **FullscreenButton** — FullscreenButton.vue — Toggles fullscreen for the map host element. + `import … from '@map/components/map-controls/FullscreenButton.vue'` +- **GeocoderComponent** — GeocoderComponent.vue — Mapbox Geocoder search bar as a self-contained Vue component. + `import … from '@map/components/map-controls/GeocoderComponent.vue'` +- **HamburgerButton** — HamburgerButton.vue — Toggle button for the side menu drawer. + `import … from '@map/components/map-controls/HamburgerButton.vue'` +- **HelpButton** — HelpButton.vue — Opens the "About this map" help modal. + `import … from '@map/components/map-controls/HelpButton.vue'` +- **LocationButton** — LocationButton.vue — Flies the map to the user's current location via the + `import … from '@map/components/map-controls/LocationButton.vue'` +- **MapControlButton** — MapControlButton.vue — Shared base button for all map toolbar controls. + `import … from '@map/components/map-controls/MapControlButton.vue'` +- **MapControlMenu** — MapControlMenu.vue — the ONE menu every map control opens. + `import … from '@map/components/map-controls/MapControlMenu.vue'` +- **MapToolbar** — MapToolbar.vue — Positioned layout shell for map control buttons. + `import … from '@map/components/map-controls/MapToolbar.vue'` +- **ShareButton** — ShareButton.vue — Non-technical share modal for the map toolbar. + `import … from '@map/components/map-controls/ShareButton.vue'` +- **ThemeToggleButton** — ThemeToggleButton.vue — Switches the app between light and dark mode. + `import … from '@map/components/map-controls/ThemeToggleButton.vue'` +- **ZoomInButton** — ZoomInButton.vue — Increments the map zoom level by one step. + `import … from '@map/components/map-controls/ZoomInButton.vue'` +- **ZoomOutButton** — ZoomOutButton.vue — Decrements the map zoom level by one step. + `import … from '@map/components/map-controls/ZoomOutButton.vue'` +- **PosterDialog** — PosterDialog.vue — modal for previewing + rendering a poster export. + `import … from '@map/components/poster/PosterDialog.vue'` +- **PosterPreview** — PosterPreview.vue — scaled live preview of the poster composition. + `import … from '@map/components/poster/PosterPreview.vue'` +- **PosterWall** — PosterWall.vue — THE SHARED POSTER WALL. + `import … from '@map/components/poster/PosterWall.vue'` +- **FooterSlot** — FooterSlot.vue — STUB. Poster-rendering slot for FooterSlot. + `import … from '@map/components/poster/slots/FooterSlot.vue'` +- **LegendSlot** — LegendSlot.vue — STUB. Poster-rendering slot for LegendSlot. + `import … from '@map/components/poster/slots/LegendSlot.vue'` +- **NorthArrowSlot** — NorthArrowSlot.vue — STUB. Poster-rendering slot for NorthArrowSlot. + `import … from '@map/components/poster/slots/NorthArrowSlot.vue'` +- **ScaleBarSlot** — ScaleBarSlot.vue — STUB. Poster-rendering slot for ScaleBarSlot. + `import … from '@map/components/poster/slots/ScaleBarSlot.vue'` +- **TitleSlot** — TitleSlot.vue — renders the poster title strip. + `import … from '@map/components/poster/slots/TitleSlot.vue'` +- **QaCorner** — QaCorner.vue — the embeddable QA corner widget (bottom-right of any map). + `import … from '@map/components/qa/QaCorner.vue'` +- **FilterDropdown** — FilterDropdown.vue — SHARED (@map/components/search/FilterDropdown.vue). + `import … from '@map/components/search/FilterDropdown.vue'` +- **SearchBar** — SearchBar.vue — SHARED (@map/components/search/SearchBar.vue). + `import … from '@map/components/search/SearchBar.vue'` +- **SearchFilterHeader** — SearchFilterHeader.vue — SHARED (@map/components/search/SearchFilterHeader.vue). + `import … from '@map/components/search/SearchFilterHeader.vue'` +- **DataTable** — DataTable.vue — THE SHARED TABULATOR LIST VIEW. + `import … from '@map/components/tables/DataTable.vue'` + +### Composables (reusable logic — Vue Composition API) + +- **researchLegendOptions** — researchLegendOptions.js — research-mfe-only options passed to the shared + `import … from '@map/composables/legends/researchLegendOptions.js'` +- **useAffinityBlockLegendData** — useAffinityBlockLegendData — 4-tier affinity-block legend data for SemanticTreeLegend. + `import … from '@map/composables/legends/useAffinityBlockLegendData.js'` +- **useLanguageFamilyLegendData** — useLanguageFamilyLegendData — language-family legend rows with + `import … from '@map/composables/legends/useLanguageFamilyLegendData.js'` +- **useLegendData** — useLegendData — builds a unified, data-driven legend item tree + `import … from '@map/composables/legends/useLegendData.js'` +- **useWagfRegionsLegendData** — useWagfRegionsLegendData — 3-tier WAGF Regions legend for SemanticTreeLegend. + `import … from '@map/composables/legends/useWagfRegionsLegendData.js'` +- **useMapPoster** — useMapPoster — render a Mapbox GL map to a poster-grade PNG or PDF blob. + `import … from '@map/composables/poster/useMapPoster.js'` +- **usePosterLayout** — usePosterLayout — slot positioning math for poster compositions. + `import … from '@map/composables/poster/usePosterLayout.js'` +- **useAvatarGenerator** — Avatar Generator Composable + `import … from '@map/composables/useAvatarGenerator.js'` +- **useCountryOutline** — useCountryOutline.js — country boundary OUTLINE overlay on country search. + `import … from '@map/composables/useCountryOutline.js'` +- **useEdgeScrollAffordance** — useEdgeScrollAffordance — the gray "you can scroll past this" edge strips. + `import … from '@map/composables/useEdgeScrollAffordance.js'` +- **useGeocoderSearch** — Shared geocoder→map search logic (kind-switch + Mapbox filter builder + the + `import … from '@map/composables/useGeocoderSearch.js'` +- **useMapAttribution** — useMapAttribution.js — the data-credit line every map MUST carry. + `import … from '@map/composables/useMapAttribution.js'` +- **useMapClustering** — useMapClustering.js — Map Clustering Composable + `import … from '@map/composables/useMapClustering.js'` +- **useMapData** — useMapData.js - Map Data Loading Composable + `import … from '@map/composables/useMapData.js'` +- **useMapEvents** — useMapEvents.js - Map Event Handlers Composable + `import … from '@map/composables/useMapEvents.js'` +- **useMapFly** — useMapFly.js - Map Navigation/Fly Composable + `import … from '@map/composables/useMapFly.js'` +- **useMapInstance** — useMapInstance.js — Map Initialization Composable + `import … from '@map/composables/useMapInstance.js'` +- **useMapLayers** — useMapLayers.js - Map Layers Composable + `import … from '@map/composables/useMapLayers.js'` +- **useMapPacket** — useMapPacket — render a multi-page "prayer packet" PDF for a map selection. + `import … from '@map/composables/useMapPacket.js'` +- **useMapProvider** — useMapProvider.js — resolve the active map engine ("provider"). + `import … from '@map/composables/useMapProvider.js'` +- **useMapSelection** — useMapSelection.js - Map Selection Composable + `import … from '@map/composables/useMapSelection.js'` +- **useMapTheme** — useMapTheme.js — shared light/dark theme for every map profile. + `import … from '@map/composables/useMapTheme.js'` +- **usePplrInstance** — usePplrInstance.js — per-map "instance store" mediator. + `import … from '@map/composables/usePplrInstance.js'` +- **usePrayedLocally** — usePrayedLocally.js — the "my dot glows" encouragement loop. + `import … from '@map/composables/usePrayedLocally.js'` +- **usePrayerStatistics** — usePrayerStatistics.js — the ONE authoritative prayer-coverage source of truth. + `import … from '@map/composables/usePrayerStatistics.js'` +- **useSearch** — useSearch.js — Local geocoder search composable + `import … from '@map/composables/useSearch.js'` +- **useSelectedPin** — useSelectedPin.js - Selected Pin Highlight + Animated GO Marker + `import … from '@map/composables/useSelectedPin.js'` +- **useShadowStyles** — useShadowStyles.js — Shadow DOM Style Injection + `import … from '@map/composables/useShadowStyles.js'` + +### Utilities (pure helper functions) + +- **ClusterHelpers** — ClusterHelpers.js — Pure helpers shared across clustering modes. + `import … from '@map/utils/ClusterHelpers.js'` +- **DataSourceManager** — Back-compat shim. The real DataSourceManager.js lives in the api/ seam + `import … from '@map/utils/DataSourceManager.js'` +- **MSTClusteringUtil** — MSTClusteringUtil.js + `import … from '@map/utils/MSTClusteringUtil.js'` +- **NetworkClusteringUtil** — NetworkClusteringUtil.js + `import … from '@map/utils/NetworkClusteringUtil.js'` +- **apiBaseUrl** — Back-compat shim. The real apiBaseUrl.js lives in the api/ seam folder. + `import … from '@map/utils/apiBaseUrl.js'` +- **embedBase** — embedBase.js — Where THIS bundle's built tree is served from, resolved at runtime. + `import … from '@map/utils/embedBase.js'` +- **geoUtils** — geoUtils.js — Geographic / GeoJSON Utility Functions + `import … from '@map/utils/geoUtils.js'` + +### Stores (Pinia — shared state) + +- **dataStore** — dataStore.js — Centralized data cache + `import … from '@map/stores/dataStore.js'` +- **mapStore** — Map Store - Centralized state management for all map instances + `import … from '@map/stores/mapStore.js'` +- **uiStore** — UI Store - Centralized UI state management + `import … from '@map/stores/uiStore.js'` + +### Constants (never-change values) + +- **mapDefaults** — mapDefaults.js — Default map initialization settings + `import … from '@map/constants/mapDefaults.js'` +- **posterDefaults** — posterDefaults.js — default margins, bleed, slot sizes, and typography + `import … from '@map/constants/posterDefaults.js'` +- **posterSizes** — posterSizes.js — preset poster dimensions (US + ISO). + `import … from '@map/constants/posterSizes.js'` +- **zoom** — zoom.js - Centralized Zoom Configuration + `import … from '@map/constants/zoom.js'` + +### Data (static reference tables) + +- **iso-numeric-to-alpha2** — "4":"AF","8":"AL","12":"DZ","24":"AO","32":"AR","36":"AU","40":"AT","50":"BD", + `import … from '@map/data/iso-numeric-to-alpha2.json'` +- **langFamilyByIso** — "aar": "Afro-Asiatic", + `import … from '@map/data/langFamilyByIso.json'` +- **langFamilyByLanguage** — {"Chamacoco":"Zamucoan","Ninam":"Yanomaman","Yagua":"Yaguan","Muinane":"Witotoan","Finnish":"Uralic","Hill Mar + `import … from '@map/data/langFamilyByLanguage.json'` + +### API / data sources (`@map/api`) + +The library provides the data-source **mechanism**; you declare WHICH sources your map uses +in your own `src/api/sources.json`. Add a REST API, CSV, or MCP source by adding one entry — +no code. Mechanism (do not edit): `@map/api/_registry.js`, `@map/api/DataSourceManager.js`. + +### Colors (`@map/colors`) + +The library provides the color-strategy **registry** (the mechanism). Shared color taxonomies +(religion, adoption, engagement, language-family) live here. Your map's OWN colors live in your +app-profile's `src/colors/` folder — strategy `.js` files auto-merge OVER the shared set +for your map only, so editing them can't break other maps. Mechanism (do not edit): `@map/colors/_registry.js`. diff --git a/internal/check-library-index.mjs b/internal/check-library-index.mjs new file mode 100644 index 000000000..6ff523799 --- /dev/null +++ b/internal/check-library-index.mjs @@ -0,0 +1,65 @@ +#!/usr/bin/env node +/** + * check-library-index.mjs — the derived library menu must not fall behind library/. + * + * THE BUG THIS EXISTS TO PREVENT (real, 2026-09-21) + * internal/generate-library-index.mjs opens with: "The index is DERIVED, never hand-edited — so it + * can't drift from the code." That guarantee had NO MECHANISM behind it: the script was invoked by + * no package.json entry, and docs/reference-library-index.md DID NOT EXIST AT ALL. A documented + * invariant enforced only by a human remembering is a wish, not an invariant. + * + * WHY RAW SOURCE IS THE RIGHT ANCESTOR HERE (and was NOT for the screenshots) + * check-shot-freshness compares a PNG against the BUILT BUNDLE, because a screenshot renders a + * built leaf and an unbuilt edit is not yet visible in one. This index is different: it is + * generated by SCANNING library/ directly, with no build step in between. So the honest ancestor + * is the newest library/ source file. ★ THE ANCESTOR IS WHATEVER THE GENERATOR ACTUALLY READS — + * copying the other gate's choice would have been wrong. + * + * NEGATIVE-TESTED: `touch library/layouts/StackLayout.vue` makes this FAIL naming that file; + * `bun run library-index` returns it to PASS. + */ +import { readdirSync, existsSync, statSync } from 'node:fs' +import { resolve, dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const INDEX = join(ROOT, 'docs', 'reference-library-index.md') +const LIB = join(ROOT, 'library') + +if (!existsSync(LIB)) { + console.log('LIBRARY-INDEX: SKIP — no library/ directory.') + process.exit(0) +} + +if (!existsSync(INDEX)) { + console.error('LIBRARY-INDEX: FAIL — docs/reference-library-index.md does not exist.') + console.error(' It is the menu of reusable pieces an author reads INSTEAD of rebuilding them.') + console.error(' Fix: bun run library-index') + process.exit(1) +} + +// Newest source the generator actually scans. +let newest = 0, newestFile = null +const walk = (d) => { + for (const e of readdirSync(d, { withFileTypes: true })) { + if (e.name === 'node_modules' || e.name.startsWith('.')) continue + const p = join(d, e.name) + if (e.isDirectory()) { walk(p); continue } + if (!/\.(vue|js|mjs)$/.test(e.name)) continue + const m = statSync(p).mtimeMs + if (m > newest) { newest = m; newestFile = p.slice(ROOT.length + 1) } + } +} +walk(LIB) + +const idxMs = statSync(INDEX).mtimeMs +console.log(`LIBRARY-INDEX: index vs ${newestFile ? 'newest library source' : 'library/'}.`) + +if (newest > idxMs) { + console.error(`\nLIBRARY-INDEX: FAIL — the derived menu is OLDER than library/.`) + console.error(` ✗ ${newestFile} is ${Math.round((newest - idxMs) / 1000)}s newer than the index`) + console.error('\n The file says "DERIVED — cannot drift from the code"; that is only true if it is') + console.error(' regenerated. Fix: bun run library-index') + process.exit(1) +} +console.log('LIBRARY-INDEX: PASS — the derived menu is newer than every library/ source.') diff --git a/package.json b/package.json index da026326b..11f38052d 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,9 @@ "check:sri": "node internal/sri-consistency-check.mjs", "check:composite": "node internal/check-composite-freshness.mjs # a COMPOSITE is a COPY taken at its own build time: fails if a nested child's source is newer than the parent's bundle. NOT in build:unlocked on purpose \u2014 there everything was just built, so it could never catch the real case (a PARTIAL build). Run it before PUBLISH.", "check:shots": "node internal/check-shot-freshness.mjs # a README screenshot that predates the bundle it shows is a picture telling a lie \u2014 and it looks IDENTICAL to a current one. Compares each shot's PNG against the BUILT BUNDLE it renders (not raw sources: an unbuilt edit is not yet in any leaf). Fix: node internal/readme-shots.mjs", + "check:library-index": "node internal/check-library-index.mjs # the DERIVED library menu must not fall behind library/. Compares the index against the newest library SOURCE (not a bundle \u2014 this generator scans library/ directly, with no build step between). Fix: bun run library-index", "shots": "node internal/readme-shots.mjs # regenerate docs/readme-images/*.png (fetches its own Mapbox token SERVER-SIDE, so the browser CORS block does not apply). Was UNREACHABLE \u2014 present but invoked by no script, which is how the 12 PNGs silently rotted a day behind the UI.", + "library-index": "node internal/generate-library-index.mjs # regenerate docs/reference-library-index.md \u2014 the DERIVED menu of the 84 reusable library pieces an author can import instead of rebuilding. Was UNREACHABLE, and the index did not exist at all: its header promised 'can't drift from the code' while nothing ever ran it.", "check:done-evidence": "node internal/check-done-evidence.mjs # a card may not reach status:done with NO bluf AND NO fields \u2014 the Doxa team inherits this board and an evidence-less 'done' cannot be reconciled against the code. 10 pre-existing are baselined by id; the list should only ever SHRINK.", "check:wp-embed": "php wordpress/tests/wp-embed-contract-test.php", "check:mobile": "bun run cap:sync && bun run cap:verify # deploy-2 CONTRACT check: needs NO JDK/SDK/device. cap sync is a file copy + plugin wiring, so it runs anywhere; cap:verify then asserts assets/public/index.html exists. Guards the SOFT FAILURE capacitor.config.json warns about: a wrong webDir yields an EMPTY assets/public and a BLANK WHITE SCREEN at launch, not an error.",