perf(ui): hoist esc() lookup table and build option lists once (#160)

Hoist the HTML-escape lookup table in static/js/ui.js out of the
String.replace callback so it is allocated once instead of on every
matched character. esc() is the canonical escaper aliased across 27
modules and runs on essentially every render, so this removes a lot of
short-lived garbage on the hottest text path. Output is byte-identical
(verified across null/undefined/emoji/attribute edge cases).

Also build the <select> option lists in cookbook-hwfit.js and group.js
by accumulating a string and assigning innerHTML once, instead of
`innerHTML +=` inside a forEach (which makes the browser re-parse the
element's markup on every iteration). Final DOM is unchanged.

Pure micro-optimizations; no behavior change.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
chrisdvz.io
2026-06-01 07:09:33 +03:00
committed by GitHub
parent 91d3511580
commit ff81a22285
3 changed files with 10 additions and 7 deletions

View File

@@ -1094,12 +1094,13 @@ export function _hwfitInit() {
for (const sel of selectors) {
if (!sel) continue;
const currentVal = sel.value;
sel.innerHTML = `<option value="local">Local</option>`;
let html = `<option value="local">Local</option>`;
_envState.servers.forEach((s, i) => {
if (!s.host) return;
const label = s.name || s.host || `Server ${i + 1}`;
sel.innerHTML += `<option value="${i}">${uiModule.esc(label)}</option>`;
html += `<option value="${i}">${uiModule.esc(label)}</option>`;
});
sel.innerHTML = html;
sel.value = currentVal;
}
}

View File

@@ -487,10 +487,11 @@ export async function showModelPicker() {
`;
const sel = document.createElement('select');
sel.style.cssText = 'font-size:11px;padding:3px 6px;border-radius:4px;border:1px solid var(--border);background:var(--bg);color:var(--fg);max-width:140px;';
sel.innerHTML = '<option value="">No character</option>';
let optsHtml = '<option value="">No character</option>';
characters.forEach(c => {
sel.innerHTML += `<option value="${c.id}">${uiModule.esc(c.name)}</option>`;
optsHtml += `<option value="${c.id}">${uiModule.esc(c.name)}</option>`;
});
sel.innerHTML = optsHtml;
sel.addEventListener('change', () => {
if (sel.value) {
const ch = characters.find(c => c.id === sel.value);

View File

@@ -516,14 +516,15 @@ export function styledPrompt(message, {
});
}
// Lookup table for esc(); hoisted out of the replace callback so it is
// allocated once rather than per matched character.
const _ESC_MAP = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' };
/**
* HTML-escape a string to prevent XSS.
* Canonical implementation — other modules should use uiModule.esc() instead of local copies.
*/
export function esc(s) {
return (s || '').replace(/[&<>"']/g, function(m) {
return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m];
});
return (s || '').replace(/[&<>"']/g, (m) => _ESC_MAP[m]);
}
// ── Mobile: suppress synthetic click/mousedown on backdrop ──