# Reference Look-up tables and contracts for doxa-maps. For learning, see the **[tutorial](./tutorial.md)**; for tasks, **[how-to.md](./how-to.md)**; for the *why*, **[explanation.md](./explanation.md)**. `doxa-maps` is a Vite bundler: **one shared library, N independent embeddable bundles.** Each bundle is a single self-contained `.js` file that registers a custom HTML element (``, ``, …) you can drop on any web page. --- ## The three zones | Zone | Path | Alias | License | You edit it… | |---|---|---|---|---| | **Shared library** | `library/` | `@map` | Apache-2.0 | …once; the fix reaches every bundle on next build | | **Your app-profile** | `app-profiles//` | — | yours | …freely; nothing here leaks to other maps | | **The build machine** | `internal/` | — | Apache-2.0 | …never — it's the bundler's own scripts | **Terminology.** "Profile" is used at two levels: a **bundle / app-profile** is a folder under `app-profiles/`; a **profile screen** is one `.vue` file sitting **flat** in that folder (no `profiles/` subfolder — the entry globs `./*.vue`). A bundle can hold several screens; the host picks which to render with the `profile` field of `profile-config`. **Three profile patterns** (mix freely in one bundle): **Individual** (one `.vue` per bundle) · **Parameterized** (one screen driven by `profile-config` props at runtime) · **Nested** (one screen that renders others via tabs, workbench-style). --- ## Directory tree ``` doxa-maps/ ├── library/ ← SHARED LIBRARY (alias @map, Apache-2.0) │ ├── index.js ← thin barrel; prefer subpath imports for tree-shaking │ ├── ProfileLoader.vue ← the ONE component that reads profile-config & loads a screen │ ├── components/ ← map UI: legends/, map-controls/, poster/, dashboards/, drawers │ ├── composables/ ← useMapInstance, useMapData, useMapLayers, … (+ legends/, poster/) │ ├── api/ ← DataSourceManager (csv | api | rest-api) + sources.json │ ├── colors/ ← one color strategy per file, auto-registered by _registry.js │ ├── constants/ ← never-change values: mapDefaults.js, zoom.js, poster*.js │ ├── i18n/locales/ ← 11 base locales: ar de en es fr hi it pt ro ru zh │ ├── stores/ ← Pinia: dataStore, mapStore, uiStore (per-instance) │ └── utils/ ← pure helpers (geoUtils, clustering) │ ├── app-profiles/ ← ALL bundles live here (each subfolder = 1 bundle) │ ├── / │ │ ├── index.js ← bundle ENTRY — globs ./*.vue, registers the web component │ │ ├── index.html ← Vite html entry = the dev STAGING page │ │ ├── .vue ← 1..N FLAT profile screens (no profiles/ subfolder) │ │ └── src/ ← everything else the map needs (its own colors/, api/, …) │ └── template-bundle/ ← minimal copy-me reference bundle │ (folders starting with _ or . are SKIPPED by the build) │ ├── internal/ ← the bundler's own build scripts — never edited by map builders ├── layouts/ ← the app shell: mounts several bundles as one single-page app ├── capacitor/ ← native projects (android/, ios/) ├── public/ ← THE SERVED WEB-ROOT, committed: │ ├── index.html the app shell │ ├── doxa-maps// built bundles + manifest.json + staging HTML │ └── assets/ posters & data the maps fetch at runtime ├── vite.config.js ← multi-entry: discovers app-profiles// folders └── vite.doxa-maps-build-config.json ← output tree name + copy destinations + token settings ``` --- ## The `profile-config` runtime contract A bundle exposes ONE attribute, `profile-config` — a JSON string parsed by `ProfileLoader.vue` at runtime. It is the entire wiring between host page and map, so the same built bundle re-points to different data/colors/language with **no rebuild**. ```html ``` - `profile` **must** match a flat `.vue` filename in the bundle. Mismatch → an inline "Profile not found" error box listing the available names. - `tk` is supplied at runtime so the token is never compiled in (dev uses `.env`'s `VITE_MAPBOX_TOKEN`). - `instanceId` scopes cross-instance window events so multiple embeds never interfere. - `dataSource` is handed to `DataSourceManager` (`csv` | `api` | `rest-api`). - `ProfileLoader` `provide()`s to descendants: `mapboxToken`, `dataSource` (both **computed refs** — `unref` before use), `colorSet`, `profileConfig`, `instanceId`, `lang`, and the instance-scoped Pinia stores `uiStore` / `mapStore` / `dataStore`. Descendants **inject**; never call `useXxxStore()` directly (it would cross-bleed between embeds). Each bundle registers a **shadow-DOM** web component via `defineCustomElement`, with its own ShadowRoot, styles, Pinia, and i18n. `customElements.define()` is one-shot per tag name, so each bundle registers a **distinct** tag. --- ## Build scripts & output | Script | What it does | |---|---| | `bun run dev` | Vite dev server + HMR (default `http://localhost:5173`). Serves every bundle at its html path. **Writes nothing to `public/`** — no HMR across that boundary. | | `bun run build` | `clean` → one `vite build` per `app-profiles/*/` folder → `tree` → `check:drop` → `check:parse`. | | `bun run build:bundle` | Builds ONE bundle (needs `BUNDLE=`). | | `bun run build:watch` | Rebuilds into `public/` on every change (integration loop). | | `bun run clean` | Wipes + recreates the `public//` output tree. | | `bun run tree` | Writes `manifest.json` + self-contained staging HTML, then copies the tree to every build-config `paths` destination. | | `bun run verify` | Data-integrity + shipped-bundle + network-policy guards (fast, no browser). | | `bun run verify:full` | + rendered posters and live apps in Firefox. | **How a build works.** `vite.config.js` is multi-entry by discovery: at build time it registers every `app-profiles/` subfolder that (a) doesn't start with `_`/`.` and (b) has **both** `index.html` and `index.js`. One `vite build` builds **exactly one** bundle, selected by `BUNDLE=`, so the output is a self-contained **IIFE** (`inlineDynamicImports: true`). `bun run build` loops every folder. Each bundle only bundles what its screens import, so sizes differ (simple ≈ 420 KB, research ≈ 1.3 MB). **Where output goes.** One bundle folder → one file: `public/doxa-maps//.js`. CSS is injected by JS (`cssCodeSplit: false`), so there is no separate `.css`. The tree root name comes from `vite.doxa-maps-build-config.json` `name` (default `doxa-maps`). `emptyOutDir: false` keeps sibling bundles intact when you rebuild one. **Bun is required.** `bun.lock` is the committed lockfile; `bun x vite` runs project-local Vite. `npm run …` resolves the same scripts but Bun must be on PATH. --- ## `vite.doxa-maps-build-config.json` fields Edit this one file, then `bun run build`. | Field | What it does | |---|---| | `name` | Output folder name (default `doxa-maps`). | | `paths` | **Extra** folders to copy the whole built tree into. Point at a host repo's `public/` → that host serves the maps and becomes the CDN. Empty (the default) ⇒ written to `public//` only, since `public/` is already the committed web-root. | | `embedBaseUrl` | The public URL the tree is served from, used to generate **absolute, paste-anywhere** embed snippets. Empty ⇒ relative URLs (position-independent; keeps the drop-portability gate green; required for Capacitor, which loads from `https://localhost`). | | `tokenUrl` | Endpoint each generated page fetches its Mapbox token from — a URL, **not** a secret. Set absolute (`https://…/api/maps/token`) so a CDN folder and the mobile app both work; endpoint must send CORS for the embedding origins. `window.MAP_TOKEN_URL` overrides at runtime. | | `token` | Opt-in literal token baked into every page. **Leave empty** — prefer `tokenUrl`. | | `assetsBaseUrl` | Where the shared asset tree (previews, posters, boundaries) is served from. Empty ⇒ a relative path per page depth (self-contained under one web-root). Set absolute for partner embeds and mobile, where there is no shared web-root to climb to. | **Token resolution ladder:** per-map `tk` prop > `?tk=` > baked `token` > `window.MAP_TOKEN_URL` > `tokenUrl` > same-origin `/api/maps/token`. --- ## Data sources Maps read data through `@map/api/` driven by `library/api/sources.json`. Three source types: | Type | What | Points at | |---|---|---| | `rest-api` | versioned REST endpoint (default) | the live people-group API; base URL resolves at runtime (`window.MAP_APP_API_URL` → `VITE_API_BASE_URL` → same-origin) | | `csv` | a local CSV parsed at load | the Joshua Project dataset (+ photo fallbacks) | | `api` | a single JSON URL | any plain JSON endpoint | Country/region polygons are **not** a `sources.json` connection — they render from Mapbox's built-in `country-boundaries-v1` tileset plus a lazy-loaded WAGF-regions GeoJSON. **Adding or changing a source?** Full contract + checklist: [architecture/data-sources.md](./architecture/data-sources.md). --- ## Colors **Reused by more than one map → shared `library/colors/`. Only your map uses it → your bundle's `src/colors/`** (merges over the shared set, that bundle only). Filename derives the mode key (`language-family.js` → `languageFamily`). **Writing or overriding a strategy?** Full contract + checklist: [architecture/color-strategies.md](./architecture/color-strategies.md) · two homes in depth: [../contributing/COLORS.md](../contributing/COLORS.md). --- ## Importing from `@map` Everything under `library/` is importable via the `@map/…` alias (`vite.config.js` maps `@map` → `library/`). The import path mirrors the file path: a file at `library//` imports as `@map//`. Examples: ```js import SemanticTreeLegend from '@map/components/legends/SemanticTreeLegend.vue' import { useMapInstance } from '@map/composables/useMapInstance.js' import { useMapData } from '@map/composables/useMapData.js' import langFamilyByIso from '@map/data/langFamilyByIso.json' ``` Prefer subpath imports over the thin `@map` barrel (which exports `MAP_CORE_VERSION` only) so tree-shaking works. The per-file catalog in [architecture/file-map.md](./architecture/file-map.md) is the authoritative menu — every `library/` file listed is importable this way. A generated one-line import index can also be produced with `node internal/generate-library-index.mjs`. --- ## Going deeper — the architecture folder Simple lookups live here; the full contracts, catalogs, and wiring detail live in [`architecture/`](./architecture/): | Doc | What it holds | |---|---| | [file-map.md](./architecture/file-map.md) | every file, one line each, with the gotcha each carries | | [data-sources.md](./architecture/data-sources.md) | the `api/` seam: connection contract + checklist | | [color-strategies.md](./architecture/color-strategies.md) | the strategy contract + legend wiring | | [components.md](./architecture/components.md) | shared component catalog + conventions | | [i18n.md](./architecture/i18n.md) | the locale contract + add-a-language checklist | | [app-wrappers.md](./architecture/app-wrappers.md) | the SPA shell / sidebar `nav.js` contract | | [poster-generation.md](./architecture/poster-generation.md) | the poster/preview generator pipeline | The **Semantic Tree Legend** component (a reusable hierarchical legend) is documented in [file-map.md](./architecture/file-map.md#the-semantic-tree-legend-mapcomponentslegendssemantictreelegendvue). --- ## Quick reference — "I want to…" | I want to… | Do this | |---|---| | Add a new map | copy `app-profiles/template-bundle/` → rename folder + tag in `index.js` | | Add a screen to an existing bundle | drop a `.vue` flat in that bundle's folder, request it via `profile-config.profile` | | Fix a legend / control for ALL maps | edit it once in `library/` (`@map`); rebuild bundles | | Add a color strategy | new file in `@map/colors/` (shared) or the bundle's `src/colors/` (map-private) | | Build everything | `bun run build` → `public/doxa-maps//.js` | | Build just one | `BUNDLE= bun run build:bundle` | | Preview in isolation | `bun run dev` → `http://localhost:5173/app-profiles//index.html` | | See a change on the served site | `bun run build`, then refresh (NOT `dev`) | | Repoint the API without a rebuild | set `window.MAP_APP_API_URL` before the bundle loads | | Repoint the Mapbox token | pass `tk` in `profile-config`, or set `window.MAP_TOKEN_URL` / build-config `tokenUrl` |