Requirement 001 phase 6, S0: - Speech: the security class shows as "Sécurité" and is spoken as in maritime radio, "say-cure-ee-tay" three times. A hex run of 7 or more characters with a letter and a digit (commit, session, message ids) is spelled in the NATO alphabet; every other digit is spoken on its own in ICAO pronunciation (42305: four two tree zero fife). The spelling runs inside clip() before the length cut, so a phrase still fits SPEECH_LIMIT; the displayed text keeps its digits and hashes. - English: every orchestrator label, tooltip, message, error, script output, document and test string is English (Regeln -> Firewall, Hierarchie -> Hierarchy, Steuerung -> Controls, Ausführen -> Run, ...); the page declares lang="en" and formats dates as en-GB. Task filter ids are English. German stays only in the skill trigger phrases that quote the operator, and as identifiers: the view keys (view=) and the leitstand id. - tests/test_english.py scans every tracked text file for umlauts and German words, with an allowlist of the quoted trigger phrases. BREAKING CHANGE: the Stream search tokens art: and seit: are now kind: and since:; an old art: or seit: token counts as a plain word. Evidence: make test 657 OK, ruff and markdownlint clean (/tmp/s0-make-test-1.log); e2e 17 OK, none skipped (/tmp/s0-e2e-1.log); MEDUSA 86 files, 24 findings on reworded strings accepted after an independent skeptic (21 path:line, 0 rejected, no behaviour change in signing, approving, rules, denying, offices, events), rescan 0 open. 🎯 Fix: 93 % 💥 Break: 4 % 💸 Cost: 100 % ⏱️ Time: – 🛡️ Security: 100 % 🏆 Score: 89 🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 % Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
103 lines
4.2 KiB
JavaScript
103 lines
4.2 KiB
JavaScript
const orderDom = {box: null, dragged: null, focus: null};
|
|
|
|
function openAgentOrder() {
|
|
go("agents", "hierarchy");
|
|
window.setTimeout(() => {
|
|
const title = document.getElementById("agent-order-title");
|
|
if (title) {
|
|
title.scrollIntoView({block: "nearest"});
|
|
title.focus();
|
|
}
|
|
}, 50);
|
|
}
|
|
|
|
function syncOrder() {
|
|
const stored = state.agent_order;
|
|
if (ui.order === null || ui.order.length !== stored.length || ui.order.some((name) => !stored.includes(name))) {
|
|
ui.order = [...stored];
|
|
}
|
|
}
|
|
|
|
function placeAgent(next, focus) {
|
|
ui.order = next;
|
|
orderDom.focus = focus;
|
|
drawAgentOrder();
|
|
}
|
|
|
|
function moveAgent(name, step) {
|
|
const from = ui.order.indexOf(name);
|
|
const to = from + step;
|
|
if (from < 0 || to < 0 || to >= ui.order.length) {
|
|
return;
|
|
}
|
|
const next = ui.order.filter((entry) => entry !== name);
|
|
next.splice(to, 0, name);
|
|
placeAgent(next, `${step < 0 ? "up" : "down"}:${name}`);
|
|
}
|
|
|
|
function dropAgent(name) {
|
|
const dragged = orderDom.dragged;
|
|
orderDom.dragged = null;
|
|
if (dragged === null || dragged === name) {
|
|
return;
|
|
}
|
|
const next = ui.order.filter((entry) => entry !== dragged);
|
|
next.splice(next.indexOf(name) + (ui.order.indexOf(dragged) < ui.order.indexOf(name) ? 1 : 0), 0, dragged);
|
|
placeAgent(next, null);
|
|
}
|
|
|
|
function orderItem(name, index) {
|
|
const up = officeBtn("bi-arrow-up", `Move ${name} up`, `Move ${name} up: higher priority`, () => moveAgent(name, -1), `up:${name}`);
|
|
const down = officeBtn("bi-arrow-down", `Move ${name} down`, `Move ${name} down: lower priority`, () => moveAgent(name, 1), `down:${name}`);
|
|
up.disabled = index === 0;
|
|
down.disabled = index === ui.order.length - 1;
|
|
const item = h("li", {draggable: "true", "data-name": name}, icon("bi-grip-vertical", "grip"), h("span", {class: "rank"}, `${index + 1}.`), face(name, false), person(name), h("span", {class: "spacer"}), up, down);
|
|
item.addEventListener("dragstart", (event) => {
|
|
orderDom.dragged = name;
|
|
item.classList.add("dragging");
|
|
event.dataTransfer.effectAllowed = "move";
|
|
event.dataTransfer.setData("text/plain", name);
|
|
});
|
|
item.addEventListener("dragend", () => item.classList.remove("dragging"));
|
|
item.addEventListener("dragover", (event) => event.preventDefault());
|
|
item.addEventListener("drop", (event) => {
|
|
event.preventDefault();
|
|
dropAgent(name);
|
|
});
|
|
return item;
|
|
}
|
|
|
|
async function saveAgentOrder() {
|
|
if (await act("order", "Priority", {agents: ui.order})) {
|
|
ui.order = null;
|
|
drawAgentOrder();
|
|
}
|
|
}
|
|
|
|
function drawAgentOrder() {
|
|
const box = orderDom.box;
|
|
if (!box || !box.isConnected) {
|
|
orderDom.box = null;
|
|
return;
|
|
}
|
|
syncOrder();
|
|
const key = orderDom.focus || focusKey(box);
|
|
orderDom.focus = null;
|
|
const changed = JSON.stringify(ui.order) !== JSON.stringify(state.agent_order);
|
|
box.replaceChildren(
|
|
h("div", {class: "inbox-head"}, h("h2", {class: "group-title", id: "agent-order-title", tabindex: "-1", "data-focus": "order:title"}, "Agent priority", counter(ui.order.length))),
|
|
h("p", {class: "muted"}, "Orders the chair's grants and decides only between equal task priorities; the tasks' --prio pins stay unchanged."),
|
|
ui.order.length ? h("ol", {class: "order-list", "aria-label": "Agent order"}, ui.order.map(orderItem)) : h("p", {class: "muted"}, "No agent with an office or an open task."),
|
|
h("div", {class: "actions"},
|
|
h("span", {class: "muted"}, "Drag or use the arrows to reorder"),
|
|
h("span", {class: "spacer"}),
|
|
h("button", {type: "button", class: "btn ghost", "data-focus": "order:reset", disabled: !changed, on: {click: () => placeAgent([...state.agent_order], "order:reset")}}, "Reset"),
|
|
h("button", {type: "button", class: "btn primary", "data-focus": "order:save", disabled: !changed, on: {click: saveAgentOrder}}, "Save order")));
|
|
const target = key && [...box.querySelectorAll("[data-focus]")].find((element) => element.dataset.focus === key);
|
|
if (target && target.disabled) {
|
|
refocus(box, key.startsWith("up:") ? `down:${key.slice(3)}` : key.startsWith("down:") ? `up:${key.slice(5)}` : "order:title");
|
|
} else {
|
|
refocus(box, key);
|
|
}
|
|
}
|