Key editor: the question heads a group of checks once; each row names the thing; 'Words it can show'

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 18:52:02 -04:00
co-authored by Claude Fable 5.1
parent c4820800b4
commit 095f20b7a6
2 changed files with 10 additions and 5 deletions
+8 -5
View File
@@ -413,12 +413,15 @@
const kt = k.tiles.find((x) => x.id === t.id) || { says: [], holding: "", checks: [] };
const fixOptions = (cur) => `<option value="">none</option><option value="restart-connection" ${cur === "restart-connection" ? "selected" : ""}>reconnect its Companion connection</option><option value="dante-auto" ${cur === "dante-auto" ? "selected" : ""}>Dante auto (PoE port or the Dante module)</option>${Object.entries(c.fixes).map(([id, f]) => `<option value="${esc(id)}" ${cur === id ? "selected" : ""}>${esc(f.short || id)} – ${esc(f.label || "")}</option>`).join("")}`;
const short = (text, n) => { text = String(text || ""); return text.length > n ? text.slice(0, n - 1) + "…" : text; };
let lastQ = null;
const checks = (t.checks || []).map((chk, j) => {
const spec = state.schema.checks[chk.type] || { fields: [], question: "" };
const sentence = (kt.checks[j] || {}).sentence || chk.type;
const what = chk.conn || chk.peer || chk.name || chk.msg || (chk.params || {}).process || chk.host || chk.label || "";
const summary = `<summary><b>${esc(spec.question || chk.type)}</b> <span class="muted">${esc(short(what || sentence, 40))}</span>${hint(sentence)}</summary>`;
if (!editing()) return `<details class="chk">${summary}<p class="small">${esc(sentence)}</p></details>`;
const what = chk.type === "midi_peer" ? `${chk.peer || "?"} <span class="muted">in ${esc(chk.session || "?")}</span>` : esc(chk.conn || chk.name || (chk.params || {}).process || chk.msg || chk.host || chk.label || chk.url || chk.session || short(sentence, 40));
const q = spec.question || chk.type;
const header = q !== lastQ ? `<div class="chkgroup">${esc(q)}</div>` : ""; lastQ = q;
const summary = `<summary><b>${what}</b>${hint(sentence)}</summary>`;
if (!editing()) return `${header}<details class="chk">${summary}<p class="small">${esc(sentence)}</p></details>`;
const base = `tiles.${i}.checks.${j}.`;
let fields = (spec.fields || []).filter((f) => !["fix", "type", "template", "params"].includes(f)).map((f) => inputHtml(base + f, F("tiles[].checks[]." + f), chk[f])).join("");
if (chk.type === "remote") {
@@ -430,7 +433,7 @@
const fixSel = typeof chk.fix === "object" && chk.fix ? `<div class="field"><span class="fl">fix per device${hint('A table: device → fix, "*" for everything else')}</span><textarea data-path="${base}fix" rows="2" class="fixtable mono">${esc(JSON.stringify(chk.fix))}</textarea></div>`
: `<div class="field"><span class="fl">fix${hint("What holding the key runs for this check")}</span><select data-path="${base}fix" class="fixsel">${fixOptions(chk.fix)}</select></div>`;
const run = typeof chk.fix === "string" && chk.fix && canRunNow(chk.fix) ? `<button class="small" data-run="${esc(t.id)}|${esc(chk.fix)}|${j}" data-hint="Through the watchdog, exactly like a hold">Run the fix</button>` : "";
return `<details class="chk">${summary}<div class="chkbody">${fields}${fixSel}
return `${header}<details class="chk">${summary}<div class="chkbody">${fields}${fixSel}
<details><summary>more</summary>${["tag", "service_only", "debounce_seconds"].filter((f) => !(spec.fields || []).includes(f)).map((f) => inputHtml(base + f, F("tiles[].checks[]." + f), chk[f])).join("")}<p class="mono small">type ${esc(chk.type)}</p></details>
<div class="row">${run}<button class="small danger" data-removecheck="${j}">Remove</button></div></div></details>`;
}).join("");
@@ -444,7 +447,7 @@
return `<div class="card keycard"><h2 style="margin:4px 0 8px">${esc(t.label)} <span class="muted small">${t.pos ? `row ${t.pos[0]}, col ${t.pos[1]}` : "in a group"}</span></h2>
${label}${members}${holdAlways}
${holdList.length ? `<details class="fold"><summary>Hold ${esc(hold)}: ${esc(short(holdList[0], 48))}${holdList.length > 1 ? ` <span class="muted">+${holdList.length - 1}</span>` : ""}${t.hold_always ? " <span class='pill'>also when green</span>" : ""}</summary><ol class="small">${holdList.map((h) => `<li>${esc(h)}</li>`).join("")}</ol></details>` : ""}
${kt.says && kt.says.length ? `<details class="fold"><summary>Can say <span class="muted">${kt.says.length} things</span></summary><p class="mono small">${esc(kt.says.join(" · "))}</p></details>` : ""}
${kt.says && kt.says.length ? `<details class="fold"><summary>Words it can show <span class="muted">${kt.says.length}</span></summary><p class="mono small">${esc(kt.says.join(" · "))}</p></details>` : ""}
${checks ? `<div class="checks">${checks}</div>` : ""}${addMenu}${more}</div>`;
}
function groupBy(obj, fn) { const out = {}; for (const [k, v] of Object.entries(obj)) (out[fn(v)] = out[fn(v)] || []).push(k); return out; }