From 19e1213e7119e0e931f82753d60a6f258e383c5b Mon Sep 17 00:00:00 2001 From: doxa-maps Date: Fri, 11 Sep 2026 18:42:35 +0000 Subject: [PATCH] =?UTF-8?q?docs:=20skimmer=20pass=20=E2=80=94=20README=20t?= =?UTF-8?q?o=201=20diagram=20+=202=20tables;=20how-to=20task=20index;=20do?= =?UTF-8?q?cs/=20router;=20CONTRIBUTING=20384=E2=86=9261=20(patterns+histo?= =?UTF-8?q?ry=20=E2=86=92=20architecture)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTRIBUTING.md | 403 ++----------------- README.md | 113 ++---- docs/README.md | 18 + docs/architecture/README.md | 1 + docs/architecture/patterns-and-provenance.md | 99 +++++ docs/how-to.md | 8 + docs/reference.md | 1 + 7 files changed, 208 insertions(+), 435 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/architecture/patterns-and-provenance.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d01910dae..1034c3764 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,384 +1,61 @@ -# Contributing to doxa-maps — building, embedding, and shipping a map bundle +# Contributing to doxa-maps -Maps sit on the edge of your ecosystem: built once, they migrate with you. -WordPress → Nuxt → whatever comes next, the maps come along unchanged. Embed them -anywhere with a single script tag. +Everything a contributor needs, short. Recipes live in [docs/how-to.md](./docs/how-to.md), +lookups in [docs/reference.md](./docs/reference.md), depth in +[docs/architecture/](./docs/architecture/). -> **TL;DR — the one rule that surprises everyone:** -> To see your map changes in the served site, run **`bun run build`** — **NOT -> `bun run dev`**. The build is what writes your bundle into `public/`, the -> served web-root. There is no HMR across that boundary. -> `bun run dev` is the fast HMR loop for working on a map **in isolation**. - -## The mental model - -This is a **standalone Vite project** with no host framework of any kind. It -compiles each map into a self-contained `.js` bundle — an IIFE that registers a -custom element — which any page can load as a plain ` - - -``` - -### Pattern B — Multi-profile bundle (several profiles, selected by name) - -**Use when:** the maps are **genuinely different map types** (different `.vue` -logic/layout), but you want them to **share one build** because they import the -same slice of the template. - -One bundle, **multiple** `.vue` profiles at the folder root. The host chooses which -profile to mount via the `profile` field — `ProfileLoader` resolves it against the -bundle's `import.meta.glob('./*.vue')` registry (key `./.vue`). Still -**one script tag**; you select the map per element with `profile`. - -**Example — `my-upg-100-list`:** two profiles in one bundle — -`upg-100-list.vue` and `research-map-clone.vue` — both served by the single -`my-upg-100-list-map` element. - -```html - - - -``` - -> **Note — profiles are selected by `profile`, not by separate script tags.** A -> bundle ships exactly one `.js`. Whether it has one profile (Pattern A) or -> many (Pattern B), you load that one script and pick the profile per element via -> `profile-config`. (A bundle *may* additionally register more than one element -> **tag** — e.g. `doxa-simple-map` registers both `` and -> `` for back-compat — but that's a tag-aliasing concern, separate -> from how many maps the bundle offers.) - -### Which to pick - -| | Pattern A — parameterized | Pattern B — multi-profile | +| Command | What | Writes to `public/`? | |---|---|---| -| `.vue` profiles in the bundle | one | several | -| Maps differ by | config **parameters** | distinct `.vue` **logic** | -| Select a map with | different `profile-config` (same `profile`) | the `profile` field | -| Script tags | one | one | -| Example | `doxa-simple-map` (3 instances) | `my-upg-100-list` (2 profiles) | +| `bun run dev` | HMR playground at `localhost:5173` — iterate on a map in isolation | **No** | +| `bun run build` | every bundle → `public/doxa-maps//.js` + generated pages | **Yes** | ---- +To see a change on the served site: `bun run build` (or `BUNDLE= bun run build:bundle`), +then refresh. `dev` never updates the served tree. -## Where this comes from — the migration story +**Two-terminal workflow** for integration work: terminal 1 `bun run build:watch` (rebuilds +into `public/` on save) · terminal 2 serves the host site / `public/`. -These maps were built **modular-first**, as standalone embeddable widgets, while -the host site still ran on **WordPress**. Each map was a ` - -``` - -This is the default for any page served from the same web-root. The -build writes straight to where the host reads, so there is no copy step, symlink, -or upload — see "Where the bundles go" below. - -### External CDN mode (a third party serves it) - -The exact same built `.js` can be published to a **public CDN** and loaded -from an absolute URL by a page on **any other site**. Nothing about the bundle -changes — it is self-contained, registers its own custom element, and pulls the -Mapbox peer from Mapbox's own CDN. - -```html - - - -``` - -The choice between modes is purely **the `src` URL on the page** — relative -(`/doxa-maps/...`, same-origin) vs absolute (`https://cdn.../...`, external). -The build itself only ever produces one thing: a portable IIFE. - -Either way the map needs a **Mapbox token**, and a page outside this web-root has -no automatic way to get one. Pass it in the element's `profile-config` as `tk`, or -set `window.MAP_TOKEN_URL` to an endpoint that returns it — the same contract the -app shell and the generated pages use. Without one the bundle loads and the -basemap stays blank. - -### Partner-site embedding - -A **partner site** is any external site that wants to show one of these maps. It -uses **external CDN mode**: it loads the Mapbox peer scripts, then the bundle from -a CDN URL, then drops in the custom element with its own `profile-config`. No -framework, no build step, and no access to this repo is required on their end. - -```html - - - - - - - -``` - -Because the bundle is the same artifact in every mode, a map you tested in -isolation here, and that runs in the app shell, will run on a partner site too — -the only difference is the URL the partner points their script tag at. - ---- - -## The two build steps - -There are two commands, and they do **different jobs**. Knowing which is which is -the whole game. - -### `bun run dev` — HMR staging (work on a map in isolation) - -```bash -bun run dev # Vite dev server for THIS project, with HMR -``` - -`dev` serves every bundle at its own staging page **inside this project**, with -hot module replacement — save a `.vue`, see it instantly. This is the fast loop -for building and styling a map **on its own**, away from the host. - -**`dev` does NOT touch the served web-root.** It writes nothing to -`public/doxa-maps/`. If you only ever run `dev`, the served site keeps showing -the **old** bundle. - -### `bun run build` — bundle to `public/doxa-maps/` - -```bash -bun run build # build every bundle → public/doxa-maps//.js -``` - -`build` compiles each bundle and writes it into the web-root's static -directory. **This is the only command that updates what the parent (or a CDN) -serves.** After a build, refresh the parent page to see the change. - -| | `bun run dev` | `bun run build` | -|---|---|---| -| Purpose | iterate on a map in isolation | publish the bundle for hosts/CDNs | -| HMR | yes — instant on save | n/a (one-shot compile) | -| Writes to `public/doxa-maps/` | **no** | **yes** | -| Updates the served site | **no** | **yes** (after a page refresh) | -| Updates a partner/CDN copy | no | yes (it produces the file to publish) | - -> Bun is the primary toolchain (see `bun.lock`); `npm` works identically — the -> scripts are the same. Use whichever the rest of your environment uses. - ---- - -## The two-terminal dev workflow - -For day-to-day work you want **two terminal tabs** open at once: - -**Tab 1 — this project (the map source).** Pick one: - -```bash -# in the doxa-maps repo -bun run dev # HMR staging: iterate on the map in isolation -# — or, when you want the parent to update on every save — -bun run build:watch # rebuild into public/doxa-maps/ on every change -``` - -**Tab 2 — the app shell.** Run the host's own dev server (from the host -root), and keep its page open in the browser: - -```bash -# in the served site root -bun run dev # the host dev server; open the page that embeds the map -``` - -The split is deliberate: - -- **Tab 1 with `bun run dev`** is for *building the map* — fast HMR, no host - involved. When it looks right, run `bun run build` once to push it across. -- **Tab 1 with `bun run build:watch`** is for *integration work* — every save - rebuilds into the web-root, and the **Tab 2** host page picks up the - new bundle on refresh. This is the closest you get to a live loop across the - boundary. - -You do not need both at once. Use `dev` while shaping a map, switch to -`build`/`build:watch` when you want to see it inside the host. - ---- - -## Where the bundles go (and why it just works) - -`vite.config.js` sets the output directory **three levels up**, into the parent -app's public assets: - -```js -build: { - outDir: resolve(__dirname, 'public/doxa-maps'), // ← the served web-root - emptyOutDir: false, // ← never wipes sibling bundles -} -``` - -- The build writes straight into **`public/doxa-maps/`**, which is inside the - served web-root — so no copy step or symlink is needed; it writes exactly where - a host reads. (To serve a bundle from an **external CDN**, publish that same - file to the CDN.) -- `emptyOutDir: false` means a build of **one** bundle leaves the **other** - bundles untouched. You can rebuild a single map without breaking the rest. - -Each folder under `app-profiles//` produces exactly one file: -`public/doxa-maps//.js`. `bun run build` loops over every -`app-profiles/*/` folder and builds them all. - -### Build just one bundle (faster iteration) - -`bun run build` rebuilds every bundle. To rebuild only the one you're editing: - -```bash -BUNDLE= bun run build:bundle -# e.g. BUNDLE=my-map bun run build:bundle → public/doxa-maps/my-map/my-map.js -``` - -(`` is the folder name under `app-profiles/`.) - ---- - -## Troubleshooting - -| Symptom | Cause | Fix | -|---|---|---| -| Changed a `.vue`, parent shows old map | You ran `dev`, or didn't rebuild | Run `bun run build`, then refresh the parent page | -| Build error: `Set BUNDLE= for build` | Ran `vite build` directly with no bundle selected | Use `bun run build` (loops all) or `BUNDLE= bun run build:bundle` | -| New bundle doesn't appear in parent | Missing `index.html` or `index.js` in the `app-profiles//` folder | Vite auto-discovers a bundle only when both files exist | -| Old bundle still loads after a hard build | Browser cached the static `.js` | Hard-refresh / disable cache in devtools | -| Partner/CDN copy is stale | The CDN still serves the old file | Re-publish the freshly built `.js` to the CDN (and bust its cache) | -| Map is blank on a partner page | Mapbox peer scripts not loaded, or no token | Load `mapbox-gl.js` + geocoder **before** the bundle; pass a valid `tk` in `profile-config` | - ---- +- Never edit `library/` to customize one map (bundle-private code goes in your `src/`). +- Never bake tokens or data into a bundle — everything arrives at runtime via `profile-config`. +- Before a PR: `bun run build && bun run verify` must pass (adds drop-portability + parse gates). +- Colors are values, not logic: [contributing/COLORS.md](./contributing/COLORS.md). ## One-line recap -**Edit here → `bun run dev` to shape it → `bun run build` to ship it.** The build -*is* the bridge: it writes the same portable bundle that the served site serves -(internal CDN) and that partner sites load from a CDN URL (external CDN). `dev` -only runs this project in isolation. +Copy `app-profiles/template-bundle/`, change exactly four things (folder name, tag in +`index.js`, tag/profile strings in `index.html`, `.vue` filenames), build everything inside +your folder from `@map` blocks, `bun run build`, embed the snippet. That's the whole job. diff --git a/README.md b/README.md index 3ec5484e1..8fd943550 100644 --- a/README.md +++ b/README.md @@ -1,71 +1,42 @@ # doxa-maps -**A bundler that turns your maps into small embeddable pieces you can drop onto any website.** +**A bundler that turns your maps into embeddable pieces you can drop onto any website.** -> ### ✨ New here, and you don't write code? -> You can build real, embeddable maps by **describing them to an AI coding agent** — no -> programming required. Start with the friendly, zero-jargon guide: +``` + app-profiles// bun run build where they run + ┌──────────────────┐ ┌──────────────────┐ ┌─────────────────────────┐ + │ your map screens │ │ one .js per map │ │ ▸ any partner website │ + │ (.vue) + colors + │──────► │ Vue + CSS inlined │────► │ ▸ a SPA (several maps │ + │ data wiring │ │ tree-shaken IIFE │ │ in one layout) │ + └──────────────────┘ │ + an index.html │ │ ▸ Android/iOS app │ + shared library/ ──────► │ per map page │ │ (Capacitor) │ + (@map: legend, map, └──────────────────┘ └─────────────────────────┘ + controls — reused) public/doxa-maps/… one build → three products +``` + +| The idea | In one line | +|---|---| +| **Bundle** | one self-contained `.js` = a custom HTML tag (``) — drop it on any page, no framework, no build step for the host | +| **Embeddable by default** | every map also gets a generated standalone page — link it or iframe it (Start.me, Notion, any CMS) | +| **Many maps, one bundle** | a bundle holds as many map screens as you want; add a `.vue`, rebuild | +| **Lean & fast** | tree-shaken, Vue + CSS inlined → **one request**, faster than loading Vue from a CDN, nothing to 404 | +| **JAMstack hot-swap** | data API, token, even the map engine (Mapbox ↔ MapLibre) are runtime settings — change them with **no rebuild** | +| **SPA** | compose several maps into one lazy-loaded layout → a full map site from the same build | + +> ✨ **Don't write code?** Describe your map to an AI coding agent — start at > **[docs/tutorial.md](./docs/tutorial.md)**. -## In plain words - -**What a bundler is.** It's a tool that takes your map code and packages it into a small, -ready-to-use file. You write the map once; the bundler produces the shippable piece. - -**What it outputs.** Plain `.js` files in the `public/` folder — one per bundle. That's it. -No server, no build step on anyone else's side. Serve the folder from anywhere and the maps work. - -**What a bundle is.** A self-contained web component — a custom HTML tag like ``. You add -one ` + + +``` + +### Pattern B — Multi-profile bundle (several profiles, selected by name) + +**Use when:** the maps are **genuinely different map types** (different `.vue` +logic/layout), but you want them to **share one build** because they import the +same slice of the template. + +One bundle, **multiple** `.vue` profiles at the folder root. The host chooses which +profile to mount via the `profile` field — `ProfileLoader` resolves it against the +bundle's `import.meta.glob('./*.vue')` registry (key `./.vue`). Still +**one script tag**; you select the map per element with `profile`. + +**Example — `my-upg-100-list`:** two profiles in one bundle — +`upg-100-list.vue` and `research-map-clone.vue` — both served by the single +`my-upg-100-list-map` element. + +```html + + + +``` + +> **Note — profiles are selected by `profile`, not by separate script tags.** A +> bundle ships exactly one `.js`. Whether it has one profile (Pattern A) or +> many (Pattern B), you load that one script and pick the profile per element via +> `profile-config`. (A bundle *may* additionally register more than one element +> **tag** — e.g. `doxa-simple-map` registers both `` and +> `` for back-compat — but that's a tag-aliasing concern, separate +> from how many maps the bundle offers.) + +### Which to pick + +| | Pattern A — parameterized | Pattern B — multi-profile | +|---|---|---| +| `.vue` profiles in the bundle | one | several | +| Maps differ by | config **parameters** | distinct `.vue` **logic** | +| Select a map with | different `profile-config` (same `profile`) | the `profile` field | +| Script tags | one | one | +| Example | `doxa-simple-map` (3 instances) | `my-upg-100-list` (2 profiles) | + +--- + diff --git a/docs/how-to.md b/docs/how-to.md index cb237be9a..a6d8815c4 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -7,6 +7,14 @@ first, then use this page to look up a task. For contracts and field tables, see The three zones govern everything: **reuse `library/` (`@map`), build in `app-profiles//`, never touch `internal/`.** +**Pick your task:** +[embed on a site](#embed-a-map-on-a-partner-site) · [link/iframe directly](#link-or-iframe-a-map-directly-zero-effort-embed) · +[add a map](#add-a-new-map-profile) · [imitate a profile](#imitate-an-existing-profile-dont-re-derive-it) · +[change colors](#change-colors) · [choose a CDN](#where-do-the-maps-get-served-from) · +[build a SPA](#compose-maps-into-a-spa-the-app-shell) · [mobile app](#build-the-android--ios-app-optional) · +[API + token](#set-the-api--mapbox-token) · [agent guardrail](#give-your-agent-the-guardrail) · +[troubleshooting](#troubleshooting) + --- ## Embed a map on a partner site diff --git a/docs/reference.md b/docs/reference.md index 5c86d08c8..392d48cdd 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -221,6 +221,7 @@ Simple lookups live here; the full contracts, catalogs, and wiring detail live i | [embeddability.md](./architecture/embeddability.md) | generated per-map pages + manifest — embeddable by default | | [app-wrappers.md](./architecture/app-wrappers.md) | the SPA shell: `nav.js` + `PROFILES` contracts | | [poster-generation.md](./architecture/poster-generation.md) | the poster/preview generator pipeline | +| [patterns-and-provenance.md](./architecture/patterns-and-provenance.md) | the two multi-map patterns + provenance | The **Semantic Tree Legend** component (a reusable hierarchical legend) is documented in [file-map.md](./architecture/file-map.md#the-semantic-tree-legend-mapcomponentslegendssemantictreelegendvue).