# Develop a plugin and see it live — the golden path **Author a map plugin, build it once, prove it mounts, and see it live — in six steps.** Each step links to the doc that teaches that piece in depth. | Step | You do | Command | Done when | |---|---|---|---| | **1. Pick a capability** | choose how much your plugin nests | — | you know your tier (1–5) | | **2. Author** | write 3 files in `plugin-designs//` | — | the folder has `.vue` + `index.js` + `index.html` | | **3. See it in the studio** | iterate with hot reload, no build | `bun run dev` | your plugin appears at http://localhost:5173/ | | **4. Build** | produce the deployable tree | `BUILD_ALLOW=1 bun run build` | exit 0, all guards PASS | | **5. Prove it mounts** | open every plugin in a real browser | `bun run smoke:embeds` | `N/N pages mount cleanly` | | **6. Publish and see it live** | copy the tree to your host, embed it | see Step 6 | the live URL shows it | Everything you build is a **compiled Vue custom element**: one folder → one bundle → one ``. A static HTML page is never a plugin (it can only be iframed). --- ## Step 1 — Pick a capability A plugin is one of five tiers, simplest → most composable: **single element → prop-table → multi-view → composite (nested elements) → app shell (nested SPAs)**. Composite tiers declare themselves in `.meta.json` (`{"type": …}`). Imitate the nearest reference plugin: [reference-capability-exemplars.md](reference-capability-exemplars.md) names one per tier. ## Step 2 — Author: three files are enough In **`plugin-designs//`**: | File | Required? | What it is | |---|---|---| | `.vue` | **required** | the root Vue SFC — your plugin's UI (styles are shadow-scoped automatically) | | `index.js` | **required** | `defineCustomElement(RootVue)` → `customElements.define('', El)` — register ONLY your own tag | | `index.html` | **required** | the staging entry page that mounts `` — the build fails without it | | `.meta.json` | composite tiers only | `{"type": "", "composes": […], "label": "…"}` | | `.instances.json` | optional | preset instances — supplying one makes the bundle **prop-table** (cap 2) | | `src/` | optional | private components used only by this plugin | **`plugin-designs/` is your free zone.** Contribute to the shared `library/` (`@map`) only when a component is genuinely **reusable and parameterized** — a library edit reaches every plugin. Walkthrough: [tutorial.md](tutorial.md). ## Step 3 — See it in the studio (seconds, no build) ```sh bun run dev # vite on :5173; `/` is the Plugin Studio, every plugin in one place bun run smoke:studio # ~5s: asserts the studio mounts and every plugin is a real subtree ``` This is the loop to live in while authoring: about 5 seconds, against a full build of 1–4 minutes. The build is for publishing, not for looking. ## Step 4 — Build the deployable tree ```sh BUILD_ALLOW=1 bun run build # discovers plugin-designs/*, bundles each, writes public/dx-maps//.js ``` **Exit 0 + every guard PASS = a good build.** Your plugin now exists under `public/dx-maps//` with a standalone leaf page and a `manifest.json` entry. `BUILD_ALLOW=1` is deliberate: a full build first wipes `public/dx-maps/`, so it refuses (exit 75) in a shared checkout unless you mean it. To compile only your plugin: `BUNDLE= bun x vite build`. Recipes: [how-to.md](how-to.md). ## Step 5 — Prove it mounts in a real browser **A green build is a static result**: it proves files exist, parse and carry correct hashes. It never opens your element, and an element that throws on setup renders nothing while the build stays green. ```sh MAPBOX_TOKEN=$(python3 -c "import json;print(json.load(open('vite.doxa-maps-build-config.json'))['token'])") MAPBOX_TOKEN=$MAPBOX_TOKEN bun run smoke:embeds # every plugin MAPBOX_TOKEN=$MAPBOX_TOKEN node internal/embed-smoke.mjs --grep # just yours ``` **Expect `N/N pages mount cleanly`.** The token matters: without it every *map* plugin fails to mount while every non-map plugin passes, which looks like many broken plugins and is not. Take the token from the build config, as above ([why](#notes-and-history)). ## Step 6 — Publish and see it live **Copy the built tree to any static host, then embed from it.** The tree is `public/dx-maps/`; every plugin's ready-made snippets are in its `manifest.json`. ```sh set -e # first line: a failing check must stop the publish bun run check:settled # never publish a tree a build is rewriting bun run check:sri # every leaf's integrity hash matches its bundle bun run check:embed-targets # every embed snippet points at a real file rsync -a --delete public/dx-maps/ /dx-maps/ # commit and push the host checkout; for a large tree: git -c http.postBuffer=524288000 push … ``` | # | Deployment method | How | Status | |---|---|---|---| | 1 | **iframe** | point an `