diff --git a/internal/backend-dev-plugin.mjs b/internal/backend-dev-plugin.mjs index 80f4e9a4..f69bdc12 100644 --- a/internal/backend-dev-plugin.mjs +++ b/internal/backend-dev-plugin.mjs @@ -33,6 +33,9 @@ export const DEV_BACKENDS = [ // This server is SUPPOSED to run the engine (a server deployment with the engine switched on): while it // is down, the plugin says "not running yet" instead of "runs on your own computer". expected: () => process.env.RUDDER_ENGINE === '1' || process.env.RUDDER_AUDIO === 'browser', + // RUDDER_ENGINE=0 (a server where Rudder is laptop-only, the agent 2026-09-27): never start it, even if a + // binary built by an earlier engine-on deploy is still there — it would offer to record a server's mic. + disabled: () => process.env.RUDDER_ENGINE === '0', }, ] @@ -117,6 +120,10 @@ export function backendDevPlugin(root) { logger.info(`[${b.name}] still running → ${b.route} (kept across the dev-server restart)`) continue } + if (b.disabled && b.disabled()) { + logger.info(`[backend] ${b.name} switched off here (${b.name === 'rudder-engine' ? 'RUDDER_ENGINE=0' : 'disabled'})`) + continue + } if (!existsSync(resolve(root, b.bin))) { logger.warn(`[backend] ${b.name} not built — its plugin will show "engine offline". Build: ${b.build}`) continue diff --git a/internal/check-square-map-controls.mjs b/internal/check-square-map-controls.mjs index 9a904e1d..6500b4b1 100644 --- a/internal/check-square-map-controls.mjs +++ b/internal/check-square-map-controls.mjs @@ -115,6 +115,18 @@ ok(/:only-child[^{]*\{[^}]*border-radius:\s*4px/.test(TOOLBAR), 'and a lone cont ok(sqGap > -1 && sqGap < mq, 'CONTROL: the base square rule really does come BEFORE the media block, which is why the override is needed at all') } +// ── 2c. NO CHILD BRINGS ITS OWN OUTLINE ────────────────────────────────────────── +// ShareButton is not a .mcb -- it wraps its button in .share-wrap with border-radius:50% and a +// conic-gradient ring on ::before -- so the button rules missed it and it kept a round rainbow halo +// inside the square panel. Found by rega at 390, not by me. +ok(/\.map-toolbar--square \.share-wrap \{[^}]*border-radius:\s*0/.test(TOOLBAR), + 'the square panel flattens .share-wrap, which is a div and not a .mcb') +ok(/\.map-toolbar--square \.share-wrap::before \{[^}]*display:\s*none/.test(TOOLBAR), + "and suppresses its conic-gradient ring -- in this variant the PANEL owns the outline") +ok(/\.share-wrap\{[^}]*border-radius:50%/.test(readFileSync(join(REPO, 'library/components/map-controls/ShareButton.vue'), 'utf8').replace(/\s+/g, '')) || + /border-radius:\s*50%/.test(readFileSync(join(REPO, 'library/components/map-controls/ShareButton.vue'), 'utf8')), + 'CONTROL: ShareButton really does carry a 50% radius of its own, so the two rules above are not decorative') + // ── 3. forced colours, which the round buttons never had ──────────────────────────────────────── ok(/@media \(forced-colors: active\)/.test(TOOLBAR), 'the square variant ships a forced-colors block') ok(/ButtonText/.test(TOOLBAR), 'using ButtonText, so the ring and dividers survive a replaced palette') diff --git a/library/components/map-controls/MapToolbar.vue b/library/components/map-controls/MapToolbar.vue index bd2d23fb..37c7fa1d 100644 --- a/library/components/map-controls/MapToolbar.vue +++ b/library/components/map-controls/MapToolbar.vue @@ -98,6 +98,14 @@ useShadowStyles(` .map-toolbar--square > * + * { border-top: 1px solid #ddd; } /* .hbg-ctrl's 10px top margin would tear the panel apart; inside the group it has no business. */ .map-toolbar--square .hbg-ctrl { margin: 0; } +/* ★ ShareButton IS NOT A .mcb, so the rules above missed it. It wraps its button in a .share-wrap div + carrying border-radius:50% plus a conic-gradient ring on ::before at inset:-2px -- which inside the + panel reads as a circular rainbow halo poking out of one corner, breaking the single-card illusion + at exactly the place the eye lands. rega measured it at 390 and asked whether square should flatten + it; it should: in this variant the PANEL owns the outline, so a child may not bring its own. + The round default keeps its ring untouched. */ +.map-toolbar--square .share-wrap { border-radius: 0; } +.map-toolbar--square .share-wrap::before { display: none; } /* ★ OUTER CORNERS ONLY, AND NO overflow:hidden. Clipping would have been the quick way to round the panel, but it also clips a focus ring on any inner button -- and the engine does not do it: it rounds the first and last buttons instead (button:focus:first-child -> 4px 4px 0 0, :last-child diff --git a/plugin-designs/rudder-voice/rudder-voice.vue b/plugin-designs/rudder-voice/rudder-voice.vue index 1d477ab9..69cb5735 100644 --- a/plugin-designs/rudder-voice/rudder-voice.vue +++ b/plugin-designs/rudder-voice/rudder-voice.vue @@ -382,6 +382,9 @@ const audioMode = ref('local') // The server is set up to run the engine (it reports expected=true while the engine is down): an absent // engine there means "starting or failed to set up", not "this only runs on your computer". const engineExpected = ref(false) +// An engine that records ITS OWN machine's microphone, reached from another machine: it would record the +// server, not this reader, so the page treats it as laptop-only. +const remoteLocalMic = computed(() => audioMode.value === 'local' && !onOwnComputer.value && engine.value === 'ready') let sock = null, media = null, actx = null async function startBrowser() { @@ -586,7 +589,7 @@ watch([draft, recording], queueActivity) const statusText = computed(() => ({ connecting: 'Connecting…', starting: 'Loading speech model… (about 20 s the first time)', - ready: recording.value ? 'Listening' : 'Ready', + ready: remoteLocalMic.value ? 'Runs on your computer' : recording.value ? 'Listening' : 'Ready', error: 'Engine error', offline: onOwnComputer.value ? 'Engine offline' : engineExpected.value ? 'Not running yet' : 'Runs on your computer', }[engine.value] || engine.value)) @@ -647,7 +650,7 @@ const statusText = computed(() => ({