limits-ui.js (4415B)
1 import { defaultLimits, executionSettings } from './execution.js'; 2 const committed = new Map(); 3 export function readSettings(runner) { 4 const settings = committed.get(runner); 5 if (!settings) 6 throw new Error('Execution settings are not ready'); 7 return { limits: { ...settings.limits }, uncapped: settings.uncapped }; 8 } 9 for (const runner of ['package', 'compiler']) { 10 const checkbox = document.getElementById(`${runner === 'package' ? '' : 'compiler-'}uncapped`); 11 const summary = document.getElementById(`${runner}-limits-summary`); 12 const container = document.getElementById(`${runner}-limits-defaults`); 13 const defaults = defaultLimits(runner), fields = new Map(); 14 const form = document.createElement('form'); 15 form.noValidate = true; 16 form.className = 'limits-editor'; 17 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)']]) { 18 const wrapper = document.createElement('div'), caption = document.createElement('label'), input = document.createElement('input'); 19 const hint = document.createElement('span'); 20 hint.className = 'hint'; 21 input.id = `${runner}-limit-${key}`; 22 input.type = 'number'; 23 input.min = key === 'seconds' || key === 'fuel' ? '0' : '1'; 24 input.step = '1'; 25 input.inputMode = 'numeric'; 26 input.required = true; 27 input.value = String(defaults[key]); 28 caption.htmlFor = input.id; 29 caption.textContent = label; 30 hint.textContent = `Default: ${defaults[key].toLocaleString()}`; 31 wrapper.append(caption, input, hint); 32 form.append(wrapper); 33 fields.set(key, input); 34 } 35 const actions = document.createElement('div'); 36 actions.className = 'controls'; 37 const apply = document.createElement('button'), restore = document.createElement('button'); 38 apply.type = 'submit'; 39 apply.textContent = 'Apply limits'; 40 restore.type = 'button'; 41 restore.textContent = 'Restore defaults'; 42 const error = document.createElement('p'); 43 error.className = 'limits-error'; 44 error.setAttribute('role', 'status'); 45 actions.append(apply, restore); 46 form.append(actions, error); 47 container.replaceWith(form); 48 committed.set(runner, executionSettings(defaults, runner, true)); 49 function update() { 50 const settings = readSettings(runner); 51 const customized = Object.entries(defaults).some(([key, value]) => settings.limits[key] !== value); 52 summary.textContent = `Optional limits${settings.uncapped ? ' (off)' : customized ? ' (custom)' : ' (on)'}`; 53 for (const input of fields.values()) 54 input.disabled = settings.uncapped; 55 apply.disabled = settings.uncapped; 56 document.getElementById(`${runner}-resource-mode`).textContent = settings.uncapped ? 'Off' : 'On'; 57 } 58 function publish(settings) { 59 committed.set(runner, settings); 60 for (const [key, input] of fields) 61 input.value = String(settings.limits[key]); 62 error.textContent = ''; 63 update(); 64 window.dispatchEvent(new CustomEvent('limits-change', { detail: runner })); 65 } 66 form.oninput = () => { error.textContent = ''; summary.textContent = 'Advanced: unapplied resource settings'; }; 67 form.onsubmit = event => { 68 event.preventDefault(); 69 try { 70 const values = {}; 71 for (const [key, field] of fields) { 72 if (!field.value.trim()) 73 throw new Error(`${key}: enter a whole number`); 74 values[key] = Number(field.value); 75 } 76 publish(executionSettings(values, runner, checkbox.checked)); 77 } 78 catch (cause) { 79 error.textContent = cause instanceof Error ? cause.message : String(cause); 80 } 81 }; 82 checkbox.onchange = () => { 83 const settings = readSettings(runner); 84 settings.uncapped = checkbox.checked; 85 // Bypass uses the last applied finite values, not a partial/invalid draft. 86 publish(settings); 87 }; 88 restore.onclick = () => { 89 for (const [key, input] of fields) 90 input.value = String(defaults[key]); 91 checkbox.checked = true; 92 publish(executionSettings(defaults, runner, true)); 93 }; 94 update(); 95 }