From ee905b8cbcbd2c6f40bd000f00ce130ca6c4a00b Mon Sep 17 00:00:00 2001 From: Alex J Lennon Date: Mon, 10 Aug 2026 09:38:16 +0100 Subject: [PATCH] =?UTF-8?q?fix=20Nebula=20MCQ:=20split=20dense=20=C2=B7=20?= =?UTF-8?q?questions=20into=20lead=20+=20detail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preloop packs referent · ask · Why on one line; Gtk already reformats that. Nebula now matches so Policy/Comms confirms are readable instead of one run-on. Co-authored-by: Cursor --- scripts/test_nebula_layout.py | 16 +++++- src/ask_question_mcp/assets/dialog/dialog.css | 31 +++++++++++ src/ask_question_mcp/assets/dialog/dialog.js | 52 ++++++++++++++++++- src/ask_question_mcp/assets/dialog/index.html | 5 +- 4 files changed, 100 insertions(+), 4 deletions(-) diff --git a/scripts/test_nebula_layout.py b/scripts/test_nebula_layout.py index d25533c..6382975 100644 --- a/scripts/test_nebula_layout.py +++ b/scripts/test_nebula_layout.py @@ -49,7 +49,7 @@ def test_banner_does_not_repeat_question() -> None: js = ( ROOT / "src" / "ask_question_mcp" / "assets" / "dialog" / "dialog.js" ).read_text(encoding="utf-8") - assert 'banner-copy").textContent' in js or "$(\"#banner-copy\")" in js + assert 'banner-copy").textContent' in js or '$("#banner-copy")' in js # Old bug: `${prefix}${payload.question || ""}` assert "${prefix}${payload.question" not in js, ( "REGRESSION: banner-copy must not append payload.question " @@ -58,7 +58,21 @@ def test_banner_does_not_repeat_question() -> None: assert "prefix}${payload.question" not in js +def test_nebula_lead_detail_split() -> None: + """Nebula must split dense Preloop ' · ' packs like Gtk (lead + detail).""" + html = INDEX.read_text(encoding="utf-8") + js = ( + ROOT / "src" / "ask_question_mcp" / "assets" / "dialog" / "dialog.js" + ).read_text(encoding="utf-8") + assert 'id="question-detail"' in html, "missing #question-detail" + assert "function formatConfirmBody" in js + assert "function splitLeadDetail" in js + assert "function renderQuestion" in js + assert "renderQuestion(payload.question" in js + + if __name__ == "__main__": test_freeform_inside_main_body() test_banner_does_not_repeat_question() + test_nebula_lead_detail_split() print("test_nebula_layout: ok") diff --git a/src/ask_question_mcp/assets/dialog/dialog.css b/src/ask_question_mcp/assets/dialog/dialog.css index 1d34441..8cd0b35 100644 --- a/src/ask_question_mcp/assets/dialog/dialog.css +++ b/src/ask_question_mcp/assets/dialog/dialog.css @@ -371,6 +371,37 @@ body { transform: translateY(0); } +.question-block { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; +} + +.question-detail { + margin: 0; + max-height: 160px; + overflow-x: hidden; + overflow-y: auto; + font-size: 13.5px; + font-weight: 450; + letter-spacing: -0.01em; + line-height: 1.45; + color: var(--text-muted, color-mix(in srgb, var(--text) 72%, transparent)); + white-space: pre-wrap; + opacity: 0; + transform: translateY(8px); + transition: + opacity 720ms var(--ease), + transform 720ms var(--ease); + transition-delay: 140ms; +} + +.app.is-ready .question-detail { + opacity: 1; + transform: translateY(0); +} + [data-theme="ink"] .question, [data-theme="hybrid"] .question { font-weight: 520; diff --git a/src/ask_question_mcp/assets/dialog/dialog.js b/src/ask_question_mcp/assets/dialog/dialog.js index fbf977e..f2eac74 100644 --- a/src/ask_question_mcp/assets/dialog/dialog.js +++ b/src/ask_question_mcp/assets/dialog/dialog.js @@ -34,6 +34,52 @@ return String(label || "").replace(/^\d+\s*[·.]\s*/, "").trim(); } + /** Match dialog_keys.format_confirm_body — dense " · " packs become lines. */ + function formatConfirmBody(question) { + const q = String(question || "").trim(); + if (!q) return q; + if (q.includes("\n")) return q; + if (q.includes(" · ")) { + const parts = q + .split(" · ") + .map((p) => p.trim()) + .filter(Boolean); + if (parts.length >= 2) return parts.join("\n"); + } + return q; + } + + /** Match dialog_keys.split_lead_detail — first line = ask/referent lead. */ + function splitLeadDetail(body) { + const text = String(body || "").replace(/^\n+|\n+$/g, ""); + if (!text.trim()) return ["", ""]; + const lines = text.split("\n"); + let i = 0; + while (i < lines.length && !lines[i].trim()) i += 1; + if (i >= lines.length) return ["", ""]; + const lead = lines[i]; + const rest = lines.slice(i + 1); + while (rest.length && !rest[0].trim()) rest.shift(); + return [lead, rest.join("\n").replace(/\n+$/g, "")]; + } + + function renderQuestion(rawQuestion) { + const body = formatConfirmBody(rawQuestion); + const [lead, detail] = splitLeadDetail(body); + const qEl = $("#question"); + const dEl = $("#question-detail"); + if (qEl) qEl.textContent = lead || body || ""; + if (dEl) { + if (detail) { + dEl.hidden = false; + dEl.textContent = detail; + } else { + dEl.hidden = true; + dEl.textContent = ""; + } + } + } + function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } @@ -690,7 +736,7 @@ if (bandClass) eyebrowEl.classList.add(bandClass); else if (dangerous) eyebrowEl.classList.add("is-danger"); $("#title-agent").textContent = payload.agent_hint || payload.title || ""; - $("#question").textContent = payload.question || ""; + renderQuestion(payload.question || ""); const banner = $("#banner"); banner.classList.toggle("is-on", dangerous); @@ -804,6 +850,7 @@ function fitWindow() { const chrome = document.querySelector(".chrome"); const banner = document.getElementById("banner"); + const questionBlock = document.getElementById("question-block"); const question = document.getElementById("question"); const options = document.getElementById("options"); const refs = document.getElementById("refs"); @@ -811,7 +858,8 @@ const footer = document.querySelector(".footer"); // Titlebar + breathing room; too-tight budgets clip OK/Cancel on scaled monitors. let h = 36; - [chrome, question, refs, freeform, footer].forEach((el) => { + const qMeasure = questionBlock || question; + [chrome, qMeasure, refs, freeform, footer].forEach((el) => { if (el && !el.hidden) h += el.offsetHeight; }); // Voice recover chrome can grow after mount — keep Cancel/OK on-screen. diff --git a/src/ask_question_mcp/assets/dialog/index.html b/src/ask_question_mcp/assets/dialog/index.html index 637eff1..c39901a 100644 --- a/src/ask_question_mcp/assets/dialog/index.html +++ b/src/ask_question_mcp/assets/dialog/index.html @@ -37,7 +37,10 @@ -

+
+

+ +