diff --git a/CHANGELOG.md b/CHANGELOG.md index 4801e6f7..50afa402 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## [Unreleased] +### Added + +- **PR #2731** by @Michaelyklam — Clarification prompts now include a compact Collapse/Expand control so users can temporarily shrink a blocking decision card and reread the chat context behind it before responding. + ## [v0.51.114] — 2026-05-22 — Release CL (stage-407 — 1-PR — update-check recovery from remote re-tags) ### Fixed diff --git a/docs/pr-media/issue-2728/clarify-collapsed.png b/docs/pr-media/issue-2728/clarify-collapsed.png new file mode 100644 index 00000000..9dc93e85 Binary files /dev/null and b/docs/pr-media/issue-2728/clarify-collapsed.png differ diff --git a/docs/pr-media/issue-2728/clarify-expanded.png b/docs/pr-media/issue-2728/clarify-expanded.png new file mode 100644 index 00000000..4b5175fc Binary files /dev/null and b/docs/pr-media/issue-2728/clarify-expanded.png differ diff --git a/static/index.html b/static/index.html index 1659189f..56e16900 100644 --- a/static/index.html +++ b/static/index.html @@ -488,6 +488,7 @@ Clarification needed +
diff --git a/static/messages.js b/static/messages.js index cb4486eb..a33323ac 100644 --- a/static/messages.js +++ b/static/messages.js @@ -2502,6 +2502,7 @@ function _ensureClarifyCardDom() { Clarification needed +
@@ -2515,10 +2516,29 @@ function _ensureClarifyCardDom() { host.appendChild(card); const submit = $("clarifySubmit"); if (submit) submit.onclick = () => respondClarify(); + const collapse = $("clarifyCollapse"); + if (collapse) collapse.onclick = () => toggleClarifyCardCollapsed(); if (typeof applyLocaleToDOM === "function") applyLocaleToDOM(); return card; } +function _syncClarifyCollapseButton(card) { + const collapse = $("clarifyCollapse"); + if (!collapse || !card) return; + const collapsed = card.classList.contains("collapsed"); + collapse.setAttribute("aria-expanded", collapsed ? "false" : "true"); + collapse.textContent = collapsed ? "Expand" : "Collapse"; + collapse.title = collapsed ? "Expand clarification" : "Collapse clarification"; +} + +function toggleClarifyCardCollapsed(forceCollapsed) { + const card = $("clarifyCard"); + if (!card) return; + const collapsed = typeof forceCollapsed === "boolean" ? forceCollapsed : !card.classList.contains("collapsed"); + card.classList.toggle("collapsed", collapsed); + _syncClarifyCollapseButton(card); +} + function _clearClarifyHideTimer() { if (_clarifyHideTimer) { clearTimeout(_clarifyHideTimer); @@ -2685,6 +2705,7 @@ function showClarifyCard(pending) { if (!sameClarify) { _clarifyVisibleSince = Date.now(); _clearClarifyHideTimer(); + card.classList.remove("collapsed"); } if (questionEl) questionEl.textContent = question; if (choicesEl) { @@ -2745,6 +2766,7 @@ function showClarifyCard(pending) { } _clarifySetControlsDisabled(false, false); card.classList.add("visible"); + _syncClarifyCollapseButton(card); if (typeof applyLocaleToDOM === "function") applyLocaleToDOM(); if (input && !sameClarify) setTimeout(() => input.focus({preventScroll: true}), 50); } diff --git a/static/style.css b/static/style.css index e34506e1..9fe385bd 100644 --- a/static/style.css +++ b/static/style.css @@ -963,11 +963,13 @@ /* ── Clarify card ── */ .clarify-card{position:absolute;left:0;right:0;bottom:-24px;max-width:var(--msg-max);margin:0 auto;padding:0 20px;box-sizing:border-box;width:100%;overflow:hidden;pointer-events:none;max-height:min(calc(100vh - 280px),420px);} .clarify-card.visible{pointer-events:auto;} - .clarify-card .clarify-inner{max-height:min(calc(100vh - 280px),420px);overflow-y:auto;transform:translateY(100%);opacity:0;transition:transform .4s cubic-bezier(.32,.72,.16,1),opacity .25s ease;} + .clarify-card .clarify-inner{max-height:min(calc(100vh - 280px),420px);overflow-y:auto;transform:translateY(100%);opacity:0;transition:transform .4s cubic-bezier(.32,.72,.16,1),opacity .25s ease,max-height .2s ease;} .clarify-card.visible .clarify-inner{transform:translateY(0);opacity:1;} .clarify-inner{background:var(--surface);backdrop-filter:blur(8px);border:1px solid var(--accent-bg-strong);border-radius:12px;padding:12px 14px 36px;box-shadow:0 1px 0 rgba(255,255,255,.02) inset;} .clarify-header{display:flex;align-items:center;gap:8px;margin-bottom:10px;font-size:12px;font-weight:700;color:var(--blue);letter-spacing:.01em;} .clarify-countdown{margin-left:auto;min-width:42px;text-align:right;color:var(--muted);font-variant-numeric:tabular-nums;font-weight:700;} + .clarify-collapse{margin-left:4px;border:1px solid var(--border2);border-radius:999px;background:var(--surface);color:var(--muted);font:inherit;font-size:11px;font-weight:700;line-height:1;padding:5px 8px;cursor:pointer;} + .clarify-collapse:hover,.clarify-collapse:focus-visible{color:var(--text);border-color:var(--accent-bg-strong);outline:none;} .clarify-countdown.urgent{color:var(--error);box-shadow:inset 0 -2px 0 var(--error);border-radius:2px;} .clarify-question{font-size:14px;color:var(--text);line-height:1.7;white-space:pre-wrap;margin-bottom:12px;} .clarify-choices{display:flex;flex-direction:column;gap:8px;margin-bottom:12px;} @@ -988,6 +990,10 @@ .clarify-submit.loading{opacity:.75;cursor:wait;} .clarify-hint{margin-top:8px;font-size:11px;line-height:1.45;color:var(--muted);} .clarify-card.visible .clarify-question{padding-left:1px;} + .clarify-card.collapsed{max-height:72px;} + .clarify-card.collapsed .clarify-inner{max-height:56px;overflow:hidden;padding-bottom:12px;} + .clarify-card.collapsed .clarify-header{margin-bottom:0;} + .clarify-card.collapsed .clarify-question,.clarify-card.collapsed .clarify-choices,.clarify-card.collapsed .clarify-response,.clarify-card.collapsed .clarify-hint{display:none;} /* Left rail (desktop primary navigation) */ .rail{display:none;width:48px;flex-shrink:0;flex-direction:column;align-items:center;gap:4px;padding:8px 0;background:var(--sidebar);border-right:1px solid var(--border);} .rail-btn{width:36px;height:36px;border-radius:8px;border:none;background:none;color:var(--muted);cursor:pointer;display:flex;align-items:center;justify-content:center;position:relative;transition:color .15s,background .15s;flex-shrink:0;padding:0;} diff --git a/tests/test_sprint30.py b/tests/test_sprint30.py index 15fa4d49..f34aac45 100644 --- a/tests/test_sprint30.py +++ b/tests/test_sprint30.py @@ -98,6 +98,7 @@ class TestClarifyCardHTML: assert 'id="clarifyChoices"' in html, "clarify choices container missing" assert 'id="clarifyInput"' in html, "clarify input missing" assert 'id="clarifySubmit"' in html, "clarify submit button missing" + assert 'id="clarifyCollapse"' in html, "clarify collapse button missing" def test_clarify_card_has_data_i18n(self): html = read(REPO / "static/index.html") @@ -167,7 +168,9 @@ class TestClarifyCardCSS: ".clarify-response", ".clarify-input", ".clarify-submit", + ".clarify-collapse", ".clarify-hint", + ".clarify-card.collapsed", ): assert cls in css, f"CSS class '{cls}' missing" @@ -317,6 +320,13 @@ class TestClarifyMessagesJS: for token in ("startClarifyPolling", "stopClarifyPolling", "hideClarifyCard", "_clarifySessionId"): assert token in src, f"{token} missing from messages.js" + def test_clarify_card_can_collapse_without_hiding_prompt(self): + src = read(REPO / "static/messages.js") + assert "function toggleClarifyCardCollapsed" in src, "clarify collapse toggle missing" + assert "aria-expanded" in src, "clarify collapse button should expose expanded state" + assert "card.classList.remove(\"collapsed\")" in src, \ + "new clarification prompts should reopen rather than stay collapsed" + # ── boot.js keyboard shortcut ────────────────────────────────────────────────