Document why a gate cannot import plugin source, and stop teaching public binds
Some checks are pending
build-gate / build (push) Waiting to run
Some checks are pending
build-gate / build (push) Waiting to run
Three docs still told a reader to start a server on every interface: the reference recipe for the share round-trip, the quoted scripts block, and app-wrappers.md. Agents followed those recipes faithfully, which is how twenty publicly bound http.server processes accumulated on this box. All three now bind 127.0.0.1. check:quoted-scripts caught the second one on its own. Also adds the gate-authoring constraint that just broke a build. Plugin and library source is written for Vite, not Node, and fails two ways: the @map alias is unresolvable (hit first), and import.meta.glob is undefined in 18 files including 11 of the 20 plugin index.js. A new check:facet-cascade imported the dashboard config, reached library/colors/_registry.js transitively, and threw -- while already wired into build:unlocked, so the build failed for a reason unrelated to the code being built. The counts in that section were measured, not estimated. A first draft said "every plugin index.js" and the real number is 11 of 20; it also blamed the glob when the first error is actually the alias. States the two rules that follow: build:unlocked is only for gates that run clean in plain Node with no browser and no network, and a gate gets run before it gets wired in -- a broken gate in the build chain is worse than no gate, because it goes red for the wrong reason and trains people to skip the step. Records the other box constraints that have each produced a wrong answer: Node 20 (no fs.glob), bfs find rejecting relative -newermt, no hardware WebGL, and bunx not existing.
This commit is contained in:
parent
0296b03aa7
commit
64731f2f3e
3 changed files with 52 additions and 4 deletions
|
|
@ -25,9 +25,9 @@ The scripts these commands are grounded in (from this branch's `package.json`):
|
|||
// claiming to be ground truth, and this one had silently stopped being it.
|
||||
"dev": "bun x vite",
|
||||
"build": "bash internal/build-lock.sh",
|
||||
"build:unlocked": "bun run check:template-literal && bun run check:sfc-parse && bun run clean && bun run token && for d in plugin-designs/*/; do BUNDLE=$(basename \"$d\") bun x vite build || exit 1; done && bun run tree && bun run check:drop && bun run check:parse && bun run verify && bun run check:sri && bun run check:public-hygiene && bun run check:label-joins && bun run check:descriptions && bun run check:no-abs-paths && bun run check:dep-names && bun run check:composite-srcs && bun run check:embed-targets && bun run check:nested-token && bun run gallery",
|
||||
"build:unlocked": "bun run check:template-literal && bun run check:sfc-parse && bun run clean && bun run token && for d in plugin-designs/*/; do BUNDLE=$(basename \"$d\") bun x vite build || exit 1; done && bun run tree && bun run check:drop && bun run check:parse && bun run verify && bun run check:sri && bun run check:public-hygiene && bun run check:label-joins && bun run check:facet-cascade && bun run check:descriptions && bun run check:no-abs-paths && bun run check:dep-names && bun run check:composite-srcs && bun run check:embed-targets && bun run check:nested-token && bun run gallery",
|
||||
"preview": "bun x vite preview",
|
||||
"serve": "python3 -m http.server 4173 --bind 0.0.0.0 --directory public",
|
||||
"serve": "python3 -m http.server 4173 --bind 127.0.0.1 --directory public",
|
||||
"cap:sync": "./node_modules/.bin/cap sync android",
|
||||
"cap:verify": "test -f deployment/capacitor/android/app/src/main/assets/public/index.html && echo \"OK webDir copied\" || (echo \"FAIL: empty assets/public -> blank white screen. Check webDir.\" && exit 1)",
|
||||
"apk:debug": "bun run build && bun run cap:sync && bun run cap:verify && cd deployment/capacitor/android && ./gradlew assembleDebug",
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ local assets. Paths follow the Lucide idiom (24×24, 2px stroke, round caps).
|
|||
|
||||
```bash
|
||||
cd library/layouts/app-wrapper-material
|
||||
python3 -m http.server 4173 --bind 0.0.0.0
|
||||
python3 -m http.server 4173 --bind 127.0.0.1
|
||||
```
|
||||
|
||||
Then open it and narrow the window below 1024px (or use the browser device toolbar) to check the
|
||||
|
|
|
|||
|
|
@ -249,13 +249,61 @@ Measured 2026-09-22, by running each one:
|
|||
| `smoke:gallery` (and `smoke:playground`, its alias) | a dev server: `bun x vite` on **:5174** | exits non-zero with *"cannot read http://localhost:5174/__plugin-gallery/registry.json — is `bun x vite` running?"* — a **precondition**, not a defect |
|
||||
| `smoke:share` | `--url <page>` arguments, a served tree, a token, and **`--browser chromium` on this box** | prints `usage:` and exits non-zero; with default Firefox it stops with "Verified on a BLANK page, so it is the engine/box, not the product" and names the chromium flag itself |
|
||||
|
||||
#### ★ WRITING A NEW GATE: you cannot `import()` plugin or library source into plain Node
|
||||
|
||||
This is the trap that will bite the next gate author, so it is written here rather than left to be
|
||||
rediscovered. Plugin and library source is written for **Vite**, not for Node, and it fails two
|
||||
different ways — counted 2026-09-22, not estimated:
|
||||
|
||||
| Vite-only feature | Where | What plain `node` does |
|
||||
|---|---|---|
|
||||
| the **`@map` alias** (`@map/ProfileLoader.vue`) | resolved by `vite.config`, not by Node | `Cannot find package '@map/ProfileLoader.vue'` — this is what you hit **first**, before any glob |
|
||||
| **`import.meta.glob`** | **18 files**: 11 of the 20 plugin `index.js`, plus `library/ProfileLoader.vue`, `library/i18n/index.js`, `library/composables/useShadowStyles.js`, `library/colors/_registry.js`, and 3 more | `TypeError: (intermediate value).glob is not a function` |
|
||||
|
||||
`import.meta.glob` is a **build-time** feature: Vite statically replaces it with real imports while
|
||||
bundling. Plain `node` leaves it undefined, so the module throws the instant it is evaluated:
|
||||
|
||||
```
|
||||
TypeError: (intermediate value).glob is not a function
|
||||
at library/colors/_registry.js:26
|
||||
```
|
||||
|
||||
The glob failure is the nastier of the two because it can arrive **transitively**, from a module you
|
||||
never named — you import a config, the config imports a colour strategy, and the registry throws.
|
||||
|
||||
Measured 2026-09-22: a new `check:facet-cascade` gate did `await import()` on the dashboard config,
|
||||
which transitively reached the colour registry, and crashed — **while already wired into
|
||||
`build:unlocked`**, so `bun run build` failed for a reason that had nothing to do with the code being
|
||||
built. The library file was correct and pre-existing; the defect was entirely in the gate reaching it.
|
||||
|
||||
So when a gate needs to exercise real plugin logic, pick one:
|
||||
|
||||
| Approach | When | Cost |
|
||||
|---|---|---|
|
||||
| Import only the **leaf module** under test | the logic is in a plain `.js` with no Vite features (most composables are) | cheapest and most reliable — keep the dependency cone small |
|
||||
| Test against the **BUILT bundle** over `bun run serve` | the logic only exists after bundling, or needs the DOM | needs a browser + a served tree, so it belongs in `verify:browser-all`, **not** `build:unlocked` |
|
||||
| Parse the **source text** instead of executing it | you are asserting a structural property (a rule exists, a field is declared) | cannot catch runtime behaviour — say so in the gate's header |
|
||||
|
||||
**Two hard rules that follow.** A gate goes in `build:unlocked` only if it runs clean in plain Node
|
||||
with no browser and no network — everything else is a standalone or browser gate, the way
|
||||
`check:a11y-button-names` and `check:composite` are handled. And **run the gate before wiring it in**:
|
||||
a broken gate inside the build chain is worse than no gate, because it goes red for the wrong reason
|
||||
and trains whoever hits it to skip the step.
|
||||
|
||||
**Other runtime constraints on the build box**, all of which have produced a wrong answer at least
|
||||
once: Node is **v20** (no `fs.glob`, which is 22+); `find` is **bfs**, which *rejects* a relative
|
||||
`-newermt '-45 seconds'` and errors to stderr while printing nothing — use `bun run check:settled`,
|
||||
or an absolute `$(date -u -d '-45 seconds' +%Y-%m-%dT%H:%M:%SZ)`; there is **no hardware WebGL** (judge
|
||||
maps with `verify:map-paint`'s SwiftShader path, never by expecting a painted basemap); and `bunx`
|
||||
does not exist here — use the spaced `bun x`.
|
||||
|
||||
**Copy-paste: the full share round-trip over every plugin that has a Share button.** Assembled and run
|
||||
2026-09-22 — **8/8 pass**. Without this recipe an inheritor has to work out the server, the token and the
|
||||
eight `--url` flags from scratch, which is why the gate had never been run across all of them.
|
||||
|
||||
```sh
|
||||
cd doxa-maps-plugin-studio
|
||||
( cd public && python3 -m http.server 4950 & ) # serve the built tree
|
||||
( cd public && python3 -m http.server 4950 --bind 127.0.0.1 & ) # serve the built tree, LOOPBACK ONLY
|
||||
export MAPBOX_TOKEN=$(curl -s https://doxa.life/api/maps/token \
|
||||
| python3 -c 'import json,sys;print(json.load(sys.stdin)["token"])')
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue