diff --git a/documentation/README-SECTION-3-cli-commands.md b/documentation/README-SECTION-3-cli-commands.md index d0470b670..348c2bfff 100644 --- a/documentation/README-SECTION-3-cli-commands.md +++ b/documentation/README-SECTION-3-cli-commands.md @@ -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", diff --git a/documentation/architecture/app-wrappers.md b/documentation/architecture/app-wrappers.md index f8808eaa3..d6a39db5c 100644 --- a/documentation/architecture/app-wrappers.md +++ b/documentation/architecture/app-wrappers.md @@ -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 diff --git a/documentation/reference.md b/documentation/reference.md index 197940822..102a2e45e 100644 --- a/documentation/reference.md +++ b/documentation/reference.md @@ -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 ` 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"])')