README: lead with the objectives, showcase the Research Gallery, teach the agent swarm
Some checks are pending
build-gate / build (push) Waiting to run
Some checks are pending
build-gate / build (push) Waiting to run
- Opens with what dx-maps is for: seven objectives, each paired with the gate or section that proves it is met. - New Research Gallery section: the cap-5 app shell and the tree of cap-4..1 plugins it nests, each still independently embeddable. - Plugin Studio: clone main (the old branch was 198 commits behind), a command table, and a fresh studio screenshot (the old one showed a retired nav and tofu icons). - Agent swarm: renamed to industry terms (trunk-based development, merge queue, maintainer), a laptop recipe (worktrees, strict ports, one builder, flock as the queue), and a redrawn diagram whose inner ring is an actual queue with one gate. SVG source committed next to the PNG. - MapLibre is now described as it is: a one-prop, token-free engine. - Removed internal credential/push instructions (AGENT-BRIEF.md covers agents). - documentation/: fixed 19 dead links left by the docs/ move, removed internal references, and listed the working docs in the index. - readme-shots: ONLY=<name> retakes a single image.
This commit is contained in:
parent
3fd1fbcaea
commit
c46a9a04bd
12 changed files with 336 additions and 208 deletions
421
README.md
421
README.md
|
|
@ -1,4 +1,4 @@
|
|||
# doxa-maps — one build, maps that embed everywhere
|
||||
# dx-maps — one build, maps that embed everywhere
|
||||
|
||||
> **The map cruncher.** Many hand-built maps go in; one shared library, one repo and one build
|
||||
> come out — and every map fans out to five deployment targets from the same artifacts.
|
||||
|
|
@ -18,24 +18,99 @@
|
|||
> (proven) (proven) (built) (built) (built)
|
||||
> ```
|
||||
|
||||
|
||||
**A Vite bundler that turns each map into a self-contained web component.** Write a map in
|
||||
`plugin-designs/<your-map>/`, run one build, and get a folder you can drop on any host — iframe it,
|
||||
script-embed it, wrap it as a mobile app, or open every map at once as one SPA.
|
||||
|
||||
This README has three parts, in the order you need them:
|
||||
|
||||
1. **[The 5 plugin capabilities](#1-the-5-plugin-capabilities)** — what you can build, simplest to richest.
|
||||
2. **[The 5 deployment methods](#2-the-5-deployment-methods)** — where a plugin can run.
|
||||
3. **[The Plugin Studio](#3-the-plugin-studio)** — ★ where you *work*. Start here if you are new.
|
||||
script-embed it, wrap it as a mobile app, or open every map at once as one app.
|
||||
|
||||
---
|
||||
|
||||
## 1. The 5 plugin capabilities — most advanced first
|
||||
## What dx-maps is for — and how each objective is met
|
||||
|
||||
Maps multiply a prayer movement only when they can live on *every* partner's site, not just one.
|
||||
dx-maps exists to make that cheap. It set out to meet seven objectives; each one is met by something
|
||||
you can open or run, not by a claim.
|
||||
|
||||
| # | Objective | How it is met | See it / check it |
|
||||
|---|-----------|---------------|-------------------|
|
||||
| 1 | **One build → one folder.** Seven maps that were seven codebases become one repo and one build. | `bun run build` writes a single drop-anywhere tree, `public/doxa-maps/`, with a `manifest.json` describing every bundle. | `bun run check:drop` · `bun run check:settled` |
|
||||
| 2 | **Every map is embeddable.** Any map, on any site, with a copy-paste snippet. | Each bundle is one custom element; `manifest.json` ships an iframe snippet and an SRI-pinned `<script>` snippet for it. | [Deployment methods](#3-the-5-deployment-methods) · `bun run check:embed-targets` |
|
||||
| 3 | **Every map is interoperable.** Maps talk to each other through props and events. | Composites wire their children with props, events and shared layouts — no copy-pasted code. | `bun run verify:interop` |
|
||||
| 4 | **One app nests every map — and each stays independently embeddable.** | The **Research Gallery** is a single capability-5 plugin that nests all seven production maps. | [The Research Gallery](#1-the-research-gallery--five-capabilities-in-one-plugin) · `bun run check:composite-reg` |
|
||||
| 5 | **5 capabilities × 5 deployment methods.** Anything you build, at any tier, ships everywhere. | A plugin's tier is read from its files; every tier builds to the same tree, and the tree feeds all five methods. | [Capabilities](#2-the-5-plugin-capabilities--most-advanced-first) · `bun run verify:5-caps` |
|
||||
| 6 | **A clean shared/private boundary.** Reuse without breaking each other. | `library/` holds shared, parameterized pieces (generated index); anything map-specific stays in its own `plugin-designs/` folder. | [reference-library-index.md](./documentation/reference-library-index.md) · `bun run check:library-index` |
|
||||
| 7 | **Many builders, one green `main`.** The repo is built to be worked by a swarm of agents at once. | One agent per plugin, one worktree each, and every change landed through a merge queue. | [Build it with an agent swarm](#5-build-it-with-an-agent-swarm) |
|
||||
|
||||
Plus one practical objective: **a token-free option.** Mapbox is the default engine; setting a single
|
||||
prop, `provider: "maplibre"`, swaps in MapLibre so a partner running many embeds needs no Mapbox key.
|
||||
`bun run check:provider-switch` proves the switch really changes the engine.
|
||||
|
||||
## Contents
|
||||
|
||||
1. **[The Research Gallery](#1-the-research-gallery--five-capabilities-in-one-plugin)** — the showcase: five capabilities in one plugin.
|
||||
2. **[The 5 plugin capabilities](#2-the-5-plugin-capabilities--most-advanced-first)** — what you can build, most advanced first.
|
||||
3. **[The 5 deployment methods](#3-the-5-deployment-methods)** — where a plugin can run.
|
||||
4. **[The Plugin Studio](#4-the-plugin-studio)** — ★ where you *work*, and every command to get it running. Start here if you are new.
|
||||
5. **[Build it with an agent swarm](#5-build-it-with-an-agent-swarm)** — how to run many agents on one laptop without collisions.
|
||||
|
||||
---
|
||||
|
||||
## 1. The Research Gallery — five capabilities in one plugin
|
||||
|
||||
The Research Gallery (`plugin-designs/doxa-maps-spa/`) is the product a user opens. It is **one
|
||||
capability-5 plugin** — one custom element, `<doxa-maps-spa>` — and every tab inside it is a
|
||||
**real, independently embeddable plugin** from a lower tier. Nothing is copied into it and nothing is
|
||||
framed: each tab loads its own bundle and mounts that bundle's own custom element.
|
||||
|
||||

|
||||
|
||||
This is how five different capabilities compose into one:
|
||||
|
||||
```text
|
||||
Research Gallery <doxa-maps-spa> capability 5 — app shell
|
||||
│
|
||||
├─ Dashboards
|
||||
│ ├─ Big Picture dashboard-plus-grid capability 4 — composite
|
||||
│ │ ├─ research dashboard research-dashboard capability 3 — monolithic
|
||||
│ │ └─ people-groups grid people-groups-grid capability 2 — prop-table
|
||||
│ ├─ Simple Research research-map-plus-grid capability 4 — composite
|
||||
│ │ ├─ research map doxa-research-map capability 3 — monolithic
|
||||
│ │ └─ people-groups grid people-groups-grid capability 2 — prop-table
|
||||
│ └─ Regions regions-mfe capability 4 — composite
|
||||
│ ├─ regions list regions-list capability 1 — single
|
||||
│ └─ region dashboards region-dashboards capability 1 — single
|
||||
├─ Prayer Mobilization
|
||||
│ ├─ UPG-100 List upg-100-mfe capability 4 — composite
|
||||
│ │ ├─ list upg-100-list capability 1 — single
|
||||
│ │ └─ poster wall poster-wall capability 1 — single
|
||||
│ └─ Prayer Progress doxa-simple-map (prayer) capability 2 — prop-table preset
|
||||
├─ Gospel Research
|
||||
│ ├─ Cities with Internet connectivity-map capability 1 — single
|
||||
│ └─ Gospel Resources resources-map capability 1 — single
|
||||
└─ Experimental (folder) every other bundle, discovered automatically from manifest.json
|
||||
```
|
||||
|
||||
What that buys you:
|
||||
|
||||
- **Seven production maps, one URL.** The three sections above are the seven curated tabs.
|
||||
- **Every tab is still a standalone embed.** Want only the UPG-100 list on a partner site? Embed
|
||||
`upg-100-mfe` — or just its `upg-100-list` leaf. Taking the whole app apart costs nothing.
|
||||
- **Proving the whole proves the parts.** When the Research Gallery was embedded cross-origin on a
|
||||
foreign host page (see [deployment](#3-the-5-deployment-methods)), every nested plugin rode along.
|
||||
- **New plugins appear on their own.** Any bundle not placed in a named section lands in
|
||||
*Experimental*, read from the built `manifest.json`. A new folder in `plugin-designs/` shows up with
|
||||
zero edits to the shell.
|
||||
|
||||
The tab order and grouping live in one place, `NAV_GROUPS` in
|
||||
[`doxa-maps-spa.vue`](./plugin-designs/doxa-maps-spa/doxa-maps-spa.vue); `bun run check:spa-tab-order`
|
||||
guards it.
|
||||
|
||||
---
|
||||
|
||||
## 2. The 5 plugin capabilities — most advanced first
|
||||
|
||||
A "capability" is **how much a plugin nests** — not how hard it is to use. The tour below starts from
|
||||
the most advanced thing this repo produces (a whole app of apps) and walks down to the simplest
|
||||
(one element), because the top of the ladder is the point: every rung below it stays independently
|
||||
the most advanced thing this repo produces (an app of apps) and walks down to the simplest (one
|
||||
element), because the top of the ladder is the point: every rung below it stays independently
|
||||
embeddable. Every screenshot is a real bundle from this repo's build output.
|
||||
|
||||
| # | Capability | What it is | Example bundle |
|
||||
|
|
@ -52,20 +127,15 @@ embeddable. Every screenshot is a real bundle from this repo's build output.
|
|||
> **[documentation/reference-capability-exemplars.md](./documentation/reference-capability-exemplars.md)**
|
||||
> — cite that file rather than remembering.
|
||||
|
||||
|
||||
|
||||
### Capability 5 — nested-SPA / app shell
|
||||
|
||||
Every plugin in one deep-linkable page. Two cap-5 apps ship, and the difference matters when you embed
|
||||
one: **`doxa-maps-spa` is what an end user gets; `plugin-studio` is what a developer works in.**
|
||||
|
||||
*`doxa-maps-spa` — the curated product SPA*, with tabs grouped by audience.
|
||||
|
||||

|
||||
one: **`doxa-maps-spa` (the Research Gallery, [section 1](#1-the-research-gallery--five-capabilities-in-one-plugin))
|
||||
is what an end user gets; `plugin-studio` is what a developer works in.**
|
||||
|
||||
*`plugin-studio` — the developer studio*, grouping every built plugin by capability (5 → 1). It is
|
||||
auto-generated: the build discovers every folder in `plugin-designs/`, so a new plugin appears in the
|
||||
studio without any wiring. This is [section 3](#3-the-plugin-studio).
|
||||
studio without any wiring. This is [section 4](#4-the-plugin-studio).
|
||||
|
||||

|
||||
|
||||
|
|
@ -104,7 +174,7 @@ group on one map.
|
|||
|
||||
---
|
||||
|
||||
## 2. The 5 deployment methods
|
||||
## 3. The 5 deployment methods
|
||||
|
||||
Ordered easiest to hardest. **Status is stated honestly** — "proven" means it has been run and seen, and
|
||||
nothing below is upgraded past what the files support.
|
||||
|
|
@ -119,12 +189,12 @@ nothing below is upgraded past what the files support.
|
|||
|
||||
**What "not shot" means:** the code exists and its contract test passes, but nobody has captured it
|
||||
running on a real device or install. Each is blocked by a missing local toolchain, not by missing code —
|
||||
Android SDK for method 2, a live WordPress for method 4, and a GUI (this box is headless) for method 5.
|
||||
Android SDK for method 2, a live WordPress for method 4, and a GUI for method 5.
|
||||
|
||||
Methods **1 and 3 are the two proven-live paths.** If you need a map on a site today, use one of those.
|
||||
|
||||
Both were **certified cross-origin on 2026-09-24** — the entire capability-5 app, embedded on a
|
||||
foreign host page, every inner plugin riding along (prove-the-whole = prove-the-parts):
|
||||
Both were **certified cross-origin on 2026-09-24** — the entire Research Gallery, embedded on a
|
||||
foreign host page, every inner plugin riding along (prove the whole = prove the parts):
|
||||
|
||||
| Method 1 — iframe on a host site | Method 3 — JS embed on a host site |
|
||||
|----------------------------------|-------------------------------------|
|
||||
|
|
@ -139,8 +209,8 @@ The full per-method command matrix, with every prerequisite, is in
|
|||
|
||||
### The Mapbox token (read this before you file a bug)
|
||||
|
||||
The maps use Mapbox, which needs a `pk.` token. **The token works differently in dev than in the built
|
||||
tree**, and diagnosing one from the other is the most common way to lose an hour.
|
||||
The maps use Mapbox by default, which needs a `pk.` token. **The token works differently in dev than in
|
||||
the built tree**, and diagnosing one from the other is the most common way to lose an hour.
|
||||
|
||||
| Where | How it gets a token |
|
||||
|---|---|
|
||||
|
|
@ -153,8 +223,7 @@ So on a built leaf page you typically need:
|
|||
http://localhost:4173/doxa-maps/demo-map/demo-map/index.html?tk=pk.your_token_here
|
||||
```
|
||||
|
||||
To use your own token in dev, create a `.env` — it is **gitignored** (`.gitignore` lines 21-24), so it
|
||||
never reaches the repo:
|
||||
To use your own token in dev, create a `.env` — it is **gitignored**, so it never reaches the repo:
|
||||
|
||||
```bash
|
||||
printf 'VITE_MAPBOX_TOKEN=%s\n' 'pk.your_token_here' > .env
|
||||
|
|
@ -163,39 +232,16 @@ printf 'VITE_MAPBOX_TOKEN=%s\n' 'pk.your_token_here' > .env
|
|||
**If a basemap is an empty grey void while the pins still draw, that is the token failing** — not a
|
||||
broken app.
|
||||
|
||||
**Why this is genuinely awkward:** the five deployment methods each handle the token differently, and a
|
||||
JAMstack embed has nowhere safe to keep a secret. The answer is not to hide the token but to make hiding
|
||||
it unnecessary: a public `pk.` token is restricted to specific URLs in the Mapbox dashboard. **MapLibre
|
||||
is being pursued to remove the per-embed token requirement entirely — that work is IN PROGRESS, not
|
||||
done** (`maplibre-gl` is a dependency and the page generator has a MapLibre path, but no plugin-design
|
||||
runs on it yet).
|
||||
**Or skip the token entirely.** A public `pk.` token can be restricted to specific URLs in the Mapbox
|
||||
dashboard, but a JAMstack embed still has nowhere to hide one. So the engine is one prop: set
|
||||
`provider: "maplibre"` on an instance (or add `?provider=maplibre` to a leaf page) and the map renders
|
||||
on MapLibre with no Mapbox key. `plugin-designs/doxa-research-map/research-map.instances.json` ships a
|
||||
ready-made MapLibre instance. The trade-off is deliberate: under MapLibre the place-search bar is
|
||||
hidden, because the geocoder would still bill the Mapbox key the instance exists to avoid.
|
||||
|
||||
---
|
||||
|
||||
## The repo at a glance
|
||||
|
||||
```text
|
||||
doxa-maps/
|
||||
├── plugin-designs/ YOUR maps live here — one folder per plugin (the FREE zone).
|
||||
│ ├── doxa-maps-spa/ the capability-5 app (nests everything below)
|
||||
│ ├── dashboard-plus-grid/ capability-4 composite: dashboard + people-groups grid
|
||||
│ ├── my-upg-100-list/ capability-4: UPG-100 lists + poster wall + map
|
||||
│ ├── doxa-simple-map/ capability-2: one root.vue, three preset maps
|
||||
│ └── template-bundle/ copy this to start a new plugin
|
||||
├── library/ the SHARED zone (@map) — reusable, parameterized only.
|
||||
│ │ A change here reaches EVERY bundle: high bar, see
|
||||
│ │ documentation/contributing/.
|
||||
│ ├── components/ map controls, legends, search, poster, detail panels
|
||||
│ ├── composables/ useMapInstance, shadow styles, print-in-place, retry
|
||||
│ └── styles/ colors/ data/ style registry, palettes, lookup tables
|
||||
├── data/ the public datasets the maps ship with
|
||||
├── documentation/ tutorial · how-to · reference · explanation + architecture
|
||||
├── internal/ the build generator + the verification gates
|
||||
└── public/doxa-maps/ the BUILD OUTPUT — the drop-anywhere tree (bundles,
|
||||
leaf pages, manifest.json). What gets served + embedded.
|
||||
```
|
||||
|
||||
## 3. The Plugin Studio
|
||||
## 4. The Plugin Studio
|
||||
|
||||
★ **This is where you work — and the keyword is INTEROPERABILITY.**
|
||||
|
||||
|
|
@ -206,27 +252,22 @@ lowest**, so you can see the whole catalogue instead of hunting through folders.
|
|||
That sorting is the point. Once you can see every plugin side by side, you can tell your agent **which
|
||||
ones to group together** — any number of them — and compose them into **higher-capability plugins**
|
||||
using **props, events and layouts**. Group cap-1 and cap-2 elements and you get a cap-4 nested-element
|
||||
SPA; group those and you get a cap-5 app shell. That is what makes the studio the right place to build
|
||||
map plugins, rather than a gallery you merely look at.
|
||||
|
||||
**Why this matters.** Without a working studio, a developer *sees the fruit but not the deeper
|
||||
strategy* — they get the maps, but they cannot reproduce or extend them. The studio is what turns a set
|
||||
of finished maps into something you can build on.
|
||||
SPA; group those and you get a cap-5 app shell. That is exactly how the Research Gallery was built.
|
||||
|
||||
### Get it running
|
||||
|
||||
Four steps from nothing to every map in one place.
|
||||
Four steps from nothing to every map in one place. You need **git**, **[Bun](https://bun.sh)** and
|
||||
**Node ≥ 18**.
|
||||
|
||||
```bash
|
||||
# 1. Clone (the repo root IS the studio — there is no subfolder to cd into).
|
||||
# This host is PRIVATE: it will prompt for your Forgejo username + token.
|
||||
git clone --branch doxa-maps-plugin-studio https://git.nuxtbase.app/doxa-life/doxa-maps.git
|
||||
# 1. Clone (the repo root IS the studio — there is no subfolder to cd into)
|
||||
git clone https://git.nuxtbase.app/doxa-life/doxa-maps.git
|
||||
cd doxa-maps
|
||||
|
||||
# 2. Install dependencies (installs 206 packages; needs Node >= 18)
|
||||
# 2. Install dependencies
|
||||
bun install --frozen-lockfile
|
||||
|
||||
# 3. Optional — your own Mapbox token for dev (see section 2)
|
||||
# 3. Optional — your own Mapbox token for dev (see section 3)
|
||||
printf 'VITE_MAPBOX_TOKEN=%s\n' 'pk.your_token_here' > .env
|
||||
|
||||
# 4. Open the Plugin Studio
|
||||
|
|
@ -239,16 +280,29 @@ spot. (If port 5173 is busy Vite picks another one and prints it; read the line
|
|||
> **You do not need to build first.** The build output in `public/` is committed, so a fresh clone has
|
||||
> plugins to show the moment the dev server starts.
|
||||
|
||||
> **If step 1 fails with `remote: Unauthorized`,** the clone is not broken — `git.nuxtbase.app` is a
|
||||
> private self-hosted Forgejo and anonymous cloning is refused. Ask for an account, then use your
|
||||
> username plus a Forgejo **access token** (not your password) when git prompts.
|
||||
|
||||
Two things worth knowing:
|
||||
|
||||
- **`/` is the studio; `/__gallery` is the old staging gallery**, kept but demoted to an explicit URL.
|
||||
- **Edits hot-reload.** The studio mounts the real `plugin-studio` custom element through its own
|
||||
`index.js` — the same path the built bundle takes — so what you see is the real element, not a preview.
|
||||
|
||||
### Every command you need
|
||||
|
||||
| Command | What it does |
|
||||
|---|---|
|
||||
| `bun run dev` | The Plugin Studio with hot reload, at http://localhost:5173 |
|
||||
| `bun x vite --port 5299 --strictPort` | A dev server on your own port (see the swarm rules below) |
|
||||
| `BUNDLE=<name> bun x vite build` | Build **one** bundle, without cleaning anyone else's output |
|
||||
| `bun run build` | The full production tree in `public/doxa-maps/` (~4 minutes, takes a lock) |
|
||||
| `bun run serve` | Serve the built tree at http://localhost:4173, the way production does |
|
||||
| `bun run check:settled` | Exit 0 = the built tree is safe to measure or publish right now |
|
||||
| `bun run gates:pre` / `bun run gates:post` | The static and post-build verification gates |
|
||||
| `bun run verify:5-caps` · `bun run verify:interop` | Prove the five capabilities, and cap-4 interoperability, in a real browser |
|
||||
| `bun run shots` | Regenerate the README screenshots |
|
||||
|
||||
The full list — every deployment command with its prerequisites — is in
|
||||
[documentation/README-SECTION-3-cli-commands.md](./documentation/README-SECTION-3-cli-commands.md).
|
||||
|
||||
### Work with your agent
|
||||
|
||||
The studio's companion is your coding agent. The loop:
|
||||
|
|
@ -270,62 +324,10 @@ Two shared layouts do the composing, both in `library/layouts/`:
|
|||
- **`StackLayout`** — the opposite: elements stacked top-to-bottom at natural height, so the page
|
||||
scrolls through them.
|
||||
|
||||
### Working fast: use the dev server, not the build
|
||||
|
||||
A full `bun run build` rebuilds every bundle and takes minutes. **You do not need it to see a
|
||||
change.** The dev server hot-reloads:
|
||||
|
||||
```bash
|
||||
bun run dev # http://localhost:5173
|
||||
bun x vite --port 5299 --strictPort # any other port, when 5173 is taken
|
||||
```
|
||||
|
||||
★ **If more than one of you is working in this repo at once, EVERY dev server must name its own
|
||||
port, and must pass `--strictPort`.** Vite's default behaviour on a busy port is to silently move to
|
||||
the next one — 5173 becomes 5174 — and five gates (`check-facet-changes-map`,
|
||||
`plugin-gallery-smoke`, `smoke-studio`, `generate-gallery`, `check-scrim-covers-viewport`) have
|
||||
`5173` written into them. A second server without `--strictPort` therefore leaves those gates
|
||||
talking to somebody else's server, or to none, and reporting a confident wrong answer with no error.
|
||||
With `--strictPort` the duplicate exits non-zero instead of drifting — verified: port taken → exit 1.
|
||||
|
||||
Reach for the full build only when you need the deployable tree, an SRI-consistent leaf page, or a
|
||||
manifest. To check one bundle's compiled output without touching anyone else's:
|
||||
|
||||
```bash
|
||||
BUNDLE=<name> bun x vite build # this bundle only; does NOT clean the tree
|
||||
```
|
||||
|
||||
### Everything must reach Forgejo
|
||||
|
||||
**A local commit is invisible.** The Driver runs a laptop watcher against
|
||||
`git.nuxtbase.app/doxa-life/doxa-maps` and sees changes in the browser from THAT repo — so work that
|
||||
stops at `git commit` has not been delivered, no matter how well it was verified locally.
|
||||
|
||||
Push every increment:
|
||||
|
||||
```bash
|
||||
TOKEN="$(pass show vivarium/forgejo-certified-sandbox | head -1)"
|
||||
git -c http.postBuffer=524288000 \
|
||||
push "https://oauth2:${TOKEN}@git.nuxtbase.app/doxa-life/doxa-maps.git" HEAD:doxa-maps-plugin-studio \
|
||||
2>&1 | sed "s/${TOKEN}/[REDACTED]/g"
|
||||
unset TOKEN
|
||||
```
|
||||
|
||||
Never write the credential into `.git/config` (see the clone note above), always filter output
|
||||
through `sed`, and prefer many small pushed commits over one large local one.
|
||||
|
||||
★ After pushing to an INLINE url, `git log @{u}..` still reports the commits as unpushed, because git
|
||||
only moves a remote-tracking ref when it pushes to a NAMED remote. Sync it so the next reader is not
|
||||
misled:
|
||||
|
||||
```bash
|
||||
git fetch "https://oauth2:${TOKEN}@git.nuxtbase.app/doxa-life/doxa-maps.git" \
|
||||
'refs/heads/doxa-maps-plugin-studio:refs/remotes/origin/doxa-maps-plugin-studio'
|
||||
```
|
||||
|
||||
### The production build
|
||||
|
||||
The dev server is for working. When you want the deployable tree:
|
||||
The dev server is for working; **you do not need a build to see a change.** When you want the
|
||||
deployable tree:
|
||||
|
||||
```bash
|
||||
bun run build # ~4 minutes; writes public/doxa-maps/
|
||||
|
|
@ -335,85 +337,131 @@ bun run serve # serve the BUILT tree at http://localhost:4173
|
|||
`bun run build` takes a **lock**: if another build is already running it fails fast rather than
|
||||
corrupting the shared output. Set `BUILD_LOCK_WAIT=600` to queue behind it instead.
|
||||
|
||||
### Before you MEASURE the built tree — `bun run check:settled`
|
||||
|
||||
```bash
|
||||
bun run check:settled # exit 0 = safe to measure/publish; exit 1 = do not
|
||||
```
|
||||
|
||||
**The lock protects a second WRITER. It does not protect a READER.** Every build begins with
|
||||
`bun run clean`, which `rm -rf`s `public/doxa-maps/`, so while a build runs the tree is transiently
|
||||
near-empty — and anything reading it then gets a confident, wrong answer: "that bundle is missing",
|
||||
"the dashboard tab does not exist", "461 files were deleted". Those are not hypotheticals; each has
|
||||
happened here, and one nearly produced a bug report against a correct change.
|
||||
|
||||
So run this gate before any browser check, any publish, and any claim about what the tree contains.
|
||||
It reports `builders`, `waiters`, `churn(45s)`, `dirs`, `leaves` and `quietFor`, and **grade it by
|
||||
exit code** — like every gate here, it prints teaching prose after its verdict, so `| tail -1` will
|
||||
mislead you.
|
||||
|
||||
★ **Do not substitute a `pgrep` check for this.** A process list samples an INSTANT; the lock knows
|
||||
the QUEUE. Between one queued build finishing and the next acquiring the lock there is a real window
|
||||
where zero build processes exist and the tree is nonetheless about to be wiped — and a long quiet
|
||||
period makes that window look *more* convincing, not less, because the queue is idle precisely
|
||||
because it is between runs. Measured 2026-09-23: `pgrep` reported 0 builders while the gate reported
|
||||
`builders=1 waiters=1`, and `ps` confirmed a build had started 17 seconds earlier.
|
||||
|
||||
If you are one of several agents working this repo at once, the stronger rule is simply: **do not run
|
||||
`bun run build` at all.** Edit source, and let one owner run the single authoritative build. To
|
||||
sanity-check one bundle without touching anyone else's output, use
|
||||
`BUNDLE=<name> bun x vite build` — it does not clean the whole tree.
|
||||
**The lock protects a second WRITER. It does not protect a READER.** Every build begins by deleting
|
||||
`public/doxa-maps/`, so while a build runs the tree is transiently near-empty — and anything reading
|
||||
it then gets a confident, wrong answer ("that bundle is missing"). Run `bun run check:settled` before
|
||||
any browser check, any publish, and any claim about what the tree contains, and **grade it by exit
|
||||
code**: like every gate here, it prints explanatory prose after its verdict, so `| tail -1` will
|
||||
mislead you. Do not substitute a `pgrep` check — a process list samples an instant, while the lock
|
||||
knows the queue.
|
||||
|
||||
---
|
||||
|
||||
## The delegation model — one build, many agents, one main
|
||||
## 5. Build it with an agent swarm
|
||||
|
||||
This repo is designed for **many builders working in parallel without stepping on each
|
||||
other**. The model is a set of concentric rings:
|
||||
This repo is designed for **many builders working in parallel without stepping on each other** — and
|
||||
the builders can be coding agents on your own laptop. It is **trunk-based development with a merge
|
||||
queue**, the same model as GitHub's merge queue, GitLab's merge trains and Rust's bors, arranged as
|
||||
concentric rings:
|
||||
|
||||

|
||||

|
||||
|
||||
- **`main`** — the one shared branch. Everything converges here.
|
||||
- **Capture ring** — before pushing to `main`, a builder must *capture* it: **one builder at
|
||||
a time, first-come-first-serve.** Whoever holds it **resolves all merge conflicts**, lands
|
||||
their work, then releases. The next builder waits and retries. No two pushes race.
|
||||
- **Plugin Studio** — one repo, every plugin visible in one place, so each builder can see
|
||||
what already exists and reuse it.
|
||||
- **Plugins** — each is built in the shared bundler, so its shared, parameterized components
|
||||
become available to every other plugin (private components stay inside each plugin's own
|
||||
folder).
|
||||
- **Agents** — **one builder per plugin.** Each owns their plugin end to end. A cluster of
|
||||
experimental plugins is owned by a single roaming builder.
|
||||
- **Shepherd** — one builder keeps everyone honoring the model: plugins built in the studio,
|
||||
shared components truly shared, and every push serialized through the capture ring.
|
||||
- **`main` — the trunk.** The one shared branch. Everything converges here, and it is always green.
|
||||
- **Merge queue.** Changes land **one at a time, in order.** Each change is rebased onto the latest
|
||||
`main` — its author resolves any conflicts — and lands only if the gates pass. A change that fails
|
||||
leaves the queue; the next one goes. Nothing writes to `main` except the queue.
|
||||
- **Plugin Studio.** One repo and one shared `library/`, with every plugin visible in one place, so
|
||||
each builder can see what already exists and reuse it instead of rebuilding it.
|
||||
- **Agents.** **One agent per plugin, each in its own git worktree.** Each owns its plugin end to end;
|
||||
its private components stay inside its own `plugin-designs/` folder.
|
||||
- **Maintainer.** One reviewer — human or agent — keeps everyone honoring the model: plugins built in
|
||||
the studio, shared components genuinely shared, and every change going through the queue.
|
||||
|
||||
The effect is cooperative: each builder races to land the most working features, but the only
|
||||
way to score is to pass through the capture ring one at a time and **leave `main` green.**
|
||||
The effect is cooperative: every agent races to land working features, but the only way to land is
|
||||
through the queue, one change at a time, **leaving `main` green.**
|
||||
|
||||
The diagram's source is [`documentation/readme-images/src/delegation-model.svg`](./documentation/readme-images/src/delegation-model.svg).
|
||||
|
||||
### Spin one up on your laptop
|
||||
|
||||
**1. One worktree per agent**, as sibling folders, so no two agents share a working directory:
|
||||
|
||||
```bash
|
||||
git worktree add -b regions/work ../dx-maps-regions main
|
||||
git worktree add -b upg100/work ../dx-maps-upg100 main
|
||||
git worktree add -b maintainer/work ../dx-maps-maintainer main
|
||||
git worktree list
|
||||
```
|
||||
|
||||
**2. One agent per worktree, each owning one plugin.** Start your coding agent in each folder and give
|
||||
it a single `plugin-designs/<name>/` folder to own. The maintainer gets no plugin; it reviews what
|
||||
lands and pushes back when shared code is duplicated or a plugin reaches into another's folder.
|
||||
|
||||
**3. One dev server per agent, each on its own port, always with `--strictPort`:**
|
||||
|
||||
```bash
|
||||
bun install --frozen-lockfile # once per worktree
|
||||
bun x vite --port 5301 --strictPort # regions agent
|
||||
bun x vite --port 5302 --strictPort # upg100 agent
|
||||
```
|
||||
|
||||
Without `--strictPort`, Vite silently moves a busy port to the next one — and several gates have
|
||||
`5173` written into them, so a drifted server leaves those gates reporting a confident wrong answer
|
||||
about somebody else's page. With `--strictPort` the duplicate exits non-zero instead.
|
||||
|
||||
**4. One owner for the full build.** Agents edit source and check their own bundle with
|
||||
`BUNDLE=<name> bun x vite build`. Only one agent runs `bun run build`, because every full build wipes
|
||||
the shared output tree first.
|
||||
|
||||
**5. Land through the merge queue.** On one machine, a file lock is a complete merge queue — agents
|
||||
wait their turn on it (`flock` ships with Linux; on macOS, `brew install flock`):
|
||||
|
||||
```bash
|
||||
flock /tmp/dx-maps-main.lock sh -c '
|
||||
git fetch origin &&
|
||||
git rebase origin/main &&
|
||||
bun run gates:pre &&
|
||||
git push origin HEAD:main
|
||||
'
|
||||
```
|
||||
|
||||
The lock guarantees one landing at a time; the rebase makes the lander resolve every conflict; the
|
||||
gates keep `main` green. When several machines share one remote, use the hosting platform's merge
|
||||
queue instead (GitHub merge queue, GitLab merge trains), or protect `main` so only one builder at a
|
||||
time may push.
|
||||
|
||||
Agents joining this repo should also read [AGENT-BRIEF.md](./AGENT-BRIEF.md).
|
||||
|
||||
---
|
||||
|
||||
## Repository layout
|
||||
|
||||
Two directories matter to a plugin author:
|
||||
|
||||
```text
|
||||
doxa-maps/
|
||||
├── plugin-designs/ YOUR maps live here — one folder per plugin (the FREE zone).
|
||||
│ ├── doxa-maps-spa/ the Research Gallery, capability 5 (nests the production maps)
|
||||
│ ├── plugin-studio/ the developer studio, capability 5 (nests every plugin)
|
||||
│ ├── dashboard-plus-grid/ capability-4 composite: research dashboard + people-groups grid
|
||||
│ ├── my-upg-100-list/ capability-4: UPG-100 list + poster wall
|
||||
│ ├── doxa-simple-map/ capability-2: one root.vue, three preset maps
|
||||
│ └── template-bundle/ copy this to start a new plugin
|
||||
├── library/ the SHARED zone (@map) — reusable, parameterized only.
|
||||
│ ├── layouts/ VerticalTabsLayout, StackLayout — how composites compose
|
||||
│ ├── components/ map controls, legends, search, poster, detail panels
|
||||
│ ├── composables/ map instance, shadow styles, print-in-place, retry
|
||||
│ └── styles/ colors/ data/ style registry, palettes, lookup tables
|
||||
├── data/ the public datasets the maps ship with
|
||||
├── deployment/ Capacitor (mobile), WordPress plugin, Tauri (desktop)
|
||||
├── documentation/ tutorial · how-to · reference · explanation + architecture
|
||||
├── internal/ the build generator + the verification gates
|
||||
└── public/doxa-maps/ the BUILD OUTPUT — the drop-anywhere tree (bundles, leaf pages,
|
||||
manifest.json). What gets served and embedded.
|
||||
```
|
||||
plugin-designs/ ← YOUR FREE ZONE. One folder per plugin. Copy template-bundle/ to start.
|
||||
library/ ← the SHARED toolkit (layouts, components, composables, colors, stores, i18n).
|
||||
```
|
||||
|
||||
Everything else is supporting cast: `internal/` (build + gates), `public/` (committed build output),
|
||||
`deployment/` (Capacitor, WordPress, Tauri), `documentation/`.
|
||||
|
||||
**`plugin-designs/` is a free zone.** Your plugin is its own application in its own folder, building to
|
||||
its own output. Nothing you do there can break another author's map.
|
||||
|
||||
**`library/` is a HIGH BAR.** It is multi-tenant — reused by many maps and authors — so every edit is a
|
||||
breaking-change risk to everyone. Only contribute here when a component is genuinely reusable *and*
|
||||
parameterized, so other maps can use it. Never widen or casually add. Keep map-specific,
|
||||
non-parameterized code in your own plugin-design.
|
||||
parameterized, so other maps can use it. Keep map-specific, non-parameterized code in your own
|
||||
plugin-design. See [documentation/contributing/](./documentation/contributing/).
|
||||
|
||||
---
|
||||
|
||||
## More docs
|
||||
|
||||
Start at **[documentation/README.md](./documentation/README.md)** — the index of everything below.
|
||||
|
||||
| Read this | When |
|
||||
|---|---|
|
||||
| **[documentation/develop-a-plugin-and-see-it-live.md](./documentation/develop-a-plugin-and-see-it-live.md)** | ★ the golden path — zero to a plugin live, in 6 steps |
|
||||
|
|
@ -429,4 +477,5 @@ non-parameterized code in your own plugin-design.
|
|||
|
||||
> **Bottom line: maps should be open source, run everywhere, and multiply.** This bundler makes each map
|
||||
> a self-contained file that embeds on your site, your partners' sites, WordPress, and mobile — from one
|
||||
> build. The Plugin Studio is where you compose them into something bigger.
|
||||
> build. The Plugin Studio is where you compose them into something bigger; the Research Gallery is
|
||||
> what that looks like when you do.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# Contributing to doxa-maps
|
||||
|
||||
Everything a contributor needs, short. Recipes live in [documentation/how-to.md](./documentation/how-to.md),
|
||||
lookups in [documentation/reference.md](./documentation/reference.md), depth in
|
||||
[documentation/architecture/](./documentation/architecture/).
|
||||
Everything a contributor needs, short. Recipes live in [documentation/how-to.md](./how-to.md),
|
||||
lookups in [documentation/reference.md](./reference.md), depth in
|
||||
[documentation/architecture/](./architecture/).
|
||||
|
||||
## The mental model — three zones
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ genuinely needs it. One fix in `library/` reaches every map on the next build.
|
|||
|
||||
Pick A when the maps differ by *configuration*, B when they differ by *layout/behavior*.
|
||||
Full walkthrough + the migration story:
|
||||
[documentation/architecture/patterns-and-provenance.md](./documentation/architecture/patterns-and-provenance.md).
|
||||
[documentation/architecture/patterns-and-provenance.md](./architecture/patterns-and-provenance.md).
|
||||
|
||||
## The two build steps (never confuse them)
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ into `public/` on save) · terminal 2 serves the host site / `public/`.
|
|||
|
||||
`public/` is the committed web-root — serve it from anywhere and every map is embeddable from
|
||||
it (script tag or the generated per-map page). Same-origin vs external-CDN modes, `paths`,
|
||||
`embedBaseUrl`: [documentation/how-to.md → "Where do the maps get served from?"](./documentation/how-to.md).
|
||||
`embedBaseUrl`: [documentation/how-to.md → "Where do the maps get served from?"](./how-to.md).
|
||||
|
||||
## Ground rules
|
||||
|
||||
|
|
|
|||
|
|
@ -274,5 +274,5 @@ this box, so `"all"` would fail two of three formats at the very end of a ~6-min
|
|||
|
||||
Still **not "proven"** — say *built, not shot*. The GUI launch is unverified because this VPS is
|
||||
headless: the binary panics in `tao` with "Failed to initialize GTK" (no display, not a defect) and
|
||||
no Xvfb is installed. See [`deployment/src-tauri/README.md`](../src-tauri/README.md) for placement rules, the
|
||||
no Xvfb is installed. See [`deployment/src-tauri/README.md`](../deployment/src-tauri/README.md) for placement rules, the
|
||||
token/CORS caveat and the full artifact table.
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ the goal is never one trained person, but trainers who train trainers. This READ
|
|||
pass it on with the same button — **[the Playbook](#the-playbook--start-a-prayer-movement-with-this-map)**.
|
||||
|
||||
Everything below is for the people who build the maps. If you only want to *run the campaign*, jump to the
|
||||
Playbook; the full walkthrough is [documentation/tutorial.md](./documentation/tutorial.md).
|
||||
Playbook; the full walkthrough is [documentation/tutorial.md](./tutorial.md).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -275,11 +275,11 @@ Your map data has to come from somewhere. Common sources:
|
|||
|
||||
| Read this | When |
|
||||
|---|---|
|
||||
| **[documentation/why.md](./documentation/why.md)** | 2 min — the problem, the idea, one diagram |
|
||||
| **[documentation/tutorial.md](./documentation/tutorial.md)** | build your first map, step by step (great to hand to your agent) |
|
||||
| **[documentation/how-to.md](./documentation/how-to.md)** | task recipes (embed, data, mobile) |
|
||||
| **[documentation/why.md](./why.md)** | 2 min — the problem, the idea, one diagram |
|
||||
| **[documentation/tutorial.md](./tutorial.md)** | build your first map, step by step (great to hand to your agent) |
|
||||
| **[documentation/how-to.md](./how-to.md)** | task recipes (embed, data, mobile) |
|
||||
| **[library/layouts/README.md](../library/layouts/README.md)** | the SPA composition machinery (many maps → one app) |
|
||||
| **[documentation/architecture/](./documentation/architecture/)** | the deep end — embeddability, app-wrappers, file map |
|
||||
| **[documentation/architecture/](./architecture/)** | the deep end — embeddability, app-wrappers, file map |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -15,5 +15,13 @@
|
|||
- **[architecture/](./architecture/)** — the deep end: full contracts, the per-file catalog.
|
||||
- **[README-embed-example.md](./README-embed-example.md)** — the drop-in README used when this bundler is embedded inside a host repo.
|
||||
|
||||
Working on the repo:
|
||||
|
||||
- **[develop-a-plugin-and-see-it-live.md](./develop-a-plugin-and-see-it-live.md)** — ★ the golden path: zero to a plugin live, in 6 steps.
|
||||
- **[README-SECTION-3-cli-commands.md](./README-SECTION-3-cli-commands.md)** — every command, per deployment method, with prerequisites.
|
||||
- **[reference-capability-exemplars.md](./reference-capability-exemplars.md)** — which bundle is at which capability tier (generated).
|
||||
- **[reference-library-index.md](./reference-library-index.md)** — the reusable `library/` pieces you can import (generated).
|
||||
- **[CONTRIBUTING.md](./CONTRIBUTING.md)** and **[contributing/](./contributing/)** — how to contribute, and the bar for `library/`.
|
||||
|
||||
Nothing on the simple path is longer than a few minutes; everything complex lives in
|
||||
`architecture/`, linked from where you'd need it.
|
||||
|
|
|
|||
|
|
@ -14,17 +14,17 @@ being duplicated.
|
|||
"**Local by default, promote deliberately.**" When in doubt, keep your change local to your
|
||||
own map; something graduates into the shared library only once a *second* map genuinely
|
||||
needs it. Full reasoning — the promotion / orphan-audit rule — is in
|
||||
[`../documentation/explanation.md`](../documentation/explanation.md).
|
||||
[`../documentation/explanation.md`](../explanation.md).
|
||||
|
||||
## Start here
|
||||
|
||||
| You want to… | Read |
|
||||
|---|---|
|
||||
| Understand the whole picture (one library, many bundles, no rebuild) | [`../README.md`](../README.md) · [`../documentation/explanation.md`](../documentation/explanation.md) |
|
||||
| Learn step-by-step with no jargon (non-coders welcome) | [`../documentation/tutorial.md`](../documentation/tutorial.md) |
|
||||
| Do a task (add a map, embed, change colors, pick a CDN) | [`../documentation/how-to.md`](../documentation/how-to.md) |
|
||||
| Look something up (file map, config fields, library index) | [`../documentation/reference.md`](../documentation/reference.md) |
|
||||
| Understand build-vs-dev + partner-site embedding | [`../documentation/CONTRIBUTING.md`](../documentation/CONTRIBUTING.md) |
|
||||
| Understand the whole picture (one library, many bundles, no rebuild) | [`../README.md`](../README.md) · [`../documentation/explanation.md`](../explanation.md) |
|
||||
| Learn step-by-step with no jargon (non-coders welcome) | [`../documentation/tutorial.md`](../tutorial.md) |
|
||||
| Do a task (add a map, embed, change colors, pick a CDN) | [`../documentation/how-to.md`](../how-to.md) |
|
||||
| Look something up (file map, config fields, library index) | [`../documentation/reference.md`](../reference.md) |
|
||||
| Understand build-vs-dev + partner-site embedding | [`../documentation/CONTRIBUTING.md`](../CONTRIBUTING.md) |
|
||||
| Edit or add colors (the two places a color can live) | [`COLORS.md`](./COLORS.md) |
|
||||
|
||||
## Colors live here
|
||||
|
|
@ -33,4 +33,4 @@ needs it. Full reasoning — the promotion / orphan-audit rule — is in
|
|||
can live** — the shared `library/colors/` set vs your bundle's private
|
||||
`plugin-designs/<bundle>/src/colors/` — and how the filename decides override-vs-add. The
|
||||
strategy **contract** (the required `export default` shape) is in
|
||||
[`../library/colors/README.md`](../library/colors/README.md).
|
||||
[`../library/colors/README.md`](../../library/colors/README.md).
|
||||
|
|
|
|||
|
|
@ -110,13 +110,13 @@ MAPBOX_TOKEN=$MAPBOX_TOKEN node internal/embed-smoke.mjs --grep <your-plugin>
|
|||
> **Do not fetch the token from `https://doxa.life/api/maps/token` here — measured 2026-09-23, it does
|
||||
> not work for this.** That endpoint serves a token URL-restricted to `doxa.life`, so a tile request
|
||||
> without that Referer returns **403** and every basemap in your smoke test renders blank while the pins
|
||||
> draw fine. That is the exact symptom the Driver reported as *"it's loading the pins, but it's not
|
||||
> draw fine. That is the exact symptom first reported as *"it's loading the pins, but it's not
|
||||
> loading the actual underlying map"*, and it is why `tokenUrl` was emptied in the build config on
|
||||
> 2026-09-22. This doc used to hand you that command, so an inheritor's first run reproduced the bug.
|
||||
>
|
||||
> The `token` field in `vite.doxa-maps-build-config.json` is the one that works: measured, the config
|
||||
> token returns **200** for a tile request with no Referer while the endpoint token returns **403**. It
|
||||
> is a `pk.` public key, baked deliberately (Driver ruling 2026-09-21) so a map works on any host —
|
||||
> is a `pk.` public key, baked deliberately (a project decision, 2026-09-21) so a map works on any host —
|
||||
> protected by URL restrictions rather than secrecy. `check:public-hygiene` allows `pk.` and fails the
|
||||
> build on `sk.`/`tk.`, so this is safe to read and never a secret to guard.
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 976 KiB After Width: | Height: | Size: 1,015 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 175 KiB |
68
documentation/readme-images/src/delegation-model.svg
Normal file
68
documentation/readme-images/src/delegation-model.svg
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1010" viewBox="0 0 1200 1010" font-family="Inter, 'Helvetica Neue', Arial, sans-serif">
|
||||
<defs>
|
||||
<marker id="ar" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0,0 L10,5 L0,10 z" fill="#64748b"/></marker>
|
||||
<marker id="arM" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="8" markerHeight="8" orient="auto"><path d="M0,0 L10,5 L0,10 z" fill="#b91c1c"/></marker>
|
||||
<radialGradient id="gM" cx="45%" cy="40%"><stop offset="0" stop-color="#16a34a"/><stop offset="1" stop-color="#166534"/></radialGradient>
|
||||
</defs>
|
||||
<rect width="1200" height="1010" fill="#ffffff"/>
|
||||
<circle cx="600" cy="475" r="450" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2.5"/>
|
||||
<circle cx="600" cy="475" r="365" fill="#eff6ff" stroke="#2563eb" stroke-width="2.5"/>
|
||||
<circle cx="600" cy="475" r="262" fill="#fffbeb" stroke="#d97706" stroke-width="2.5"/>
|
||||
<circle cx="600" cy="475" r="168" fill="#fef2f2" stroke="#dc2626" stroke-width="2.5" stroke-dasharray="8 6"/>
|
||||
<rect x="578.0" y="297.0" width="44" height="20" fill="#fef2f2"/>
|
||||
<line x1="578.0" y1="291.0" x2="578.0" y2="323.0" stroke="#dc2626" stroke-width="5" stroke-linecap="round"/>
|
||||
<line x1="622.0" y1="291.0" x2="622.0" y2="323.0" stroke="#dc2626" stroke-width="5" stroke-linecap="round"/>
|
||||
<text x="600.0" y="283.0" text-anchor="middle" font-size="13" font-weight="700" fill="#b91c1c">ONE GATE</text>
|
||||
<line x1="600.0" y1="319.0" x2="600" y2="389" stroke="#b91c1c" stroke-width="3" marker-end="url(#arM)"/>
|
||||
<text x="610" y="355.0" font-size="12" font-weight="700" fill="#b91c1c">merge</text>
|
||||
<rect x="515.7941844763895" y="305.1584883484701" width="48" height="26" rx="13" fill="#ffffff" stroke="#dc2626" stroke-width="2"/>
|
||||
<text x="539.7941844763895" y="323.1584883484701" text-anchor="middle" font-size="13" font-weight="700" fill="#b91c1c">#1</text>
|
||||
<rect x="465.7820831295948" y="335.2087905225743" width="48" height="26" rx="13" fill="#ffffff" stroke="#dc2626" stroke-width="2"/>
|
||||
<text x="489.7820831295948" y="353.2087905225743" text-anchor="middle" font-size="13" font-weight="700" fill="#b91c1c">#2</text>
|
||||
<rect x="429.0638892005815" y="380.55198379861537" width="48" height="26" rx="13" fill="#ffffff" stroke="#dc2626" stroke-width="2"/>
|
||||
<text x="453.0638892005815" y="398.55198379861537" text-anchor="middle" font-size="13" font-weight="700" fill="#b91c1c">#3</text>
|
||||
<text x="431.7820831295948" y="328.2087905225743" text-anchor="middle" font-size="12" fill="#b91c1c" font-style="italic">waiting in line</text>
|
||||
<text x="600" y="581" text-anchor="middle" font-size="16" font-weight="800" fill="#b91c1c">MERGE QUEUE</text>
|
||||
<text x="600" y="599" text-anchor="middle" font-size="12" fill="#7f1d1d">one change at a time · rebased on main</text>
|
||||
<text x="600" y="614" text-anchor="middle" font-size="12" fill="#7f1d1d">lands only if the gates pass</text>
|
||||
<circle cx="600" cy="475" r="82" fill="url(#gM)" stroke="#14532d" stroke-width="2"/>
|
||||
<text x="600" y="473" text-anchor="middle" font-size="30" font-weight="800" fill="#ffffff">main</text>
|
||||
<text x="600" y="497" text-anchor="middle" font-size="13" fill="#dcfce7">trunk · always green</text>
|
||||
<rect x="405.70000000000005" y="683.0" width="388.59999999999997" height="30" rx="15" fill="#ffffff" stroke="#d97706" stroke-width="1.5"/>
|
||||
<text x="600" y="703.0" text-anchor="middle" font-size="14" font-weight="700" fill="#92400e">PLUGIN STUDIO · one repo, shared library/</text>
|
||||
<rect x="371.3" y="777.5" width="457.4" height="30" rx="15" fill="#ffffff" stroke="#2563eb" stroke-width="1.5"/>
|
||||
<text x="600" y="797.5" text-anchor="middle" font-size="14" font-weight="700" fill="#1e40af">AGENTS · one per plugin, each in its own worktree</text>
|
||||
<rect x="336.9" y="867.5" width="526.2" height="30" rx="15" fill="#ffffff" stroke="#7c3aed" stroke-width="1.5"/>
|
||||
<text x="600" y="887.5" text-anchor="middle" font-size="14" font-weight="700" fill="#5b21b6">MAINTAINER · reviews what lands, keeps shared code shared</text>
|
||||
<circle cx="834.6174775355869" cy="195.393778261573" r="58" fill="#ffffff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="834.6174775355869" y="189.393778261573" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">Research</text>
|
||||
<text x="834.6174775355869" y="206.393778261573" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">Gallery</text>
|
||||
<text x="834.6174775355869" y="224.393778261573" text-anchor="middle" font-size="10.5" fill="#64748b">agent + worktree</text>
|
||||
<line x1="796.0502209543945" y1="241.3564448487117" x2="711.8450440854579" y2="341.7082668972978" stroke="#64748b" stroke-width="2" marker-end="url(#ar)"/>
|
||||
<circle cx="965.0" cy="475.0" r="58" fill="#ffffff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="965.0" y="469.0" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">Big</text>
|
||||
<text x="965.0" y="486.0" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">Picture</text>
|
||||
<text x="965.0" y="504.0" text-anchor="middle" font-size="10.5" fill="#64748b">agent + worktree</text>
|
||||
<line x1="905.0" y1="475.0" x2="774.0" y2="475.0" stroke="#64748b" stroke-width="2" marker-end="url(#ar)"/>
|
||||
<circle cx="858.0939751330899" cy="733.0939751330898" r="58" fill="#ffffff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="858.0939751330899" y="736.0939751330898" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">Regions</text>
|
||||
<text x="858.0939751330899" y="762.0939751330898" text-anchor="middle" font-size="10.5" fill="#64748b">agent + worktree</text>
|
||||
<line x1="815.667568261897" y1="690.667568261897" x2="723.0365799264592" y2="598.0365799264592" stroke="#64748b" stroke-width="2" marker-end="url(#ar)"/>
|
||||
<circle cx="341.9060248669102" cy="733.0939751330899" r="58" fill="#ffffff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="341.9060248669102" y="727.0939751330899" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">UPG-100</text>
|
||||
<text x="341.9060248669102" y="744.0939751330899" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">List</text>
|
||||
<text x="341.9060248669102" y="762.0939751330899" text-anchor="middle" font-size="10.5" fill="#64748b">agent + worktree</text>
|
||||
<line x1="384.332431738103" y1="690.667568261897" x2="476.96342007354076" y2="598.0365799264592" stroke="#64748b" stroke-width="2" marker-end="url(#ar)"/>
|
||||
<circle cx="235.0" cy="475.00000000000006" r="58" fill="#ffffff" stroke="#2563eb" stroke-width="2"/>
|
||||
<text x="235.0" y="469.00000000000006" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">Gospel</text>
|
||||
<text x="235.0" y="486.00000000000006" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">Resources</text>
|
||||
<text x="235.0" y="504.00000000000006" text-anchor="middle" font-size="10.5" fill="#64748b">agent + worktree</text>
|
||||
<line x1="295.0" y1="475.00000000000006" x2="426.0" y2="475.0" stroke="#64748b" stroke-width="2" marker-end="url(#ar)"/>
|
||||
<circle cx="320.39377826157306" cy="240.38252246441309" r="58" fill="#ffffff" stroke="#2563eb" stroke-width="2" stroke-dasharray="5 4"/>
|
||||
<text x="320.39377826157306" y="234.38252246441309" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">your next</text>
|
||||
<text x="320.39377826157306" y="251.38252246441309" text-anchor="middle" font-size="14" font-weight="700" fill="#1e3a8a">plugin</text>
|
||||
<text x="320.39377826157306" y="269.3825224644131" text-anchor="middle" font-size="10.5" fill="#64748b">agent + worktree</text>
|
||||
<text x="600" y="25" dy="-0" />
|
||||
<text x="600" y="968" text-anchor="middle" font-size="18" font-weight="800" fill="#0f172a">Many agents build in parallel. Changes land one at a time. main stays green.</text>
|
||||
<text x="600" y="992" text-anchor="middle" font-size="13" fill="#475569">Each arrow is a change entering the merge queue; only the queue writes to main.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.7 KiB |
|
|
@ -344,7 +344,7 @@ export MAPBOX_TOKEN=$(python3 -c "import json;print(json.load(open('vite.doxa-ma
|
|||
> diagnosed the blank basemap and then kept prescribing its cause.
|
||||
>
|
||||
> The `token` field in `vite.doxa-maps-build-config.json` returns **200** on the identical request
|
||||
> from any origin. It is a `pk.` public key baked deliberately (Driver ruling 2026-09-21), protected
|
||||
> from any origin. It is a `pk.` public key baked deliberately (a project decision, 2026-09-21), protected
|
||||
> by URL restrictions rather than secrecy; `check:public-hygiene` allows `pk.` and fails the build on
|
||||
> `sk.`/`tk.`. So the SHELL COMMANDS above — the ones that curl a token and hand it to a gate as
|
||||
> `MAPBOX_TOKEN` — now work where they previously fetched a token that 403s.
|
||||
|
|
@ -514,7 +514,7 @@ Country/region polygons are **not** a `sources.json` connection — they render
|
|||
|
||||
**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).
|
||||
two homes in depth: [../contributing/COLORS.md](./contributing/COLORS.md).
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ const browser = await chromium.launch({ args: [
|
|||
|
||||
// ★ A SHOT THAT FAILS MUST NOT SILENTLY PRODUCE A README IMAGE. Collected and asserted at the end.
|
||||
const failures = []
|
||||
const ONLY = process.env.ONLY ? process.env.ONLY.split(',') : null
|
||||
|
||||
// mapbox canvas selector to wait on (leaf pages mount a custom element with a canvas)
|
||||
const CANVAS = 'canvas'
|
||||
|
|
@ -114,6 +115,8 @@ async function shot(name, path, {
|
|||
// generalised: the predicate is "does this surface use iframes", not "did this shot fail".
|
||||
mapboxReferer = true,
|
||||
} = {}) {
|
||||
// ONLY=<name>[,<name>] retakes just those shots, leaving every other image untouched.
|
||||
if (ONLY && !ONLY.includes(name)) return
|
||||
const ctx = await browser.newContext({ viewport: vp, deviceScaleFactor: dsf })
|
||||
// ★ SEND THE REFERER THE TOKEN IS SCOPED TO, or every map ships a BLANK BASEMAP.
|
||||
//
|
||||
|
|
|
|||
Loading…
Reference in a new issue