The settings tab's settings an MCP may read and change in a listener's tab (get_settings and set_settings, shared-tools.json): shared by the Worker, which checks a change before it reaches a tab, and the tab (website/src/jev/tabTools.mjs), which checks it again against its own store (website/src/settings.mjs). Plain JavaScript without imports, like art-rubric.mjs, so the Worker, Vite and Node all load it.
Only how the editor and panel look and behave while typing. Never a setting that runs or routes anything, so none can weaken the sandbox or act as the listener: prebakeScript code the page evaluates before every song: in the page includePrebakeScriptInShare, userPatterns, latestCode the listener's own code isSyncEnabled shares the editor across tabs patternAutoStart, isBlockBasedEvalEnabled when and how code is evaluated audioEngineTarget osc sends every note to a local server audioDeviceName, maxPolyphony, multiChannelOrbits the audio device and engine A key not listed here is refused, so a setting added upstream stays out until someone decides it is harmless.
20const FLAG = { type: 'boolean' };
22export const SETTINGS = { 23 theme: { type: 'string', about: 'the editor theme (get_settings lists them)' }, 24 fontFamily: { 25 type: 'enum', 26 values: ['monospace', 'Courier', 'CutiePi', 'JetBrains', 'Hack', 'FiraCode', 'FiraCode-SemiBold', 'teletext', 'tic80', 'mode7', 'BigBlueTerminal', 'x3270', 'Monocraft', 'PressStart', 'we-come-in-peace', 'galactico'], 27 }, 28 fontSize: { type: 'number', min: 10, max: 40 }, 29 keybindings: { type: 'enum', values: ['codemirror', 'vim', 'emacs', 'helix', 'vscode'] }, 30 panelPosition: { type: 'enum', values: ['bottom', 'right'] }, 31 activeFooter: { type: 'enum', values: ['patterns', 'sounds', 'reference', 'export', 'console', 'settings'], about: "the right panel's open tab (Strudel's)" }, 32 isPanelOpen: FLAG, 33 jevTab: { type: 'enum', values: ['songs', 'activity', 'listening', 'you', 'mcp', 'data'], about: "the jev panel's open tab (everything jevstrudel adds, on the left)" }, 34 isJevPanelOpen: FLAG, 35 isZen: FLAG, 36 isBracketMatchingEnabled: FLAG, 37 isBracketClosingEnabled: FLAG, 38 isLineNumbersDisplayed: FLAG, 39 isActiveLineHighlighted: FLAG, 40 isPatternHighlightingEnabled: FLAG, 41 isAutoCompletionEnabled: FLAG, 42 isTooltipEnabled: FLAG, 43 isLineWrappingEnabled: FLAG, 44 isMinimapEnabled: FLAG, 45 isTabIndentationEnabled: FLAG, 46 isMultiCursorEnabled: FLAG, 47 isFlashEnabled: FLAG, 48 isButtonRowHidden: FLAG, 49 isCSSAnimationDisabled: FLAG, 50}; 51 52export const MAX_THEME_CHARS = 64; 53 54const isPlain = (x) => typeof x === 'object' && x !== null && !Array.isArray(x); 55const own = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
Why value is not a valid key, or null. themes, when given, is the
tab's own list of themes.
59export function whyNotSetting(key, value, themes) { 60 if (!own(SETTINGS, key)) return `${String(key).slice(0, 40)} is not a setting an MCP may change`; 61 const rule = SETTINGS[key]; 62 switch (rule.type) { 63 case 'boolean': 64 return typeof value === 'boolean' ? null : `${key} is true or false`; 65 case 'number': 66 return typeof value === 'number' && Number.isInteger(value) && value >= rule.min && value <= rule.max 67 ? null 68 : `${key} is a whole number from ${rule.min} to ${rule.max}`; 69 case 'enum': 70 return rule.values.includes(value) ? null : `${key} is one of ${rule.values.join(', ')}`; 71 default: 72 if (typeof value !== 'string' || !value || value.length > MAX_THEME_CHARS) return `${key} is a name`; 73 if (themes && !themes.includes(value)) return `no theme ${value}; get_settings lists them`; 74 return null; 75 } 76}
Why changes is not a set of changes, or null.
79export function whyNotChanges(changes, themes) { 80 if (!isPlain(changes)) return 'changes is an object of setting: value'; 81 const keys = Object.keys(changes); 82 if (!keys.length) return 'no changes'; 83 for (const key of keys) { 84 const why = whyNotSetting(key, changes[key], themes); 85 if (why) return why; 86 } 87 return null; 88}
The allowed settings out of a settings object, typed; anything else dropped.
91export function settingsView(all) { 92 const view = {}; 93 if (!isPlain(all)) return view; 94 for (const key of Object.keys(SETTINGS)) { 95 if (!own(all, key)) continue; 96 let value = all[key]; 97 const rule = SETTINGS[key]; 98 if (rule.type === 'boolean') value = value === true || value === 'true'; 99 else if (rule.type === 'number') value = Number(value); 100 if (whyNotSetting(key, value) === null) view[key] = value; 101 } 102 return view; 103}