From 35955a4db607e04c08fb701f061e2b1fa3270ebf Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Mon, 28 Sep 2026 06:49:22 +0000 Subject: [PATCH] check:one-palette sees a bundle's private src/colors/ palettes; Rudder's offline hint states Go 1.22+ and the setup Two findings from upg100's census. check-one-palette read strategy palettes from library/colors/ only, so a bundle's own src/colors/ strategy (which overrides the library one for that bundle) was never checked; it now reads both, the bundle's over the library's, and --self-test plants a private palette. Rudder's engine-offline notice gave a go build line without saying Go 1.22+ is needed or that setup-runtime.sh downloads the model; it now says both and points at bun run desktop:build and the desktop HOWTO. --- internal/check-one-palette.mjs | 42 +++++++++++++++----- plugin-designs/rudder-voice/rudder-voice.vue | 6 ++- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/internal/check-one-palette.mjs b/internal/check-one-palette.mjs index 2da8e395c..fcd43a662 100644 --- a/internal/check-one-palette.mjs +++ b/internal/check-one-palette.mjs @@ -47,8 +47,9 @@ * NEGATIVE-TESTED: reintroducing a private `const CATEGORICAL = ['#...']` into the aggregations * composable fails assertion 2 by name; an `[i % CATEGORICAL.length]` lookup fails assertion 3. */ -import { readFileSync, existsSync, readdirSync } from 'node:fs' +import { readFileSync, existsSync, readdirSync, mkdtempSync, writeFileSync, rmSync } from 'node:fs' import { resolve, dirname, join } from 'node:path' +import { tmpdir } from 'node:os' import { fileURLToPath } from 'node:url' import { TREE_NAME } from './_tree-name.mjs' @@ -96,7 +97,32 @@ export function paletteHexes(src) { return [...new Set(strings.flatMap((t) => [...t.matchAll(/#[0-9a-fA-F]{6}\b/g)].map((m) => m[0].toLowerCase())))] } +/** + * The strategy palettes in one colors folder: library/colors/, or a bundle's own + * plugin-designs//src/colors/ (bundle-private strategies merge OVER the library set for that + * bundle — see that folder's README). Filename → key as the registry names it is not needed here; the + * file stem is the key both places use. + */ +export function collectStrategies(dir) { + const out = {} + if (!existsSync(dir)) return out + for (const f of readdirSync(dir)) { + if (!f.endsWith('.js') || f.startsWith('_') || f.startsWith('.') || f === 'index.js') continue + const hexes = paletteHexes(readFileSync(join(dir, f), 'utf8')) + if (hexes && hexes.length) out[f.replace(/\.js$/, '')] = hexes + } + return out +} + if (process.argv.includes('--self-test')) { + // A bundle's PRIVATE palette (src/colors/.js) must be seen, not only library/colors/ (the gate was + // blind to it: upg100's census, BUG-CHECK-ONE-PALETTE-BLIND-TO-PRIVATE-PALETTES). + const tmp = mkdtempSync(join(tmpdir(), 'one-palette-')) + writeFileSync(join(tmp, 'religion.js'), "export const PALETTE = { islam: '#010203', hinduism: '#040506' }\n") + writeFileSync(join(tmp, '_skip.js'), "export const PALETTE = { x: '#999999' }\n") + const seen = JSON.stringify(collectStrategies(tmp)) + rmSync(tmp, { recursive: true, force: true }) + if (seen !== JSON.stringify({ religion: ['#010203', '#040506'] })) { console.error(`ONE-PALETTE: SELF-TEST FAIL — a planted private palette read as ${seen}`); process.exit(1) } const src = [ "export const PALETTE = {", " a: '#112233', // not #445566 (a comment inside the export)", @@ -107,18 +133,14 @@ if (process.argv.includes('--self-test')) { ].join('\n') const got = paletteHexes(src).join(',') if (got !== '#112233,#aabbcc') { console.error(`ONE-PALETTE: SELF-TEST FAIL — read ${got}, want #112233,#aabbcc`); process.exit(1) } - console.log('ONE-PALETTE: SELF-TEST PASS — only the PALETTE export\'s string values are read; comments and later code are not.') + console.log('ONE-PALETTE: SELF-TEST PASS — only the PALETTE export\'s string values are read; comments and later code are not; a bundle\'s private src/colors/ palette is seen.') process.exit(0) } // ── the expected palettes, read from the strategy modules the MAP uses ──────────────────────── -const strategies = {} -for (const f of readdirSync(COLORS)) { - if (!f.endsWith('.js') || f.startsWith('_') || f.startsWith('.')) continue - const src = readFileSync(join(COLORS, f), 'utf8') - const hexes = paletteHexes(src) - if (hexes && hexes.length) strategies[f.replace(/\.js$/, '')] = hexes -} +// The library set, and per bundle its own src/colors/ strategies merged over it (same key overrides). +const strategies = collectStrategies(COLORS) +const privateOf = (bundle) => collectStrategies(join(ROOT, 'plugin-designs', bundle, 'src', 'colors')) // ── the consumers that must agree ───────────────────────────────────────────────────────────── const CONSUMERS = [ @@ -142,7 +164,7 @@ for (const c of CONSUMERS) { const p = join(BUILT, c.bundle) if (!existsSync(p)) { errs.push(`bundle missing: ${c.bundle} — build is incomplete, so this gate cannot judge it.`); continue } const js = readFileSync(p, 'utf8').toLowerCase() - for (const [strat, hexes] of Object.entries(strategies)) { + for (const [strat, hexes] of Object.entries({ ...strategies, ...privateOf(c.name) })) { const missing = hexes.filter((h) => !js.includes(h)) checkedPairs++ // A consumer need not use EVERY strategy, but if it uses one at all it must use the whole diff --git a/plugin-designs/rudder-voice/rudder-voice.vue b/plugin-designs/rudder-voice/rudder-voice.vue index a0cb2087d..8e6246fa7 100644 --- a/plugin-designs/rudder-voice/rudder-voice.vue +++ b/plugin-designs/rudder-voice/rudder-voice.vue @@ -707,8 +707,10 @@ const statusText = computed(() => ({ the engine runs on. Open the Plugin Studio on your computer (bun run dev) or use the Rudder desktop app.

- The local speech engine isn't reachable at {{ apiBase }}. In the Plugin Studio it starts with - bun run dev once built: cd backend/rudder-engine && go build -o ../bin/rudder-engine . + The local speech engine isn't reachable at {{ apiBase }}. It needs Go 1.22 or newer and a + one-time setup that downloads the speech model (about 750 MB): bash backend/rudder-engine/setup-runtime.sh. + Then bun run dev starts it here. For the desktop app, bun run desktop:build rudder-voice; + both are described in documentation/HOWTO-build-the-desktop-app.md.