The table is development and main is production: the joining page, DEPLOY and the laptop sync follow it

The joining page now teaches laptop (the workshop), development (the table everyone lands on, followed by the
shared dev server) and main (production: /cdn, the websites and every partner snippet load it; never pushed to
directly). Its commands pull and push development. Schema is made on a laptop, never in the shared server's
PocketBase screen, whose automatic migration files are lost at the next restart.

DEPLOY: the cloud server follows development and publishes main at /cdn (PRODUCTION_BRANCH=main); publishing
merges development into main; EMBED_BASE_URL makes snippets absolute; /cdn revalidates within 5 minutes. The
drop-in example pointed at a deleted branch and now names research-gallery-drop-in, with a link to running all
three DX-life ways on a laptop.

Laptop sync (DX_SYNC_REMOTE) follows development by default, so teammates' landings arrive before they are
published.
This commit is contained in:
Doxa Maps 2026-09-29 00:15:55 +00:00
parent c283ac8467
commit 490dfd1f8c
4 changed files with 66 additions and 40 deletions

View file

@ -16,9 +16,9 @@ VITE_API_BASE_URL=https://pray.doxa.life
# Empty = OFF: the dev server hot-reloads YOUR local edits only (the default).
# Set to a remote name (origin) or a repo URL (https://…/repo.git) and `bun run dev` fetches every few
# seconds and fast-forwards when this checkout is on that branch with no local edits or commits; otherwise
# it only reports "main is N commits ahead" (in the log and at /api/dev-sync). It never merges over your work.
# it only reports "development is N commits ahead" (in the log and at /api/dev-sync). It never merges over your work.
DX_SYNC_REMOTE=
# The branch to follow. Empty = main.
# The branch to follow. Empty = development (the shared table; main is production).
DX_SYNC_BRANCH=
# Seconds between checks. Empty = 5 (minimum 2).
DX_SYNC_INTERVAL=

View file

@ -36,12 +36,13 @@ Details: [HOWTO-join-the-table.md](./HOWTO-join-the-table.md).
## 2. A shared cloud dev server — Docker or Coolify, with `/data`
One running copy of the Plugin Studio that follows a branch and hot-reloads every push, for a whole team
or for an agent that lives on a server. Nobody edits files on it: changes arrive by pushing.
or for an agent that lives on a server. Nobody edits files on it: changes arrive by pushing. It follows
`development`, the branch the team lands on, and publishes `main` (production) at `/cdn` (way 3).
```bash
docker build -t dx-maps-dev deployment/cloud
docker run -d -p 5173:5173 -v dx-maps-data:/data \
-e REPO_URL=https://<host>/<owner>/<repo>.git -e BRANCH=main dx-maps-dev
-e REPO_URL=https://<host>/<owner>/<repo>.git -e BRANCH=development -e PRODUCTION_BRANCH=main dx-maps-dev
```
Or paste `deployment/cloud/Dockerfile` into a container host (Coolify's "Dockerfile" build pack) and give
@ -64,24 +65,31 @@ Details: [deployment/cloud/README.md](../deployment/cloud/README.md) and
The built folder, `dx-maps/`, is what other sites embed. Every link inside it is relative, so it works
from any address.
- **From the cloud dev server:** `/cdn/` serves the **`production`** branch, never `main`. `main` is the
shared sandbox where everyone lands all day; `production` moves only when you decide the current state
is ready to be public. The container notices when `production` moves and rebuilds `/cdn` by itself
(about 2 minutes after the move, then about 2 minutes to build). Each build removes private files,
- **From the cloud dev server:** `/cdn/` serves **`main`**, which is production (`PRODUCTION_BRANCH=main`;
the container's default is a branch named `production`). `development` is the shared table where
everyone lands all day; `main` moves only when a maintainer publishes. The container notices when `main`
moves and rebuilds `/cdn` by itself (about 2 minutes after the move, then about 2 minutes to build). Each build removes private files,
refuses to publish if any remain, keeps the last good build if anything fails, and swaps
`/cdn/dx-maps/` over atomically. `/cdn/` is public (no sign-in) and sends
`Access-Control-Allow-Origin: *`, so both iframes and JS embeds work on other sites.
`Access-Control-Allow-Origin: *`, so both iframes and JS embeds work on other sites. The manifest, the
pages and each map's script revalidate after 5 minutes (with an ETag), so browsers pick up a publish
within minutes.
**To publish (promote main to production)**, once the checks pass on the commit you want public:
**To publish (merge development into main)**, once the checks pass on the commit you want public:
```bash
git fetch origin && git checkout main && git merge --ff-only origin/main
bun run gates:pre # the checks must pass on exactly this commit
git push origin main:production # /cdn follows within a few minutes
git fetch origin && git switch main && git merge --ff-only origin/main
git merge --ff-only origin/development # everything the team landed, as checked
bun run gates:pre # the checks must pass on exactly this commit
git push origin main # /cdn follows within a few minutes
```
An admin can also rebuild at once (`POST /admin/build`), and `/admin/status` shows what `/cdn` serves
and what `production` points at.
and what `main` points at.
Copy-paste snippets are relative unless the build is given an absolute base:
`EMBED_BASE_URL=https://<host>/<path>` (the address the `dx-maps/` folder is served under, without
`dx-maps`) makes every snippet absolute, so it works when pasted on another site.
- **On any static host:** build locally with `BUILD_ALLOW=1 bun run build` and upload `public/dx-maps/`.
For JS embeds on other sites, the host must send `Access-Control-Allow-Origin: *`.
@ -98,10 +106,16 @@ Copy the built `dx-maps/` folder into your site's `public/` (renaming it is fine
the host: no second server and no cross-site setup, because the maps and the page share one origin.
**Live example:** the DX-life branch
[`research-gallery`](https://git.nuxtbase.app/doxa-life/doxa-maps-cdn/src/branch/research-gallery)
[`research-gallery-drop-in`](https://git.nuxtbase.app/doxa-life/doxa-maps-cdn/src/branch/research-gallery-drop-in)
drops the folder into DX-life's `public/` and fills `/research` with the Research Gallery. Run it with
`bun install && bun run dev`, then open `/research/`.
**All three ways a site can take the maps, on your laptop:** DX-life has one branch per way:
`research-gallery-drop-in` (this section), `research-gallery-cdn` (way 3, loading from a dx-maps server) and
`research-gallery-subdomain` (the Research Gallery on its own address). The exact commands to run each
against your local dx-maps (ports, the one or two settings, and what to check) are in
[LAPTOP-6WAY.md](https://git.nuxtbase.app/doxa-life/doxa-maps-cdn/src/branch/research-gallery-drop-in/documentation/LAPTOP-6WAY.md).
---
**Which one?** Building maps: **1**, plus **2** if several people or agents work together. Showing maps

View file

@ -3,9 +3,10 @@
You want to build a map. Someone told you there is a repo, a dev server, and a thing called PocketBase, and
now you are staring at three of them wondering which one you are supposed to touch.
Short answer: **you work on your laptop, you land your work on main, and a shared server shows everyone what
landed.** That is the whole shape. The rest of this page is what each of those three is for, and the five
commands you actually type.
Short answer: **you work on your laptop, you land your work on `development`, and a shared server shows
everyone what landed. `main` is production:** it moves only when a maintainer publishes, and it is what the
public sites and every partner embed load. That is the whole shape. The rest of this page is what each of
those three is for, and the five commands you actually type.
> **This page is about working alongside other people.** If you want to know how to build a plugin itself —
> the files, the build, getting it live — read
@ -32,29 +33,32 @@ reset it, fill it with nonsense — it is yours.
If your coding agent runs on a server rather than on your laptop, there is a second way to work —
see [CLOUD-DEVELOPMENT-ENVIRONMENT.md](./CLOUD-DEVELOPMENT-ENVIRONMENT.md). Most people do not need it.
### 2. `main` — the truth
### 2. `development` — the table
When your work is ready, it goes to `main`. That is the one place everybody agrees on. Nothing is "real"
until it is there.
When your work is ready, it goes to `development`. That is the one branch the whole team lands on, all day.
A shared dev server follows it and reloads about every 15 seconds, so the team sees each other's work
without anyone sending anyone a zip file. You do not edit on that server. You never need to log into it to
do your job.
### 3. The shared dev server — the table
### 3. `main` — production
A server watches `main` and reloads about every 15 seconds. That is where the team sees each other's work without
anyone sending anyone a zip file. You do not edit here. You never need to log into it to do your job.
`main` is what the public sees: the public build (`/cdn`), the websites that embed the maps, and every
partner's copy-paste snippet all load it. **You never push to `main`.** A maintainer publishes by merging
`development` into it once the team's work is checked, and the public build follows within a few minutes.
> **The rule that makes this work:** one plugin folder per person. You edit `plugin-designs/<your-plugin>/`
> and nobody else does. Everything below depends on that.
---
## Getting your work onto main
## Getting your work onto the table
Three commands, in this order, every time:
```bash
git add -A && git commit -m "what you changed"
git pull --rebase origin main
git push origin HEAD:main
git pull --rebase origin development
git push origin HEAD:development
```
If the push is rejected, someone landed first. Run the middle command again and push again. That is not an
@ -100,7 +104,7 @@ Two optional dials, if you want them:
| | what it does | default |
|---|---|---|
| `DX_SYNC_BRANCH` | which branch to follow | `main` |
| `DX_SYNC_BRANCH` | which branch to follow | `development` (the table) |
| `DX_SYNC_INTERVAL` | seconds between checks | `5` (minimum 2) |
**It only pulls when it is completely safe to.** All three must be true: you are on that branch, you have no
@ -108,7 +112,7 @@ unsaved edits, and you have no local commits. If any one of them is not, it does
it tells you instead, in the dev server log:
```
main is 3 commits ahead … pull when ready
development is 3 commits ahead … pull when ready
```
So it cannot surprise you mid-thought. You can also check that any time at `/api/dev-sync`.
@ -129,8 +133,12 @@ There are three databases. You work against one of the first two — never the t
fields — as migration files committed to the repo, in `backend/pocketbase/pb_migrations/`.
So adding a field is a normal change: you edit, you commit the migration, you push it like any other file,
and it goes through review with everything else. When the person who publishes runs the publish step, the
migrations apply.
and it goes through review with everything else. A server applies new migrations when it restarts.
**Make schema on your laptop, never in the shared server's database screen.** PocketBase writes a migration
file for every change made in its admin screen. On your laptop that file lands in your checkout, and you
commit it. On the shared server it lands in a copy of the repo that is thrown away at the next restart, so
the table's database and the repo drift apart.
This is deliberate. A database rule that opens something to the public arrives as **a commit someone can
read** rather than as a setting somebody clicked at midnight. If you have ever inherited a system where the
@ -181,7 +189,8 @@ provider instead.
1. **Build on your laptop.** It is fast and it is yours.
2. **One plugin folder per person.** This is what keeps ten people out of each other's way.
3. **`pull --rebase`, then push.** A rejected push is normal — 95% of them were, and none became conflicts.
3. **`pull --rebase`, then push to `development`.** A rejected push is normal — 95% of them were, and none
became conflicts. Never push to `main`: that is production.
4. **The shared server is for looking, not editing.**
5. **Schema is a commit, never a click.** You never publish a database.
@ -189,9 +198,11 @@ provider instead.
## When something is wrong
- **"Your push was rejected"** — normal. `git pull --rebase origin main`, push again.
- **"Your push was rejected"** — normal. `git pull --rebase origin development`, push again.
- **A real merge conflict** — two people edited one file. Talk to them; the one-folder rule slipped.
- **Your change is not on the shared server** — it reloads about every 15 seconds, not instantly. Check your
push actually landed: `git log origin/main -1`.
- **The shared server shows an old version of your plugin** — you are probably looking at what is on `main`,
not what is on your laptop. Those are different on purpose.
push actually landed: `git log origin/development -1`.
- **The shared server shows an old version of your plugin** — you are probably looking at what is on
`development`, not what is on your laptop. Those are different on purpose.
- **The public site does not show your change** — it shows `main` (production), which moves only when a
maintainer publishes. After a publish, a browser can keep the old maps for up to 5 minutes.

View file

@ -1,17 +1,18 @@
/**
* internal/dev-sync-plugin.mjs — `bun run dev` follows a shared branch, so a laptop sees teammates' landings.
*
* WHY: ten people each running `bun run dev` should see what the others landed on main without anyone
* WHY: ten people each running `bun run dev` should see what the others landed on development without anyone
* typing `git pull`: the dev server fast-forwards and Vite hot-reloads the changed files. It is the local
* form of the cloud container's pull loop (deployment/cloud/entry.sh), MINUS the `reset --hard`: a laptop
* holds someone's unsaved work, so this NEVER merges over local edits, never stashes, never resets.
*
* OPT-IN, ONE switch in .env.local: DX_SYNC_REMOTE = a remote name (origin) OR a repo URL (https://…/x.git),
* e.g. to watch what cloud agents push. DX_SYNC_BRANCH defaults to main, DX_SYNC_INTERVAL to 5 seconds.
* e.g. to watch what cloud agents push. DX_SYNC_BRANCH defaults to development (the shared table; main is
* production and moves only on a publish), DX_SYNC_INTERVAL to 5 seconds.
* Unset = off: plain hot reload of local edits, as always. Every interval it fetches <remote> <branch>
* (a URL needs no `git remote add`; private repos use your normal git credentials) and fast-forwards ONLY when
* this checkout is on that branch, no tracked file is modified, and `git merge --ff-only` agrees.
* Otherwise it logs once per new upstream commit ("main is N commits ahead, pull when ready") and reports
* Otherwise it logs once per new upstream commit ("development is N commits ahead, pull when ready") and reports
* the state at /api/dev-sync for a Studio badge.
*
* DEV ONLY (`apply: 'serve'`).
@ -63,7 +64,7 @@ export async function syncOnce(root, remote, branch, log = { info() {}, warn() {
export function devSyncPlugin(root, env = process.env) {
const remote = env.DX_SYNC_REMOTE || ''
const branch = env.DX_SYNC_BRANCH || 'main'
const branch = env.DX_SYNC_BRANCH || 'development'
const every = Math.max(2, Number(env.DX_SYNC_INTERVAL) || 5)
return {
name: 'dx-dev-sync',