diff --git a/static/js/cookbook-hwfit.js b/static/js/cookbook-hwfit.js
index 3d438aa..6817d15 100644
--- a/static/js/cookbook-hwfit.js
+++ b/static/js/cookbook-hwfit.js
@@ -1094,12 +1094,13 @@ export function _hwfitInit() {
for (const sel of selectors) {
if (!sel) continue;
const currentVal = sel.value;
- sel.innerHTML = ``;
+ let html = ``;
_envState.servers.forEach((s, i) => {
if (!s.host) return;
const label = s.name || s.host || `Server ${i + 1}`;
- sel.innerHTML += ``;
+ html += ``;
});
+ sel.innerHTML = html;
sel.value = currentVal;
}
}
diff --git a/static/js/group.js b/static/js/group.js
index 4914d76..ed98872 100644
--- a/static/js/group.js
+++ b/static/js/group.js
@@ -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 = '';
+ let optsHtml = '';
characters.forEach(c => {
- sel.innerHTML += ``;
+ optsHtml += ``;
});
+ sel.innerHTML = optsHtml;
sel.addEventListener('change', () => {
if (sel.value) {
const ch = characters.find(c => c.id === sel.value);
diff --git a/static/js/ui.js b/static/js/ui.js
index 3142790..5af1d2c 100644
--- a/static/js/ui.js
+++ b/static/js/ui.js
@@ -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 = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
/**
* 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 {'&':'&','<':'<','>':'>','"':'"',"'":'''}[m];
- });
+ return (s || '').replace(/[&<>"']/g, (m) => _ESC_MAP[m]);
}
// ── Mobile: suppress synthetic click/mousedown on backdrop ──