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.