var test = require("node:test");
var assert = require("node:assert/strict");
var fs = require("node:fs");
var path = require("node:path");
var pathToFileURL = require("node:url").pathToFileURL;
function FakeElement(tag) {
this.tagName = tag.toUpperCase();
this.children = [];
this.attributes = {};
this.listeners = {};
this.className = "";
this.textContent = "";
this.disabled = false;
this.dataset = {};
this.value = "";
this.tabIndex = 0;
var element = this;
this.classList = {
add: function () {
for (var i = 0; i < arguments.length; i++) if ((" " + element.className + " ").indexOf(" " + arguments[i] + " ") === -1) element.className += (element.className ? " " : "") + arguments[i];
},
remove: function () {},
toggle: function () {},
};
}
FakeElement.prototype.closest = function () { return null; };
FakeElement.prototype.appendChild = function (child) { this.children.push(child); child.parentNode = this; return child; };
FakeElement.prototype.insertBefore = function (child, before) { var index = this.children.indexOf(before); if (index === -1) return this.appendChild(child); this.children.splice(index, 0, child); child.parentNode = this; return child; };
FakeElement.prototype.setAttribute = function (name, value) { this.attributes[name] = String(value); };
FakeElement.prototype.addEventListener = function (name, handler) { this.listeners[name] = handler; };
FakeElement.prototype.click = function () { if (this.listeners.click) this.listeners.click({ currentTarget: this }); };
FakeElement.prototype.focus = function (options) { this.focusOptions = options; };
FakeElement.prototype.querySelector = function (selector) {
var className = selector.charAt(0) === "." ? selector.slice(1) : null;
var role = selector.match(/^\[role="([^"]+)"\]$/);
var nodes = flatten(this);
for (var i = 1; i < nodes.length; i++) {
if (className && (" " + nodes[i].className + " ").indexOf(" " + className + " ") !== -1) return nodes[i];
if (role && nodes[i].attributes.role === role[1]) return nodes[i];
}
return null;
};
FakeElement.prototype.querySelectorAll = function (selector) {
var className = selector.charAt(0) === "." ? selector.slice(1) : null;
if (!className) return [];
return flatten(this).filter(function (node) { return (" " + node.className + " ").indexOf(" " + className + " ") !== -1; });
};
function flatten(root) {
var result = [root];
for (var i = 0; i < root.children.length; i++) result = result.concat(flatten(root.children[i]));
return result;
}
test("Home debate proposal card is safe, accessible, keyboard-native, and server-confirmed", async function () {
var originals = { document: global.document, requestAnimationFrame: global.requestAnimationFrame, lucide: global.lucide };
global.document = {
createElement: function (tag) { return new FakeElement(tag); },
getElementById: function () { return null; },
querySelector: function () { return null; },
querySelectorAll: function () { return []; },
addEventListener: function () {},
};
global.requestAnimationFrame = function () { return 1; };
global.lucide = { createIcons: function () {} };
try {
var storeModule = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/store.js")).href);
storeModule.createStore({ cachedMatesList: [{ id: "panel-1", name: "Analyst" }] });
var module = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-planning.js")).href);
var calls = [];
var message = { role: "proposal", status: "pending", proposal: { proposalId: "dp-1", topic: "
", panelists: [{ mateId: "panel-1", role: "Skeptic" }] } };
var card = module.createHomeDebateProposalCard(message, function (selected, action, opener) { calls.push([selected, action, opener]); });
var nodes = flatten(card);
var start = nodes.find(function (node) { return node.attributes["aria-label"] === "Approve and start this debate"; });
var cancel = nodes.find(function (node) { return node.attributes["aria-label"] === "Cancel this debate proposal"; });
assert.ok(start);
assert.ok(cancel);
assert.equal(card.attributes["aria-label"], "Debate proposal:
");
assert.equal(nodes.some(function (node) { return Object.prototype.hasOwnProperty.call(node, "innerHTML") && node.innerHTML; }), false);
start.click();
assert.deepEqual(calls[0], [message, "start", start]);
module.markHomeDebateProposalSubmitting(message, start);
assert.equal(message.status, "submitting");
assert.equal(start.disabled, true);
assert.equal(cancel.disabled, true);
assert.equal(start.textContent, "Starting…");
var resolved = module.resolveHomeDebateProposal([message], { proposalId: "dp-1", action: "start" });
var resolvedCard = module.createHomeDebateProposalCard(resolved[0], function () {});
var status = flatten(resolvedCard).find(function (node) { return node.attributes.role === "status"; });
assert.equal(status.textContent, "Debate started");
} finally {
global.document = originals.document;
global.requestAnimationFrame = originals.requestAnimationFrame;
global.lucide = originals.lucide;
}
});
test("Home debate proposal selects per-participant models and submits debate-only overrides", async function () {
var originals = { document: global.document, requestAnimationFrame: global.requestAnimationFrame, lucide: global.lucide };
global.document = {
createElement: function (tag) { return new FakeElement(tag); },
getElementById: function () { return null; }, querySelector: function () { return null; }, querySelectorAll: function () { return []; }, addEventListener: function () {},
};
global.requestAnimationFrame = function () { return 1; };
global.lucide = { createIcons: function () {} };
try {
var storeModule = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/store.js")).href);
storeModule.createStore({ cachedMatesList: [{ id: "moderator", name: "Clay" }, { id: "panel", name: "Architect" }] });
var module = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-planning.js")).href);
var calls = [];
var proposal = {
proposalId: "dp-models", topic: "Architecture", panelists: [{ mateId: "panel", role: "Architect" }],
modelSelections: [
{ mateId: "moderator", role: "moderator", selectedModel: "sonnet", models: [{ value: "sonnet", label: "Sonnet" }, { value: "opus", label: "Opus" }] },
{ mateId: "panel", role: "panelist", selectedModel: "gpt-5.6", models: [{ value: "gpt-5.6", label: "GPT-5.6" }] },
],
};
var message = { role: "proposal", status: "pending", proposal: proposal };
var card = module.createHomeDebateProposalCard(message, function () { calls.push(Array.prototype.slice.call(arguments)); });
var selects = card.querySelectorAll(".home-debate-model-select");
assert.equal(selects.length, 2);
assert.equal(selects[0].attributes["aria-label"], "Model for Clay");
selects[0].value = "opus";
var start = flatten(card).find(function (node) { return node.attributes["aria-label"] === "Approve and start this debate"; });
start.click();
assert.deepEqual(calls[0][4], [{ mateId: "moderator", model: "opus" }, { mateId: "panel", model: "gpt-5.6" }]);
module.markHomeDebateProposalSubmitting(message, start);
assert.equal(selects[0].disabled, true);
} finally {
global.document = originals.document;
global.requestAnimationFrame = originals.requestAnimationFrame;
global.lucide = originals.lucide;
}
});
test("Home transcript proposal state rejects duplicates and restores cancel status", async function () {
var module = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-planning.js")).href);
var proposal = { proposalId: "dp-restore", topic: "Restored" };
var messages = module.normalizeHomeTranscript([{ role: "assistant", text: "Ready" }, { role: "proposal", proposal: proposal, status: "cancelled" }]);
messages = module.applyHomeDebateProposal(messages, { proposal: proposal });
assert.equal(messages.length, 2);
assert.equal(messages[1].status, "cancelled");
});
test("Home AskUserQuestion card safely submits options or Other and restores persisted state", async function () {
var originalDocument = global.document;
global.document = { createElement: function (tag) { return new FakeElement(tag); } };
try {
var module = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-planning.js")).href);
var calls = [];
var message = { role: "question", toolId: "tool-", status: "pending", questions: [{ header: "Direction", question: "Which outcome? ", options: [{ label: "Decision", description: "Choose one path" }, { label: "Trade-offs", description: "Compare costs" }] }] };
var card = module.createHomeDebateQuestionCard(message, function (selected, action, opener, answers) { calls.push({ selected: selected, action: action, opener: opener, answers: answers }); });
var nodes = flatten(card);
var option = nodes.find(function (node) { return node.className === "home-debate-question-option"; });
var submit = nodes.find(function (node) { return node.className === "home-debate-question-submit"; });
assert.equal(card.dataset.toolId, "tool-");
assert.equal(nodes.some(function (node) { return Object.prototype.hasOwnProperty.call(node, "innerHTML") && node.innerHTML; }), false);
option.click();
assert.equal(option.attributes["aria-pressed"], "true");
submit.click();
assert.deepEqual(calls[0].answers, { 0: "Decision" });
var restored = module.normalizeHomeTranscript([{ role: "question", toolId: "tool-1", questions: message.questions, status: "pending" }]);
assert.equal(module.hasPendingHomeDebateQuestion(restored), true);
restored = module.resolveHomeDebateQuestion(restored, { toolId: "tool-1", status: "expired", error: "Expired" });
assert.equal(restored[0].status, "expired");
assert.equal(module.hasPendingHomeDebateQuestion(restored), false);
} finally {
global.document = originalDocument;
}
});
test("Home freeform debate question renders one labeled textarea without options or Other", async function () {
var originalDocument = global.document;
global.document = { createElement: function (tag) { return new FakeElement(tag); } };
try {
var module = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-planning.js")).href);
var calls = [];
var message = { role: "question", toolId: "topic-freeform", status: "pending", questions: [{ header: "Topic", question: "What would you like the debate to be about?", options: [] }] };
var card = module.createHomeDebateQuestionCard(message, function (selected, action, opener, answers) { calls.push(answers); });
var nodes = flatten(card);
var textarea = nodes.find(function (node) { return node.tagName === "TEXTAREA"; });
var submit = nodes.find(function (node) { return node.className === "home-debate-question-submit"; });
assert.ok(textarea);
assert.equal(submit.disabled, true);
assert.equal(nodes.some(function (node) { return node.className === "home-debate-question-option"; }), false);
assert.equal(nodes.some(function (node) { return node.textContent === "Other"; }), false);
assert.equal(nodes.some(function (node) { return node.textContent === "Your answer"; }), true);
textarea.value = "도시 주거 정책";
textarea.listeners.input();
assert.equal(submit.disabled, false);
var prevented = false;
textarea.listeners.keydown({ key: "Enter", ctrlKey: true, metaKey: false, preventDefault: function () { prevented = true; } });
assert.equal(prevented, true);
assert.deepEqual(calls, [{ 0: "도시 주거 정책" }]);
} finally {
global.document = originalDocument;
}
});
test("answered debate question keeps an ephemeral preparing state until the next correlated event", async function () {
var originalDocument = global.document;
global.document = { createElement: function (tag) { return new FakeElement(tag); } };
try {
var module = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-planning.js")).href);
module.clearHomeDebatePlanningPending();
var changes = 0;
var message = { role: "question", toolId: "pending-next", status: "pending", questions: [{ question: "Topic?", options: [] }] };
var responder = module.createHomeDebateResponder(function () { return true; }, function () { return {}; }, function () { changes++; });
var card = module.createHomeDebateQuestionCard(message, responder);
var nodes = flatten(card);
var input = nodes.find(function (node) { return node.tagName === "TEXTAREA"; });
var submit = nodes.find(function (node) { return node.className === "home-debate-question-submit"; });
input.value = "Housing";
input.listeners.input();
submit.click();
assert.equal(message.status, "submitting");
assert.equal(module.isHomeDebatePlanningPending(), true);
assert.equal(changes, 1);
var row = module.createHomeDebatePlanningPendingRow();
var activityNodes = flatten(row);
assert.equal(row.attributes.role, "status");
assert.equal(row.attributes["aria-live"], "polite");
assert.equal(row.attributes["aria-atomic"], "true");
assert.equal(row.attributes["aria-label"], "Clay is preparing the next question");
assert.match(row.className, /home-debate-activity-next/);
assert.equal(activityNodes.some(function (node) { return node.textContent === "Clay"; }), true);
assert.equal(activityNodes.some(function (node) { return node.textContent === "Preparing the next question"; }), true);
assert.equal(activityNodes.filter(function (node) { return node.tagName === "I"; }).length, 3);
assert.equal(module.createHomeDebatePlanningPendingRow().attributes["aria-live"], "off");
module.syncHomeDebatePlanningPending({ debatePhase: "planning", isProcessing: true, messages: [{ role: "question", status: "answered" }] });
assert.equal(module.isHomeDebatePlanningPending(), true);
module.syncHomeDebatePlanningPending({ debatePhase: "planning", isProcessing: false, messages: [{ role: "question", status: "answered" }] });
assert.equal(module.isHomeDebatePlanningPending(), true);
module.clearHomeDebatePlanningPending();
assert.equal(module.isHomeDebatePlanningPending(), false);
} finally { global.document = originalDocument; }
});
test("Home live debate reducer renders multiple speaker identities without transcript controls", async function () {
var originalDocument = global.document;
var originalWindow = global.window;
var storageKey = "local" + "Storage";
var originalStorage = global[storageKey];
var originalMarked = global.marked;
var originalMermaid = global.mermaid;
var originalPurifier = global.DOMPurify;
var originalNodeFilter = global.NodeFilter;
global.document = {
createElement: function (tag) { return new FakeElement(tag); },
getElementById: function () { return null; },
querySelector: function () { return null; },
querySelectorAll: function () { return []; },
createTreeWalker: function () { return { nextNode: function () { return null; } }; },
addEventListener: function () {},
};
global.window = { addEventListener: function () {}, matchMedia: function () { return { matches: false }; } };
global[storageKey] = { getItem: function () { return null; }, setItem: function () {} };
global.marked = { use: function () {}, parse: function (value) { return value; } };
global.mermaid = { initialize: function () {} };
global.DOMPurify = { sanitize: function (value) { return value; } };
global.NodeFilter = { SHOW_TEXT: 4 };
try {
var storeModule = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/store.js")).href);
storeModule.store.set({ cachedMatesList: [], cachedAllUsers: [{ id: "user-a", displayName: "Ari", avatarSeed: "ari" }], myUserId: "user-a" });
var module = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-live.js")).href);
var messages = [];
messages = module.applyHomeDebateEvent(messages, { eventType: "debate_started", topic: "Housing", moderatorId: "builtin:clay", moderatorName: "Clay", panelists: [{ mateId: "panel-1", name: "Panel", role: "Analyst" }] });
messages = module.applyHomeDebateEvent(messages, { eventType: "debate_turn", turnId: "d:1", speakerMateId: "builtin:clay", mateName: "Clay", role: "moderator", round: 1, avatarStyle: "imprint", avatarSeed: "clay-seed", avatarCustom: "data:image/svg+xml,clay-exact" });
messages = module.applyHomeDebateEvent(messages, { eventType: "debate_activity", turnId: "d:1", activity: "Thinking" });
messages = module.applyHomeDebateEvent(messages, { eventType: "debate_stream", turnId: "d:1", delta: "Hello " });
messages = module.applyHomeDebateEvent(messages, { eventType: "debate_turn_done", turnId: "d:1", text: "Hello panel" });
messages = module.applyHomeDebateEvent(messages, { eventType: "debate_stop_requested" });
assert.equal(messages[0].stopping, true);
messages = module.applyHomeDebateEvent(messages, { eventType: "debate_stop_cancelled" });
assert.equal(messages[0].stopping, false);
messages = module.applyHomeDebateEvent(messages, { eventType: "debate_tool_decision", decisionId: "tool-safe", speakerMateId: "panel-1", mateName: "Panel", toolName: "Bash", action: "Run read-only shell commands (rg)", command: "rg -n housing lib", decision: "allowed", reason: "Read-only investigation" });
messages = module.applyHomeDebateEvent(messages, { eventType: "debate_tool_decision", decisionId: "tool-safe", speakerMateId: "panel-1", mateName: "Panel", toolName: "Bash", action: "Run read-only shell commands (rg)", decision: "allowed", reason: "Read-only investigation" });
messages = module.applyHomeDebateEvent(messages, { eventType: "debate_tool_decision", decisionId: "tool-safe-2", speakerMateId: "panel-1", mateName: "Panel", toolName: "Bash", action: "Run read-only shell commands (rg)", command: "rg -n housing test", decision: "allowed", reason: "Read-only investigation" });
messages = module.applyHomeDebateEvent(messages, { eventType: "debate_tool_decision", decisionId: "tool-blocked", speakerMateId: "panel-1", mateName: "Panel", toolName: "Edit", action: "Edit a project file", decision: "blocked", reason: "Would modify project files" });
messages = module.applyHomeDebateEvent(messages, { eventType: "debate_turn", turnId: "d:2", speakerMateId: "panel-1", mateName: "Panel", role: "Analyst", round: 1, avatarStyle: "imprint", avatarSeed: "panel-seed", avatarCustom: "data:image/svg+xml,panel-exact" });
assert.equal(messages.filter(function (message) { return message.role === "debate_turn"; }).length, 2);
assert.equal(messages.filter(function (message) { return message.role === "debate_tool_decision"; }).length, 1);
var planningModule = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-planning.js")).href);
var restored = module.normalizeHomeDebateMessages(planningModule.normalizeHomeTranscript(messages));
assert.equal(restored.filter(function (message) { return message.role === "debate_tool_decision"; }).length, 1);
assert.equal(messages[1].text, "Hello panel");
assert.equal(messages[1].status, "done");
assert.equal(module.isHomeDebateLive(messages), true);
var header = module.createHomeDebateLiveCard(messages[0], true);
var headerNodes = flatten(header);
assert.equal(headerNodes.some(function (node) { return node.textContent === "Housing"; }), true);
assert.equal(headerNodes.some(function (node) { return node.tagName === "BUTTON"; }), false);
var finalized = module.createHomeDebateLiveCard(messages[1], true);
var finalizedNodes = flatten(finalized);
assert.equal(finalized.attributes["aria-label"], "Clay, Moderator, round 1");
assert.equal(finalizedNodes.find(function (node) { return node.tagName === "IMG"; }).src, "data:image/svg+xml,clay-exact");
assert.equal(finalizedNodes.some(function (node) { return node.textContent === "Clay"; }), true);
assert.equal(finalizedNodes.some(function (node) { return node.textContent === "Moderator · Round 1"; }), true);
assert.equal(finalizedNodes.some(function (node) { return node.className === "home-debate-live-activity"; }), false);
var toolDecision = module.createHomeDebateLiveCard(messages[2], true);
var toolDecisionNodes = flatten(toolDecision);
assert.equal(toolDecision.attributes.role, "status");
assert.equal(toolDecisionNodes.some(function (node) { return node.textContent === "Moderator access"; }), true);
assert.equal(toolDecisionNodes.some(function (node) { return node.textContent === "Run read-only shell commands (rg)"; }), true);
assert.equal(toolDecisionNodes.some(function (node) { return node.textContent === "Would modify project files"; }), true);
assert.equal(toolDecisionNodes.some(function (node) { return node.textContent === "\u00d72"; }), true);
assert.equal(toolDecisionNodes.some(function (node) { return node.tagName === "SUMMARY" && node.textContent === "Audit 2 commands"; }), true);
assert.equal(toolDecisionNodes.some(function (node) { return node.tagName === "CODE" && node.textContent === "rg -n housing lib"; }), true);
assert.equal(toolDecisionNodes.some(function (node) { return node.textContent.indexOf("<") !== -1; }), false);
var active = module.createHomeDebateLiveCard(messages[3], false);
var activeNodes = flatten(active);
var activeContent = active.querySelector(".dm-bubble-content");
var activity = active.querySelector(".home-debate-live-activity");
var markdown = active.querySelector(".md-content");
assert.equal(active.attributes["aria-label"], "Panel, Analyst, round 1");
assert.equal(activeNodes.find(function (node) { return node.tagName === "IMG"; }).src, "data:image/svg+xml,panel-exact");
assert.equal(activeNodes.some(function (node) { return node.textContent === "Panel"; }), true);
assert.equal(activeNodes.some(function (node) { return node.textContent === "Analyst · Round 1"; }), true);
assert.equal(activity.parentNode, activeContent);
assert.ok(activeContent.children.indexOf(activity) < activeContent.children.indexOf(markdown));
assert.equal(flatten(activity).filter(function (node) { return node.tagName === "I"; }).length, 3);
messages = module.applyHomeDebateEvent(messages, { eventType: "debate_user_floor_done", text: "I want to add one constraint." });
var userMessage = messages.filter(function (message) { return message.role === "debate_user"; }).pop();
var user = module.createHomeDebateLiveCard(userMessage, true);
var userNodes = flatten(user);
assert.equal(user.attributes["aria-label"], "You, participant");
assert.equal(userNodes.some(function (node) { return node.textContent === "You"; }), true);
assert.equal(userNodes.some(function (node) { return node.textContent === "Participant"; }), true);
assert.ok(userNodes.find(function (node) { return node.tagName === "IMG"; }).src);
assert.equal(user.children[0], user.querySelector(".dm-bubble-avatar"));
assert.equal(user.children[1].children[0], user.querySelector(".dm-bubble-header"));
assert.equal(user.children[1].children[1], user.querySelector(".bubble"));
assert.notEqual(finalizedNodes.find(function (node) { return node.tagName === "IMG"; }).src, activeNodes.find(function (node) { return node.tagName === "IMG"; }).src);
messages = module.applyHomeDebateEvent(messages, { eventType: "debate_ended", reason: "user_stopped" });
assert.equal(module.homeDebatePhase(messages), "ended");
} finally {
global.document = originalDocument;
global.window = originalWindow;
global[storageKey] = originalStorage;
global.marked = originalMarked;
global.mermaid = originalMermaid;
global.DOMPurify = originalPurifier;
global.NodeFilter = originalNodeFilter;
}
var css = fs.readFileSync(path.join(__dirname, "../lib/public/css/home-debate-live.css"), "utf8");
assert.match(css, /prefers-reduced-motion: reduce/);
assert.match(css, /home-debate-tool-decision[\s\S]*var\(--text-secondary\)/);
assert.match(css, /home-debate-tool-decision-mark[\s\S]*var\(--success\)/);
assert.match(css, /@media \(max-width: 600px\)[\s\S]*home-debate-tool-decision/);
assert.match(css, /grid-template-columns:\s*34px minmax\(0, 1fr\)/);
assert.match(css, /home-debate-live-turn > \.dm-bubble-avatar[\s\S]*display:\s*block/);
assert.match(css, /@media \(max-width: 600px\)[\s\S]*grid-template-columns:\s*32px minmax\(0, 1fr\)/);
});
test("Home debate responder sends exact session correlation once and locks the question", async function () {
var module = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-planning.js")).href);
var sent = [];
var message = { role: "question", toolId: "ask-1", status: "pending" };
var opener = { textContent: "Submit answer", tagName: "BUTTON", disabled: false, parentNode: { children: [{ tagName: "BUTTON", disabled: false }, { tagName: "INPUT", disabled: false }] } };
var respond = module.createHomeDebateResponder(function (payload) { sent.push(payload); return true; }, function () { return { mateId: "builtin:clay", sessionId: "cli-1", requestId: "req-1" }; });
respond(message, "answer", opener, { 0: "Decision" });
respond(message, "answer", opener, { 0: "Duplicate" });
assert.equal(sent.length, 1);
assert.deepEqual(sent[0], { type: "home_debate_question_response", toolId: "ask-1", answers: { 0: "Decision" }, mateId: "builtin:clay", sessionId: "cli-1", requestId: "req-1" });
assert.equal(message.status, "submitting");
assert.equal(opener.parentNode.children[0].disabled, true);
});
test("Home debate question resolution restores focus without scrolling and errors remain retryable", async function () {
var module = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-planning.js")).href);
var target = { focusOptions: null, focus: function (options) { this.focusOptions = options; } };
var originalDocument = global.document;
try {
global.document = { querySelectorAll: function () { return [{ dataset: { toolId: "ask-focus" }, querySelector: function () { return target; } }]; } };
module.restoreHomeDebateQuestionFocus("ask-focus");
assert.deepEqual(target.focusOptions, { preventScroll: true });
var messages = [{ role: "question", toolId: "ask-focus", status: "submitting", questions: [] }];
messages = module.failHomeDebateQuestion(messages, { text: "Try again" });
assert.equal(messages[0].status, "pending");
assert.equal(messages[0].error, "Try again");
} finally {
global.document = originalDocument;
}
});
test("debate planning restores the composer with preventScroll without stealing later intentional focus", async function () {
var module = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-planning.js")).href);
var originalDocument = global.document;
var opener = { id: "home-sidebar-debate" };
var body = { classList: { contains: function (name) { return name === "home-active"; } } };
var focusOptions = null;
var input = { disabled: false, focus: function (options) { focusOptions = options; } };
try {
global.document = { activeElement: opener, body: body };
module.requestHomeDebateComposerFocus();
global.document.activeElement = { id: "home-sidebar-expand" };
input.disabled = true;
module.restoreHomeDebateComposerFocus(input);
assert.equal(focusOptions, null);
input.disabled = false;
module.restoreHomeDebateComposerFocus(input);
assert.deepEqual(focusOptions, { preventScroll: true });
focusOptions = null;
global.document.activeElement = opener;
module.requestHomeDebateComposerFocus();
global.document.activeElement = { id: "capsules-button" };
module.restoreHomeDebateComposerFocus(input);
assert.equal(focusOptions, null);
} finally {
global.document = originalDocument;
}
});
test("Home archive New debate selects builtin Clay while the sidebar only opens the archive", function () {
var root = path.join(__dirname, "..");
var chat = fs.readFileSync(path.join(root, "lib/public/modules/home-mate-chat.js"), "utf8");
var sidebar = fs.readFileSync(path.join(root, "lib/public/modules/home-sidebar.js"), "utf8");
var archive = fs.readFileSync(path.join(root, "lib/public/modules/home-debates-archive.js"), "utf8");
var router = fs.readFileSync(path.join(root, "lib/public/modules/app-message-router.js"), "utf8");
var project = fs.readFileSync(path.join(root, "lib/project.js"), "utf8");
var sdkBridge = fs.readFileSync(path.join(root, "lib/sdk-bridge.js"), "utf8");
var schema = fs.readFileSync(path.join(root, "lib/ws-schema.js"), "utf8");
var debateEngine = fs.readFileSync(path.join(root, "lib/project-debate.js"), "utf8");
var sessions = fs.readFileSync(path.join(root, "lib/sessions.js"), "utf8");
var css = fs.readFileSync(path.join(root, "lib/public/css/home-debate-planning.css"), "utf8");
assert.match(chat, /builtinKey === "clay"/);
assert.match(chat, /type: "home_mate_debate_plan"/);
assert.doesNotMatch(chat, /openDebateModal|clay:home-debate/);
assert.match(sidebar, /openHomeDebatesArchive\(\);[\s\S]*closeNarrowDrawer\(false\)/);
var debateSidebarHandler = sidebar.slice(sidebar.indexOf("function openDebatesFromSidebar"), sidebar.indexOf("function startMateCreationFromSidebar"));
assert.doesNotMatch(debateSidebarHandler, /home_mate_debate_plan|openHomeMateAction/);
assert.match(archive, /function startNewDebate\(\)[\s\S]*homeDebateTopicFormOpen: true/);
assert.match(archive, /openHomeMateAction\("debate", topic\.slice\(0, 1000\)\)/);
assert.match(router, /home_debate_question[\s\S]*handleHomeDebateTranscript/);
assert.match(router, /home_debate_proposal_resolved[\s\S]*handleHomeDebateTranscript/);
assert.match(project, /home_debate_question_response[\s\S]*opts\.onDmMessage\(ws, msg(?:, slug)?\)/);
assert.match(project, /onUserInputRequest: function \(session, request, respond\)[\s\S]*_askUser\.createHandler\(session\)/);
assert.doesNotMatch(project, /_askUser\.getToolDefs\(session\)/);
assert.match(sdkBridge, /requestedUserInputMode = session\.debateSetupMode \|\| session\.mateCreationMode \? "fallback" : "auto"/);
assert.match(sdkBridge, /yoke\.userInput\.fallbackToolDefs\(sessionUserInputHandler, \{ maxQuestions: scheduleQuestionLimit \}\)/);
assert.match(schema, /"home_debate_question_response"[\s\S]*"home_debate_question"[\s\S]*"home_debate_question_resolved"/);
assert.match(debateEngine, /var reuseHomeSession = session\.homeDebatePlanning === true[\s\S]*if \(reuseHomeSession\)[\s\S]*session\.homeDebatePhase = "live"[\s\S]*else \{[\s\S]*createSession\(liveOpts, targetWs \|\| null\)/);
assert.match(project, /home_debate_question_response" \|\| msg\.type === "home_debate_control"[\s\S]*opts\.onDmMessage\(ws, msg(?:, slug)?\)/);
assert.match(schema, /"home_debate_control"[\s\S]*"home_debate_event"/);
assert.match(sessions, /if \(session\.homeDebatePlanning === true\) return;/);
assert.match(css, /\.home-debate-proposal button:focus-visible/);
assert.match(css, /@media \(max-width: 600px\)[\s\S]*\.home-debate-proposal/);
assert.match(css, /\.home-debate-question button:focus-visible/);
});
test("Home debate launch immediately renders a dedicated correlated loading transcript", async function () {
var originalDocument = global.document;
global.document = { createElement: function (tag) { return new FakeElement(tag); } };
try {
var module = await import(pathToFileURL(path.join(__dirname, "../lib/public/modules/home-debate-launch.js")).href);
module.resetHomeDebateLaunch();
module.beginHomeDebateLaunch("plan-loading");
assert.equal(module.isHomeDebateLaunching(), true);
var row = module.createHomeDebateLaunchRow();
var nodes = flatten(row);
assert.equal(row.attributes.role, "status");
assert.equal(row.attributes["aria-live"], "polite");
assert.equal(row.attributes["aria-atomic"], "true");
assert.equal(row.attributes["aria-label"], "Clay is preparing your debate");
assert.match(row.className, /home-debate-activity-launch/);
assert.equal(nodes.some(function (node) { return node.textContent === "Clay"; }), true);
assert.equal(nodes.some(function (node) { return node.textContent === "Preparing your debate"; }), true);
var avatar = nodes.find(function (node) { return node.className === "home-debate-activity-avatar"; });
assert.equal(avatar.src, "/clay-studio-symbol.png");
assert.equal(avatar.alt, "");
assert.equal(nodes.filter(function (node) { return node.tagName === "I"; }).length, 3);
assert.equal(module.createHomeDebateLaunchRow().attributes["aria-live"], "off");
assert.equal(module.settleHomeDebateLaunch({ requestId: "stale" }), false);
assert.equal(module.isHomeDebateLaunching(), true);
module.syncHomeDebateLaunchHistory({ requestId: "plan-loading", debatePlanning: true, messages: [] });
assert.equal(module.isHomeDebateLaunching(), true);
assert.equal(module.settleHomeDebateLaunch({ requestId: "plan-loading", type: "home_debate_question" }), true);
assert.equal(module.isHomeDebateLaunching(), false);
} finally {
global.document = originalDocument;
}
var chat = fs.readFileSync(path.join(__dirname, "../lib/public/modules/home-mate-chat.js"), "utf8");
assert.match(chat, /var hasConversation = debateLaunching \|\| messages\.length/);
assert.match(chat, /if \(debateLaunching \|\| mateCreationActive \|\| !!debatePhase\) inputEl\.disabled = true/);
assert.match(chat, /debateLaunching \? "Preparing your debate…"/);
assert.match(chat, /if \(debateLaunching\) \{\s*transcript\.appendChild\(createHomeDebateLaunchRow\(\)\)/);
var launch = fs.readFileSync(path.join(__dirname, "../lib/public/modules/home-debate-launch.js"), "utf8");
var planning = fs.readFileSync(path.join(__dirname, "../lib/public/modules/home-debate-planning.js"), "utf8");
var css = fs.readFileSync(path.join(__dirname, "../lib/public/css/home-debate-planning.css"), "utf8");
var activityCss = css.slice(css.indexOf(".home-debate-activity"), css.indexOf(".home-debate-proposal"));
assert.match(launch, /createHomeDebateActivityRow\("Preparing your debate", "Clay is preparing your debate"/);
assert.match(planning, /createHomeDebateActivityRow\("Preparing the next question", "Clay is preparing the next question"/);
assert.match(activityCss, /display: grid/);
assert.match(activityCss, /align-self: flex-start/);
assert.match(activityCss, /var\(--text-primary\)/);
assert.match(activityCss, /var\(--accent\)/);
assert.match(activityCss, /var\(--border\)/);
assert.doesNotMatch(activityCss, /justify-content:\s*center|min-height:\s*120px|#[0-9a-f]{3,8}|serif/i);
assert.doesNotMatch(css, /\.home-debate-preparing|\.home-debate-planning-pending/);
assert.match(css, /prefers-reduced-motion: reduce[\s\S]*\.home-debate-activity-dots i/);
assert.match(css, /@media \(max-width: 600px\)[\s\S]*\.home-debate-activity/);
});
test('MCP import review reuses a safe compact question card and requires explicit option selection', async function () {
var previous = global.document; global.document = { createElement: function (tag) { return new FakeElement(tag); }, getElementById: function () { return null; }, querySelectorAll: function () { return []; }, addEventListener: function () {} };
try {
var module = await import('../lib/public/modules/home-debate-planning.js'), calls = [];
var message = module.applyHomeDebateQuestion([], { type: 'home_mcp_import_question', toolId: 'consent', questions: [{ header: 'Import MCP', question: 'Source: