limits-ui.ts (4286B)
1 import { defaultLimits, executionSettings, type ExecutionSettings, type Runner } from './execution.js'; 2 const committed = new Map<Runner, ExecutionSettings>(); 3 export function readSettings(runner: Runner): ExecutionSettings { 4 const settings = committed.get(runner); 5 if (!settings) throw new Error('Execution settings are not ready'); 6 return { limits: { ...settings.limits }, uncapped: settings.uncapped }; 7 } 8 for (const runner of ['package', 'compiler'] as const) { 9 const checkbox = document.getElementById(`${runner === 'package' ? '' : 'compiler-'}uncapped`) as HTMLInputElement; 10 const summary = document.getElementById(`${runner}-limits-summary`)!; 11 const container = document.getElementById(`${runner}-limits-defaults`)!; 12 const defaults = defaultLimits(runner), fields = new Map<string, HTMLInputElement>(); 13 const form = document.createElement('form'); form.noValidate = true; form.className = 'limits-editor'; 14 for (const [key, label] of [['nodes', 'Memory budget (live nodes)'], ['fuel', 'Work units (0 = no work limit)'], ['stack', 'Evaluation depth (frames)'], ['seconds', 'Active seconds (0 = unlimited)']] as const) { 15 const wrapper = document.createElement('div'), caption = document.createElement('label'), input = document.createElement('input'); 16 const hint = document.createElement('span'); hint.className = 'hint'; 17 input.id = `${runner}-limit-${key}`; input.type = 'number'; input.min = key === 'seconds' || key === 'fuel' ? '0' : '1'; input.step = '1'; 18 input.inputMode = 'numeric'; input.required = true; input.value = String(defaults[key]); 19 caption.htmlFor = input.id; caption.textContent = label; 20 hint.textContent = `Default: ${defaults[key].toLocaleString()}`; 21 wrapper.append(caption, input, hint); form.append(wrapper); fields.set(key, input); 22 } 23 const actions = document.createElement('div'); actions.className = 'controls'; 24 const apply = document.createElement('button'), restore = document.createElement('button'); 25 apply.type = 'submit'; apply.textContent = 'Apply limits'; restore.type = 'button'; restore.textContent = 'Restore defaults'; 26 const error = document.createElement('p'); error.className = 'limits-error'; error.setAttribute('role', 'status'); 27 actions.append(apply, restore); form.append(actions, error); container.replaceWith(form); 28 committed.set(runner, executionSettings(defaults, runner, true)); 29 function update(): void { 30 const settings = readSettings(runner); 31 const customized = Object.entries(defaults).some(([key, value]) => settings.limits[key as keyof typeof defaults] !== value); 32 summary.textContent = `Optional limits${settings.uncapped ? ' (off)' : customized ? ' (custom)' : ' (on)'}`; 33 for (const input of fields.values()) input.disabled = settings.uncapped; 34 apply.disabled = settings.uncapped; 35 document.getElementById(`${runner}-resource-mode`)!.textContent = settings.uncapped ? 'Off' : 'On'; 36 } 37 function publish(settings: ExecutionSettings): void { 38 committed.set(runner, settings); 39 for (const [key, input] of fields) input.value = String(settings.limits[key as keyof typeof settings.limits]); 40 error.textContent = ''; update(); 41 window.dispatchEvent(new CustomEvent('limits-change', { detail: runner })); 42 } 43 form.oninput = () => { error.textContent = ''; summary.textContent = 'Advanced: unapplied resource settings'; }; 44 form.onsubmit = event => { 45 event.preventDefault(); 46 try { 47 const values: Record<string, number> = {}; 48 for (const [key, field] of fields) { 49 if (!field.value.trim()) throw new Error(`${key}: enter a whole number`); 50 values[key] = Number(field.value); 51 } 52 publish(executionSettings(values, runner, checkbox.checked)); 53 } catch (cause) { error.textContent = cause instanceof Error ? cause.message : String(cause); } 54 }; 55 checkbox.onchange = () => { 56 const settings = readSettings(runner); settings.uncapped = checkbox.checked; 57 // Bypass uses the last applied finite values, not a partial/invalid draft. 58 publish(settings); 59 }; 60 restore.onclick = () => { 61 for (const [key, input] of fields) input.value = String(defaults[key as keyof typeof defaults]); 62 checkbox.checked = true; publish(executionSettings(defaults, runner, true)); 63 }; 64 update(); 65 }