Merge branch 'dx-maps/readme-pilot' into HEAD

This commit is contained in:
Doxa Maps 2026-10-02 05:49:53 +00:00
commit bb56908600
3 changed files with 72 additions and 44 deletions

View file

@ -70,7 +70,7 @@ capabilities, three outputs, one shared library, one build.
| **One app nests every map** — and each stays embeddable on its own | [capability 5](#capability-5--app-shell-the-research-gallery) · `bun run check:composite-reg` |
| **5 capabilities, every one shippable in every output** | `bun run verify:5-caps` |
| **Shared library, private plugins** — reuse without breaking each other | `bun run check:library-index` |
| **One owner per plugin, a second reader for every change, one landing at a time** | [agent swarm](#build-it-with-an-agent-swarm) |
| **One owner per plugin, a second reader for every change, one merge at a time** | [agent swarm](#build-it-with-an-agent-swarm) |
## For vibe coders: build, open the Studio, embed a map
@ -695,50 +695,63 @@ your own page, set `"locale"` in its `profile-config`. Arabic reads right to lef
## Build it with an agent swarm
**Many AI agents can build many plugins at once without breaking each other's work.** Every plugin
has one agent that owns it, and changes are added to `development`, the shared working version, one
at a time. `main` is the production version: it moves only when you publish a release.
has one agent that owns it, and the work moves through three kinds of branch:
- **The pilot branch (`development`)** is the shared working version. A development server with hot
reload follows it, so each change that lands can be seen there within about 15 seconds.
- **The landing branch (`main`)** is production. It moves only when you publish, and the production
server rebuilds from it.
- **PR branches** hold one feature each. They start from the landing branch and land on the pilot
branch, one at a time, after the checks pass.
**Bring your own harness.** dx-maps works with whatever AI coding agent or agent harness you already
use. The repo gives you the branch strategy below, the checks (`bun run gates:pre`) and the Plugin
Studio; it does not ship a harness. Roles such as a coordinator that puts the landings in order, or a
Studio; it does not ship a harness. Roles such as a coordinator that puts the merges onto the pilot in order, or a
gate-keeper that reads each change before it lands, are jobs your harness gives to its agents.
![The delegation model: development at the centre behind a landing lock, inside the Plugin Studio; eight plugins around it, each owned by one agent; a coordinator and a gate-keeper over the agents; below, how a change lands in four steps, and the three outputs](documentation/readme-images/delegation-model.png)
![The delegation model: the pilot branch (development) at the centre behind the pilot lock; publishing to the landing branch (main) and its outputs below it; inside the Plugin Studio; eight plugins around it, each owned by one agent; a coordinator and a gate-keeper over the agents; below, how a change lands in four steps, and the three outputs](documentation/readme-images/delegation-model.png)
<sub>[Open the diagram full size](documentation/readme-images/delegation-model.png): on a phone its small text needs it.</sub>
### Set up the branch strategy
1. **`main` is production.** It moves only when you publish a release (step 7).
2. **`development` is the shared working branch.** Create it once, from `main`:
1. **The landing branch (`main`) is production.** It moves only when you publish (step 7).
2. **The pilot branch (`development`) is the shared working branch.** Create it once, from `main`:
```bash
git fetch origin && git switch -c development origin/main && git push -u origin development
```
3. **One branch and worktree per plugin agent**, from `origin/development`, then install once in each:
3. **One PR branch and worktree per feature**, each started from the landing branch, then install
once in each:
```bash
git worktree add -b regions/work ../dx-maps-regions origin/development
git worktree add -b upg100/work ../dx-maps-upg100 origin/development
git worktree add -b regions/work ../dx-maps-regions origin/main
git worktree add -b upg100/work ../dx-maps-upg100 origin/main
cd ../dx-maps-regions && bun install --frozen-lockfile
```
4. **One preview server per agent, each on its own port:**
4. **One preview server per agent on your own machine, each on its own port** (in the cloud, the one
pilot server below takes this role):
```bash
bun x vite --port 5301 --strictPort # the next agent uses 5302, and so on
```
5. **Land one at a time, through a lock.** Catch up with `development`, run the checks, and push only
if nothing fails (`gates:pre` exits non-zero when a check fails, so the push never runs then):
5. **Land on the pilot one at a time, through a lock.** Inside the lock, merge your PR branch into a
throwaway copy of the pilot branch, run the checks, and push that merge only if nothing fails
(`gates:pre` exits non-zero when a check fails, so the push never runs then). Your PR branch itself
stays one feature on the landing branch; only the pilot gets the merge:
```bash
flock /tmp/dx-maps-development.lock sh -c '
git fetch origin && git rebase origin/development &&
bun run gates:pre && git push origin HEAD:development'
git fetch origin && git switch --detach origin/development &&
git merge --no-edit regions/work &&
bun run gates:pre && git push origin HEAD:development
git switch regions/work'
```
`flock` guards one machine. Agents on several machines need a shared lock, such as one your
If the merge stops on a conflict, run `git merge --abort`, fix the clash on your PR branch and land
again. `flock` guards one machine. Agents on several machines need a shared lock, such as one your
harness provides.
6. **One agent owns the full build**; the others build only their own plugin:
```bash
BUILD_ALLOW=1 bun run build # the build owner: every plugin
BUNDLE=<name> bun x vite build && bun run tree && bun run fix:sri # everyone else: one plugin
```
7. **Release:** fast-forward `main` to a `development` commit that passed the checks, tag it, and
7. **Publish:** fast-forward the landing branch to a pilot commit that passed the checks, tag it, and
deploy from the tag:
```bash
git fetch origin && git switch main && git merge --ff-only origin/main
@ -749,6 +762,20 @@ gate-keeper that reads each change before it lands, are jobs your harness gives
The tag's `public/dx-maps/` folder is the built site: deploy that
([how to deploy](./documentation/DEPLOY.md)).
### Developing in the cloud with a swarm
Keep the repository on a forge you host yourself: a swarm pushes often, and on a public host one wrong
push publishes work you did not mean to share. Give each app two servers: a **pilot server** that
follows the pilot branch with hot reload, and a **landing server** that rebuilds from the landing
branch. The cloud container in `deployment/cloud/` runs both today (`BRANCH=development` for the pilot server,
`PRODUCTION_BRANCH=main` for the landing server; `BRANCH` defaults to `main`). One pilot server per app, rather
than one per pull request, keeps the memory the same however many pull requests are open. Each
developer can still build on their own machine with the Plugin Studio and push their branch.
Planned, not built yet: switching the pilot server to show one pull request at a time (with a table of
the open pull requests under it), and rebuilding the landing branch from the accepted pull requests in
order, so a rejected one is simply left out of the next rebuild.
How to contribute, and the bar for shared code: [documentation/CONTRIBUTING.md](./documentation/CONTRIBUTING.md).
Taking contributions from a contributor's own forge into your GitHub repository: [documentation/contribute/TUTORIAL.md](./documentation/contribute/TUTORIAL.md).
Diagram source: [`delegation-model.svg`](./documentation/readme-images/src/delegation-model.svg).
@ -766,13 +793,13 @@ So the project works like a busy kitchen:
1. **Each agent works in its own branch and copy of the project** — its own counter, so nobody bumps
anybody. One owner per plugin, and one per shared file.
2. **Only one change lands at a time.** Whoever holds the *landing lock* first catches up with
everyone else's latest work, fixes any clash itself and runs every check; the change goes into
`development` only if nothing fails. `development` always works.
3. **Someone keeps order.** A coordinator can put the landings in order, and a gate-keeper can read
2. **Only one change lands at a time.** Whoever holds the *pilot lock* first catches up with
everyone else's latest work, fixes any clash itself and runs every check; the change goes into the
pilot branch only if nothing fails. The pilot branch always works.
3. **Someone keeps order.** A coordinator can put the merges onto the pilot in order, and a gate-keeper can read
each change by its content before it lands. Your harness gives these jobs to its agents.
4. **`main` moves only when you publish a release**, after the checks pass on that exact version.
A preview can follow `development`; the published site follows `main`.
4. **The landing branch moves only when you publish**, after the checks pass on that exact version.
The pilot server follows the pilot branch; the production server rebuilds from the landing branch.
That's how many plugins get built at the same time, in one project, without the maps breaking.
@ -784,12 +811,12 @@ That's how many plugins get built at the same time, in one project, without the
| Word | In plain terms |
|---|---|
| **repo** | the project folder, with its full history |
| **development** | the shared working version: every agent's change is added here, one at a time |
| **main** | the production version: it moves only when `development` is published |
| **development** (the pilot) | the shared working version: every agent's change is added here, one at a time |
| **main** (the landing) | the production version: it moves only when `development` is published |
| **worktree** | a private copy of the project for one agent to work in |
| **merge conflict** | two changes touched the same lines; someone has to decide how to combine them |
| **rebase** | catching your work up with everyone else's latest before adding it |
| **landing lock** | only one change lands on `development` at a time; its holder catches up and runs every check first |
| **merge** | combining your branch with everyone else's latest work; here it happens inside the pilot lock, in a throwaway copy of the pilot |
| **pilot lock** | only one change merges onto `development` at a time; its holder catches up and runs every check first |
| **tag** | a fixed name for one release commit, such as `v1.0.0`, so you can always deploy exactly it |
</details>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 858 KiB

After

Width:  |  Height:  |  Size: 767 KiB

View file

@ -10,7 +10,7 @@
<path id="mqL" d="M 679,884 A 71,71 0 0 0 821,884"/>
</defs>
<rect width="1500" height="2086" fill="#ffffff"/>
<text x="750" y="54" text-anchor="middle" font-size="34" font-weight="800" fill="#0f172a">How dx-maps is built: one owner per plugin, one landing at a time</text>
<text x="750" y="54" text-anchor="middle" font-size="34" font-weight="800" fill="#0f172a">How dx-maps is built: one owner per plugin, one merge at a time</text>
<circle cx="750" cy="850" r="540" fill="none" stroke="#2563eb" stroke-width="1.6" stroke-dasharray="3 7" opacity=".75"/>
<circle cx="750" cy="850" r="355" fill="none" stroke="#15803d" stroke-width="2.2" opacity=".85"/>
<circle cx="750" cy="850" r="606" fill="none" stroke="#7c3aed" stroke-width="2" stroke-dasharray="8 7" opacity=".6"/>
@ -29,8 +29,8 @@
<circle cx="750" cy="850" r="120" fill="#fef2f2" stroke="#dc2626" stroke-width="3" stroke-dasharray="10 6"/>
<path id="mqT" d="M 646,850 A 104,104 0 0 1 854,850" fill="none"/>
<path id="mqB" d="M 644,850 A 106,106 0 0 0 856,850" fill="none"/>
<text font-size="13" font-weight="800" fill="#b91c1c" letter-spacing="1.6"><textPath href="#mqT" startOffset="50%" text-anchor="middle">LANDING LOCK</textPath></text>
<text font-size="12" font-weight="700" fill="#b91c1c" letter-spacing="1.2"><textPath href="#mqB" startOffset="50%" text-anchor="middle">ONE LANDING AT A TIME</textPath></text>
<text font-size="13" font-weight="800" fill="#b91c1c" letter-spacing="1.6"><textPath href="#mqT" startOffset="50%" text-anchor="middle">PILOT LOCK</textPath></text>
<text font-size="12" font-weight="700" fill="#b91c1c" letter-spacing="1.2"><textPath href="#mqB" startOffset="50%" text-anchor="middle">ONE MERGE AT A TIME</textPath></text>
<rect x="617" y="846" width="26" height="20" rx="4" fill="#dc2626"/>
<path d="M 622,846 v -6 a 8,8 0 0 1 16,0 v 6" fill="none" stroke="#dc2626" stroke-width="3.5"/>
<circle cx="630" cy="856" r="3" fill="#fff"/>
@ -38,9 +38,10 @@
<path d="M 862,846 v -6 a 8,8 0 0 1 16,0 v 6" fill="none" stroke="#dc2626" stroke-width="3.5"/>
<circle cx="870" cy="856" r="3" fill="#fff"/>
<circle cx="750" cy="850" r="72" fill="#166534" stroke="#14532d" stroke-width="3"/>
<text x="750" y="850" text-anchor="middle" font-size="18" font-weight="800" fill="#fff">development</text>
<text x="750" y="872" text-anchor="middle" font-size="12.5" fill="#dcfce7">every change</text>
<text x="750" y="888" text-anchor="middle" font-size="12.5" fill="#dcfce7">lands here</text>
<text x="750" y="842" text-anchor="middle" font-size="24" font-weight="800" fill="#fff">pilot</text>
<text x="750" y="862" text-anchor="middle" font-size="13" font-weight="700" fill="#dcfce7">development</text>
<text x="750" y="882" text-anchor="middle" font-size="12.5" fill="#dcfce7">every change</text>
<text x="750" y="897" text-anchor="middle" font-size="12.5" fill="#dcfce7">lands here</text>
<text font-size="16" font-weight="800" fill="#15803d" letter-spacing="3"><textPath href="#plL" startOffset="50%" text-anchor="middle">PLUGINS</textPath></text>
<text font-size="16" font-weight="800" fill="#2563eb" letter-spacing="3"><textPath href="#agL" startOffset="18%" text-anchor="middle">AGENTS</textPath></text>
<line x1="862.1" y1="579.3" x2="822.7" y2="674.5" stroke="#15803d" stroke-width="1.4" opacity=".45"/>
@ -121,14 +122,14 @@
<text x="690" y="254" text-anchor="middle" font-size="14" font-weight="800" fill="#4c1d95">coordinator</text>
<circle cx="812" cy="248" r="56" fill="#f1f5f9" stroke="#334155" stroke-width="3.5"/>
<text x="812" y="254" text-anchor="middle" font-size="14" font-weight="800" fill="#1e293b">gate-keeper</text>
<text x="884" y="192" font-size="17" fill="#4c1d95">a coordinator puts the landings in order;</text>
<text x="884" y="192" font-size="17" fill="#4c1d95">a coordinator puts the merges in order;</text>
<text x="884" y="216" font-size="17" fill="#1e293b">a gate-keeper checks each one by its content</text>
<text x="750" y="1502" text-anchor="middle" font-size="20" font-weight="800" fill="#1e3a8a">one owner per plugin, in its own branch · a second reader for every change</text>
<rect x="36" y="110" width="304" height="280" rx="14" fill="#f8fafc" stroke="#e2e8f0" stroke-width="2"/>
<circle cx="64" cy="142" r="11" fill="#166534" stroke="#14532d" stroke-width="2.5"/>
<text x="88" y="147" font-size="15" fill="#334155">development — shared branch</text>
<text x="88" y="147" font-size="15" fill="#334155">pilot branch (development)</text>
<circle cx="64" cy="174" r="11" fill="#fef2f2" stroke="#dc2626" stroke-width="2.5"/>
<text x="88" y="179" font-size="15" fill="#334155">landing lock — one at a time</text>
<text x="88" y="179" font-size="15" fill="#334155">pilot lock — one at a time</text>
<circle cx="64" cy="206" r="11" fill="#fffbeb" stroke="#d97706" stroke-width="2.5"/>
<text x="88" y="211" font-size="15" fill="#334155">Plugin Studio — the one repo</text>
<circle cx="64" cy="238" r="11" fill="#f0fdf4" stroke="#15803d" stroke-width="2.5"/>
@ -138,9 +139,9 @@
<circle cx="64" cy="302" r="11" fill="#eff6ff" stroke="#2563eb" stroke-width="2.5"/>
<text x="88" y="307" font-size="15" fill="#334155">agent — owns one plugin</text>
<circle cx="64" cy="334" r="11" fill="#f5f3ff" stroke="#7c3aed" stroke-width="2.5"/>
<text x="88" y="339" font-size="15" fill="#334155">coordinator — orders landings</text>
<text x="88" y="339" font-size="15" fill="#334155">coordinator — orders merges</text>
<circle cx="64" cy="366" r="11" fill="#f1f5f9" stroke="#334155" stroke-width="2.5"/>
<text x="88" y="371" font-size="15" fill="#334155">gate-keeper — checks landings</text>
<text x="88" y="371" font-size="15" fill="#334155">gate-keeper — checks each merge</text>
<rect x="60" y="1536" width="1380" height="232" rx="20" fill="#fef2f2" stroke="#fca5a5" stroke-width="2"/>
<text x="750" y="1578" text-anchor="middle" font-size="22" font-weight="800" fill="#7f1d1d">How a change lands — one at a time, so many plugins can be built at once</text>
<rect x="80.0" y="1600" width="305" height="148" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
@ -163,21 +164,21 @@
<circle cx="804.0" cy="1634" r="18" fill="#dc2626"/>
<text x="804.0" y="1641" text-anchor="middle" font-size="19" font-weight="800" fill="#fff">3</text>
<text x="832.0" y="1641" font-size="19" font-weight="800" fill="#0f172a">One at a time</text>
<text x="790.0" y="1680" font-size="15" fill="#475569">in the lock: merge development,</text>
<text x="790.0" y="1680" font-size="15" fill="#475569">in the lock: merge the pilot,</text>
<text x="790.0" y="1702" font-size="15" fill="#475569">rebuild, run every check;</text>
<text x="790.0" y="1724" font-size="15" fill="#475569">push only if nothing fails</text>
<text x="1095.0" y="1682" text-anchor="middle" font-size="26" font-weight="800" fill="#dc2626">→</text>
<rect x="1115.0" y="1600" width="305" height="148" rx="14" fill="#fff" stroke="#dc2626" stroke-width="2"/>
<circle cx="1149.0" cy="1634" r="18" fill="#dc2626"/>
<text x="1149.0" y="1641" text-anchor="middle" font-size="19" font-weight="800" fill="#fff">4</text>
<text x="1177.0" y="1641" font-size="19" font-weight="800" fill="#0f172a">Promote to main</text>
<text x="1135.0" y="1680" font-size="15" fill="#475569">main is production: it moves</text>
<text x="1135.0" y="1702" font-size="15" fill="#475569">only when the owner approves,</text>
<text x="1177.0" y="1641" font-size="19" font-weight="800" fill="#0f172a">Publish to landing</text>
<text x="1135.0" y="1680" font-size="15" fill="#475569">landing (main), production:</text>
<text x="1135.0" y="1702" font-size="15" fill="#475569">moves only when the owner approves,</text>
<text x="1135.0" y="1724" font-size="15" fill="#475569">after full checks on that version</text>
<line x1="750" y1="956" x2="750" y2="1786" stroke="#334155" stroke-width="2.5" stroke-dasharray="2 5" opacity=".0"/>
<rect x="60" y="1812" width="1380" height="254" rx="20" fill="#f8fafc" stroke="#cbd5e1" stroke-width="2"/>
<text x="750" y="1856" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">main → ONE build → one web folder → three outputs</text>
<text x="750" y="1882" text-anchor="middle" font-size="16" fill="#475569">the dev preview follows development · the published site follows main</text>
<text x="750" y="1856" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">landing (main) → ONE build → one web folder → three outputs</text>
<text x="750" y="1882" text-anchor="middle" font-size="16" fill="#475569">the pilot server follows development · the production server rebuilds from main</text>
<rect x="80.0" y="1908" width="420" height="130" rx="14" fill="#fff" stroke="#15803d" stroke-width="2.5"/>
<text x="290.0" y="1930" text-anchor="middle" font-size="13" font-weight="800" fill="#64748b">OUTPUT 1</text>
<text x="290.0" y="1962" text-anchor="middle" font-size="22" font-weight="800" fill="#0f172a">The web folder</text>

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB