doxa-maps/internal/check-element-bootstrap.mjs
Doxa Maps 2c397d25ef Source comments state design history and measurements as facts, without quoting or naming anyone
Comments in library/ and internal/ that quoted a person, attributed a decision to one, used first person,
named fleet agents or carried private host names now state the same technical fact in neutral words:
poster-render (including the comments it writes into generated posters), packet-render, the poster and
research scripts, PosterWall, DataTable, the legends, the layouts and the gates. The network-policy note
states the policy itself. Behaviour is unchanged: a few printed gate messages and reason labels drop an
agent name, the poster-render integrity pin follows its comment-only edit, and series.json changes only in
its _meta and _why notes. The historical poster design snapshots (library/scripts/posters/snapshots/),
which nothing reads, are removed.
2026-10-01 00:37:17 +00:00

114 lines
6.9 KiB
JavaScript

#!/usr/bin/env node
/**
* check-element-bootstrap — every plugin that ships a custom element must build it the same way.
*
* ★ THE BUG THIS EXISTS FOR WAS LIVE IN FIVE PLUGINS AT ONCE. A profile element is
* defineCustomElement(ProfileLoader, { configureApp }) and the configureApp has four steps; the fourth,
* `app.provide('appI18n', i18n)`, is the one that gets dropped. ProfileLoader injects that instance
* (ProfileLoader.vue:176) to drive its locale from profile-config, so without it the drive never runs
* and every useI18n()/t() string is stuck at the boot locale — while setRuntimeLocale() on the next line
* still fires, so tStatic strings DO follow the host. The element renders a convincing MIXTURE of the
* reader's language and English.
*
* Nothing throws, because the t() shim falls back to its key. So it reads as "a few strings not
* translated yet" and it survived in country-posters, key-cities, gospel-resources, cities-internet and
* my-upg-100-list simultaneously — confirmed independently by a census of all 19 entry
* files and found the same five and no sixth.
*
* ★ AND A CENSUS THAT COUNTED FUNCTION BODIES SAW FOUR OF SIXTEEN. That is why this gate greps for the
* SHAPE (`defineCustomElement(ProfileLoader`) rather than for a known list: eleven of the sites are not
* `function buildXElement()` at all, they are `const El = defineCustomElement(...)`, and a check written
* around the four that matched would have passed the twelve that did not.
*
* TWO ARMS:
* A. no plugin entry may call defineCustomElement(ProfileLoader, …) directly — use the helper;
* B. the helper itself must provide appI18n, PLANTED as a negative case, so the regression cannot
* return by editing library instead of a plugin.
*
* Exit 0 pass · 1 fail.
*/
import { readFileSync, existsSync, readdirSync } from 'node:fs'
import { resolve, dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const TAG = 'ELEMENT-BOOTSTRAP'
const HELPER = 'library/elements/defineMapElement.js'
const fails = []
// Stated exemptions. An element that wraps its OWN root component rather than ProfileLoader is a
// different thing and is not in scope: it has no ProfileLoader to inject anything.
const EXEMPT = new Map([])
// ── ARM A: no entry builds a profile element by hand ─────────────────────────────────────────────
const PD = join(ROOT, 'plugin-designs')
let entries = 0, viaHelper = 0
for (const p of readdirSync(PD)) {
const f = join(PD, p, 'index.js')
if (!existsSync(f)) continue
const src = readFileSync(f, 'utf8')
const buildsProfileElement = /defineCustomElement\s*\(\s*ProfileLoader/.test(src)
const usesHelper = /defineMapElement\s*\(/.test(src)
if (!buildsProfileElement && !usesHelper) continue
entries++
if (usesHelper) viaHelper++
// ★ CALLED IS NOT IMPORTED. The first version of this migration called defineMapElement in eleven
// entries that never imported it: a build does not fail on an unbound name, only the element's
// first import does (ReferenceError), so the shape check above stayed green while eleven plugins
// could not load. The landing precondition caught it by importing the profiles.
if (usesHelper && !/import\s*\{[^}]*\bdefineMapElement\b[^}]*\}\s*from\s*['"][^'"]*defineMapElement(\.js)?['"]/.test(src)) {
fails.push(`${p}/index.js calls defineMapElement(…) but never imports it: the element throws\n`
+ ` "defineMapElement is not defined" the moment it loads.`)
}
if (buildsProfileElement) {
if (EXEMPT.has(p)) continue
fails.push(`${p}/index.js calls defineCustomElement(ProfileLoader, …) directly.\n`
+ ` Use ${HELPER}. Building it by hand is how five plugins shipped without\n`
+ " provide('appI18n') and rendered a mixture of the reader's language and English.")
}
}
if (entries < 10) {
fails.push(`only ${entries} profile-element entries found — the detection stopped matching and this `
+ 'gate would pass while checking almost nothing.')
}
// ── ARM B: the helper provides appI18n, proved by PLANTING its absence ───────────────────────────
const helperSrc = existsSync(join(ROOT, HELPER)) ? readFileSync(join(ROOT, HELPER), 'utf8') : ''
if (!helperSrc) {
fails.push(`${HELPER} does not exist — every entry above is pointing at nothing.`)
} else {
// ★ COMMENTS STRIPPED FIRST, and this gate's own negative case is what caught the need. The helper's
// docstring QUOTES the provide line in prose, so a predicate over the raw source matched the
// documentation and reported the code present even after the code was removed — it would have passed
// a helper that only talked about providing appI18n. A check that reads comments is reading the
// author's intentions, not the program.
const code = (s) => s.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '')
// Either spelling counts: app.provide('appI18n', …) or provideOwn(app, 'appI18n', …). The second
// exists because a NESTED element must shadow its parent's instance deliberately (see
// library/elements/provide-own.js); what this gate cares about is that the instance reaches
// ProfileLoader at all, not which function put it there.
const provides = (s) => /(?:app\.provide\(\s*|provideOwn\(\s*app\s*,\s*)['"]appI18n['"]\s*,/.test(code(s))
const installs = (s) => /app\.use\(\s*i18n\s*\)|app\.use\(\s*createAppI18n\(\)\s*\)/.test(code(s))
if (!provides(helperSrc)) {
fails.push(`${HELPER} installs i18n but never provides it. ProfileLoader injects 'appI18n' to drive\n`
+ ' the locale; without the provide the element is permanently at its boot locale, silently.')
}
if (!installs(helperSrc)) fails.push(`${HELPER} does not install an i18n instance at all.`)
// THE PLANT: the same predicate, run against a bootstrap with the provide removed, MUST report it.
// /gm, not /m: the docstring quotes the line too, so removing only the FIRST match deleted the
// DOCUMENTATION and left the code — the plant then "failed to detect" a provide that was still there.
// Caught by this assertion failing on a correct helper, which is the plant doing its job on itself.
const planted = helperSrc.replace(/^.*(?:app\.provide\(\s*|provideOwn\(\s*app\s*,\s*)['"]appI18n['"].*$/gm, '')
if (provides(planted)) {
fails.push('the check for the provide cannot detect its absence — it is measuring nothing. '
+ 'A gate that passes its own negative case is worse than no gate.')
}
}
if (fails.length) {
console.error(`${TAG}: FAIL`)
for (const f of fails) console.error(` ✗ ${f}`)
process.exit(1)
}
console.log(`${TAG}: PASS — ${viaHelper}/${entries} profile elements are built through ${HELPER}, which provides `
+ "appI18n; an element built without it is detected, so the 'permanently English' regression cannot return quietly.")