From c19aaae27f8435ff687d358e19a90a3855605753 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Wed, 24 Jun 2026 09:04:14 -0400 Subject: [PATCH 1/6] fix(#4752): show tool result instead of call args in collapsed card preview --- static/ui.js | 10 + ...t_issue4752_collapsed_card_shows_result.py | 187 ++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 tests/test_issue4752_collapsed_card_shows_result.py diff --git a/static/ui.js b/static/ui.js index 56f9f888..c6d8afe4 100644 --- a/static/ui.js +++ b/static/ui.js @@ -12903,9 +12903,19 @@ function _formatToolArgPreview(args){ const out=parts.join(' · '); return out.length>180?`${out.slice(0,177)}…`:out; } +function _toolResultOneLiner(preview){ + if(!preview) return ''; + const first=preview.split('\n').find(l=>l.trim())||''; + const trimmed=first.trim(); + if(!trimmed) return ''; + if(trimmed[0]==='{'||trimmed[0]==='[') return ''; + return trimmed.length>180?trimmed.slice(0,177)+'…':trimmed; +} function _toolCardPreviewText(tc, displaySnippet){ const explicitPreview=String(tc&&tc.preview||'').trim(); if(tc&&tc.done===false&&explicitPreview) return explicitPreview; + const resultLine=_toolResultOneLiner(explicitPreview); + if(tc&&tc.done!==false&&resultLine) return resultLine; const argPreview=_formatToolArgPreview(tc&&tc.args); if(argPreview) return argPreview; if(tc&&tc.done===false) return 'Running'; diff --git a/tests/test_issue4752_collapsed_card_shows_result.py b/tests/test_issue4752_collapsed_card_shows_result.py new file mode 100644 index 00000000..932186a9 --- /dev/null +++ b/tests/test_issue4752_collapsed_card_shows_result.py @@ -0,0 +1,187 @@ +"""Regression tests for issue #4752 — collapsed tool card shows call result, not args. + +Extracts _toolResultOneLiner, _toolCardPreviewText, and _formatToolArgPreview from +static/ui.js by line range and runs them via Node.js to verify the fix. + +Before the fix, _toolCardPreviewText always fell through to argPreview for completed +tools, so 'Found 3 matches' would show as 'path=src/' in the collapsed header. +""" +import json +import os +import re +import subprocess +import tempfile + +UI_JS = os.path.join(os.path.dirname(__file__), '..', 'static', 'ui.js') + + +def _extract_js_block(): + """Extract the tool-card preview functions from ui.js by finding their definition range.""" + lines = open(UI_JS, encoding='utf-8').readlines() + src = "".join(lines) + + # Find start of the earliest function we need + fn_names = [ + "_toolArgPreviewValue", + "_toolArgPreviewKeyIsHidden", + "_formatToolArgPreview", + "_toolResultOneLiner", + "_toolCardPreviewText", + ] + + # Find start line of _toolArgPreviewValue (the earliest dependency) + start_line = None + for i, line in enumerate(lines): + if re.search(r'^function _toolArgPreviewValue\(', line): + start_line = i + break + if start_line is None: + raise AssertionError("_toolArgPreviewValue not found in ui.js") + + # Find end line of _toolCardPreviewText (the last function we need) + # It ends at the function _toolCardAllowsDetail that follows it + end_line = None + for i in range(start_line, len(lines)): + if re.search(r'^function _toolCardAllowsDetail\(', lines[i]): + end_line = i + break + if end_line is None: + raise AssertionError("_toolCardAllowsDetail (end sentinel) not found in ui.js") + + return "".join(lines[start_line:end_line]) + + +def _run_js(js_body, *args): + """Run a Node.js snippet with optional JSON arguments, return stdout.""" + fn_defs = _extract_js_block() + js_code = fn_defs + "\n" + js_body + tf = tempfile.NamedTemporaryFile(mode='w', suffix='.js', delete=False, encoding='utf-8') + tf.write(js_code) + tf.close() + try: + result = subprocess.run( + ['node', tf.name] + [json.dumps(a) for a in args], + capture_output=True, text=True, timeout=30 + ) + if result.returncode != 0: + raise RuntimeError(f"node error: {result.stderr}") + return result.stdout + finally: + os.unlink(tf.name) + + +def _preview_text(tc): + """Call _toolCardPreviewText(tc) via Node.js.""" + return _run_js( + "var tc=JSON.parse(process.argv[2]);\n" + "process.stdout.write(String(_toolCardPreviewText(tc)));\n", + tc + ) + + +def _result_one_liner(preview): + """Call _toolResultOneLiner(preview) via Node.js.""" + return _run_js( + "var p=JSON.parse(process.argv[2]);\n" + "process.stdout.write(String(_toolResultOneLiner(p)));\n", + preview + ) + + +class TestToolResultOneLiner: + """Unit tests for the _toolResultOneLiner helper.""" + + def test_empty_string_returns_empty(self): + assert _result_one_liner("") == "" + + def test_plain_text_returned(self): + assert _result_one_liner("Found 3 matches") == "Found 3 matches" + + def test_json_object_suppressed(self): + assert _result_one_liner('{"key":"val"}') == "" + + def test_json_array_suppressed(self): + assert _result_one_liner('[1,2,3]') == "" + + def test_multiline_collapses_to_first_nonempty_line(self): + assert _result_one_liner("Line 1\nLine 2\nLine 3") == "Line 1" + + def test_leading_blank_lines_skipped(self): + assert _result_one_liner("\n\nActual result") == "Actual result" + + def test_truncation_at_180_chars(self): + long_line = "x" * 200 + result = _result_one_liner(long_line) + assert result.endswith("…") + # 177 ASCII chars + 1 ellipsis character = 178 chars total + assert len(result) == 178 + + def test_short_line_not_truncated(self): + line = "x" * 180 + result = _result_one_liner(line) + assert len(result) == 180 + assert not result.endswith("…") + + +class TestToolCardPreviewText: + """Integration tests for the updated _toolCardPreviewText function.""" + + def test_result_wins_on_completion(self): + """Completed tool with both preview and args returns the preview result.""" + tc = {"done": True, "preview": "Found 3 matches", "args": {"path": "src/"}} + assert _preview_text(tc) == "Found 3 matches" + + def test_args_fallback_when_no_preview(self): + """Completed tool without preview falls back to arg preview.""" + tc = {"done": True, "preview": "", "args": {"path": "src/"}} + result = _preview_text(tc) + assert "path" in result + assert "src" in result + + def test_json_preview_suppressed_falls_through_to_args(self): + """JSON body in preview is suppressed; falls back to arg preview.""" + tc = {"done": True, "preview": '{"key":"val"}', "args": {"path": "src/"}} + result = _preview_text(tc) + # JSON suppressed, so falls through to arg preview + assert result != '{"key":"val"}' + assert "path" in result or "src" in result + + def test_array_preview_suppressed_falls_through_to_args(self): + """Array body in preview is suppressed; falls back to arg preview.""" + tc = {"done": True, "preview": "[1,2,3]", "args": {"path": "src/"}} + result = _preview_text(tc) + assert result != "[1,2,3]" + assert "path" in result or "src" in result + + def test_multiline_preview_collapses_to_first_line(self): + """Multiline preview collapses to first non-empty line.""" + tc = {"done": True, "preview": "Line 1\nLine 2\nLine 3", "args": {}} + assert _preview_text(tc) == "Line 1" + + def test_running_preview_unchanged(self): + """Running tool (done===false) returns the explicit preview unchanged.""" + tc = {"done": False, "preview": "Searching...", "args": {"path": "src/"}} + assert _preview_text(tc) == "Searching..." + + def test_long_preview_truncated(self): + """Single-line preview >180 chars is truncated with ellipsis.""" + long_preview = "x" * 200 + tc = {"done": True, "preview": long_preview, "args": {}} + result = _preview_text(tc) + assert result.endswith("…") + assert len(result) == 178 # 177 chars + ellipsis + + def test_no_args_no_preview_returns_completed(self): + """Completed tool with no args and no preview returns 'Completed'.""" + tc = {"done": True, "preview": "", "args": {}} + assert _preview_text(tc) == "Completed" + + def test_error_state_returns_failed(self): + """Error tool with no preview or args returns 'Failed'.""" + tc = {"done": True, "is_error": True, "preview": "", "args": {}} + assert _preview_text(tc) == "Failed" + + def test_running_no_preview_returns_running(self): + """Running tool with no preview returns 'Running'.""" + tc = {"done": False, "preview": "", "args": {}} + assert _preview_text(tc) == "Running" From 29604e5e7b0e6e5d5ffe93c58b6e05bb92c6baf0 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Wed, 24 Jun 2026 09:16:10 -0400 Subject: [PATCH 2/6] fix: fall back to tc.snippet for cold-loaded tool results --- static/ui.js | 3 ++- ...t_issue4752_collapsed_card_shows_result.py | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/static/ui.js b/static/ui.js index c6d8afe4..8fd37a78 100644 --- a/static/ui.js +++ b/static/ui.js @@ -12914,7 +12914,8 @@ function _toolResultOneLiner(preview){ function _toolCardPreviewText(tc, displaySnippet){ const explicitPreview=String(tc&&tc.preview||'').trim(); if(tc&&tc.done===false&&explicitPreview) return explicitPreview; - const resultLine=_toolResultOneLiner(explicitPreview); + const resultSource=explicitPreview||String(tc&&tc.snippet||'').trim(); + const resultLine=_toolResultOneLiner(resultSource); if(tc&&tc.done!==false&&resultLine) return resultLine; const argPreview=_formatToolArgPreview(tc&&tc.args); if(argPreview) return argPreview; diff --git a/tests/test_issue4752_collapsed_card_shows_result.py b/tests/test_issue4752_collapsed_card_shows_result.py index 932186a9..2d52a5ff 100644 --- a/tests/test_issue4752_collapsed_card_shows_result.py +++ b/tests/test_issue4752_collapsed_card_shows_result.py @@ -9,9 +9,15 @@ tools, so 'Found 3 matches' would show as 'path=src/' in the collapsed header. import json import os import re +import shutil import subprocess import tempfile +import pytest + +NODE = shutil.which("node") +pytestmark = pytest.mark.skipif(NODE is None, reason="node not on PATH") + UI_JS = os.path.join(os.path.dirname(__file__), '..', 'static', 'ui.js') @@ -60,7 +66,7 @@ def _run_js(js_body, *args): tf.close() try: result = subprocess.run( - ['node', tf.name] + [json.dumps(a) for a in args], + [NODE, tf.name] + [json.dumps(a) for a in args], capture_output=True, text=True, timeout=30 ) if result.returncode != 0: @@ -185,3 +191,15 @@ class TestToolCardPreviewText: """Running tool with no preview returns 'Running'.""" tc = {"done": False, "preview": "", "args": {}} assert _preview_text(tc) == "Running" + + def test_snippet_fallback_on_cold_load(self): + """Cold-loaded tool with result in snippet (no preview) uses snippet.""" + tc = {"done": True, "preview": "", "snippet": "Found 3 matches", "args": {"path": "src/"}} + assert _preview_text(tc) == "Found 3 matches" + + def test_snippet_json_suppressed(self): + """Cold-loaded tool with JSON in snippet falls through to args.""" + tc = {"done": True, "preview": "", "snippet": '{"key":"val"}', "args": {"path": "src/"}} + result = _preview_text(tc) + assert result != '{"key":"val"}' + assert "path" in result or "src" in result From 32d171c1a1f948f8e42ebaa4215f47d7d9bd410f Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Wed, 24 Jun 2026 09:23:14 -0400 Subject: [PATCH 3/6] fix: add _toolResultOneLiner to sibling test extractors and pin error-snippet behavior --- tests/test_issue4658_transparent_collapsed_preview.py | 2 +- tests/test_issue4752_collapsed_card_shows_result.py | 5 +++++ tests/test_tool_card_preview_summary.py | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_issue4658_transparent_collapsed_preview.py b/tests/test_issue4658_transparent_collapsed_preview.py index c0aa224c..eee3e79e 100644 --- a/tests/test_issue4658_transparent_collapsed_preview.py +++ b/tests/test_issue4658_transparent_collapsed_preview.py @@ -119,7 +119,7 @@ var _FN_NAMES=[ '_decodeToolLabelEntities','_redactToolTargetLabel','_shortToolLabel','_toolI18n', '_toolTargetLabel','_toolVisibleTargetLabel','_toolCommandTitle','_toolQueryTitle', '_toolActionLabelText','_toolArgPreviewValue','_toolArgPreviewKeyIsHidden', - '_formatToolArgPreview','_toolCardPreviewText','_toolCardAllowsDetail', + '_formatToolArgPreview','_toolResultOneLiner','_toolCardPreviewText','_toolCardAllowsDetail', '_toolDetailLeadLabel','_toolDetailLeadText','_toolShortName','_transparentEventPreview', '_transparentToolStatus','_transparentToolSummary', '_isMemorySave','_isSkillUpdate','_tcAction', diff --git a/tests/test_issue4752_collapsed_card_shows_result.py b/tests/test_issue4752_collapsed_card_shows_result.py index 2d52a5ff..ce70056d 100644 --- a/tests/test_issue4752_collapsed_card_shows_result.py +++ b/tests/test_issue4752_collapsed_card_shows_result.py @@ -197,6 +197,11 @@ class TestToolCardPreviewText: tc = {"done": True, "preview": "", "snippet": "Found 3 matches", "args": {"path": "src/"}} assert _preview_text(tc) == "Found 3 matches" + def test_error_with_text_snippet_shows_message(self): + """Error tool with a plain-text snippet shows the error message, not 'Failed'.""" + tc = {"done": True, "is_error": True, "preview": "", "snippet": "Permission denied", "args": {}} + assert _preview_text(tc) == "Permission denied" + def test_snippet_json_suppressed(self): """Cold-loaded tool with JSON in snippet falls through to args.""" tc = {"done": True, "preview": "", "snippet": '{"key":"val"}', "args": {"path": "src/"}} diff --git a/tests/test_tool_card_preview_summary.py b/tests/test_tool_card_preview_summary.py index ca9010b8..4866c7b2 100644 --- a/tests/test_tool_card_preview_summary.py +++ b/tests/test_tool_card_preview_summary.py @@ -38,6 +38,7 @@ function extractFunc(name) { eval(extractFunc('_toolArgPreviewValue')); eval(extractFunc('_toolArgPreviewKeyIsHidden')); eval(extractFunc('_formatToolArgPreview')); +eval(extractFunc('_toolResultOneLiner')); eval(extractFunc('_toolCardPreviewText')); let buf = ''; process.stdin.on('data', c => { buf += c; }); From 4bf659e4335b36af0af18787f111a4a52cbd8aa9 Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Wed, 24 Jun 2026 09:55:22 -0400 Subject: [PATCH 4/6] fix: harden test drivers against extractFunc brace-counter overrun --- ...test_issue1824_cli_patch_diff_rendering.py | 1 + ...issue4658_transparent_collapsed_preview.py | 121 ++++++++++++------ ...t_issue4752_collapsed_card_shows_result.py | 10 -- tests/test_tool_card_preview_summary.py | 25 +--- 4 files changed, 91 insertions(+), 66 deletions(-) diff --git a/tests/test_issue1824_cli_patch_diff_rendering.py b/tests/test_issue1824_cli_patch_diff_rendering.py index 7e51d750..ad6051a2 100644 --- a/tests/test_issue1824_cli_patch_diff_rendering.py +++ b/tests/test_issue1824_cli_patch_diff_rendering.py @@ -112,6 +112,7 @@ def test_rendered_apply_patch_tool_card_html_contains_diff_lines(): "_toolArgPreviewValue", "_toolArgPreviewKeyIsHidden", "_formatToolArgPreview", + "_toolResultOneLiner", "_toolCardPreviewText", # #3336: buildToolCard now wraps diff snippets via these helpers. "_snippetLooksLikeDiff", diff --git a/tests/test_issue4658_transparent_collapsed_preview.py b/tests/test_issue4658_transparent_collapsed_preview.py index eee3e79e..5401ef50 100644 --- a/tests/test_issue4658_transparent_collapsed_preview.py +++ b/tests/test_issue4658_transparent_collapsed_preview.py @@ -23,6 +23,7 @@ shims, and runs the same render path live streaming and persisted reload share (both go through _decorateTransparentEventRow(buildToolCard(tc))). """ import json +import re import shutil import subprocess from pathlib import Path @@ -35,11 +36,77 @@ UI_JS_PATH = REPO_ROOT / "static" / "ui.js" NODE = shutil.which("node") pytestmark = pytest.mark.skipif(NODE is None, reason="node not on PATH") +_FN_NAMES = [ + '_toolDisplayName', '_toolActionKind', '_toolKindIcon', '_toolPathBasename', + '_decodeToolLabelEntities', '_redactToolTargetLabel', '_shortToolLabel', '_toolI18n', + '_toolTargetLabel', '_toolVisibleTargetLabel', '_toolCommandTitle', '_toolQueryTitle', + '_toolActionLabelText', '_toolArgPreviewValue', '_toolArgPreviewKeyIsHidden', + '_formatToolArgPreview', '_toolResultOneLiner', '_toolCardPreviewText', '_toolCardAllowsDetail', + '_toolDetailLeadLabel', '_toolDetailLeadText', '_toolShortName', '_transparentEventPreview', + '_transparentToolStatus', '_transparentToolSummary', + '_isMemorySave', '_isSkillUpdate', '_tcAction', + 'buildToolCard', '_decorateTransparentEventRow', +] -_DRIVER_SRC = r""" -const fs = require('fs'); -const src = fs.readFileSync(process.argv[2], 'utf8'); +def _function_source(src: str, name: str) -> str: + match = re.search(rf"function\s+{re.escape(name)}\s*\(", src) + if not match: + return "" + brace = src.find("{", match.end()) + assert brace != -1, f"{name}() has no body" + depth = 1 + i = brace + 1 + in_string = None + escaped = False + in_line_comment = False + in_block_comment = False + while i < len(src) and depth: + ch = src[i] + nxt = src[i + 1] if i + 1 < len(src) else "" + if in_line_comment: + if ch == "\n": + in_line_comment = False + i += 1 + continue + if in_block_comment: + if ch == "*" and nxt == "/": + in_block_comment = False + i += 2 + continue + i += 1 + continue + if in_string: + if escaped: + escaped = False + elif ch == "\\": + escaped = True + elif ch == in_string: + in_string = None + i += 1 + continue + if ch == "/" and nxt == "/": + in_line_comment = True + i += 2 + continue + if ch == "/" and nxt == "*": + in_block_comment = True + i += 2 + continue + if ch in "'\"`": + in_string = ch + i += 1 + continue + if ch == "{": + depth += 1 + elif ch == "}": + depth -= 1 + i += 1 + assert depth == 0, f"{name}() body did not close" + return src[match.start():i] + + +_DRIVER_TEMPLATE = r""" // ── Minimal DOM shims (enough for buildToolCard + _decorateTransparentEventRow) function makeEl(tag){ const el = { @@ -61,7 +128,6 @@ function makeEl(tag){ get innerHTML(){return el._html;}, set innerHTML(v){ el._html=String(v); - // Parse just the spans we assert on out of the template buildToolCard emits. el.children=[]; const mk=(cls,text)=>{const c=makeEl('span'); c._classes=new Set(cls.split(' ').filter(Boolean)); c.textContent=text; c.parentNode=el; el.children.push(c);}; const grab=(cls)=>{const re=new RegExp('([\\s\\S]*?)<\\/span>'); const m=el._html.match(re); return m?m[1]:null;}; @@ -70,7 +136,6 @@ function makeEl(tag){ const preview=grab('tool-card-preview'); if(preview!==null) mk('tool-card-preview', preview); const header=makeEl('div'); header._classes=new Set(['tool-card-header']); - // re-home the parsed spans under a header element so querySelector('.tool-card-header') works header.children=el.children.slice(); header.children.forEach(c=>c.parentNode=header); const card=makeEl('div'); card._classes=new Set(['tool-card']); card.appendChild(header); el.children=[card]; el.firstChild=card; @@ -87,8 +152,8 @@ function makeEl(tag){ return el; } -global.document = { createElement: (t)=>makeEl(t), querySelectorAll:()=>[], querySelector:()=>null }; -global.window = {}; +global.document = { createElement: (t)=>makeEl(t), querySelectorAll:()=>[], querySelector:()=>null, addEventListener:()=>{}, removeEventListener:()=>{} }; +global.window = { addEventListener:()=>{}, removeEventListener:()=>{} }; global.CSS = { escape: s=>s }; global.t = undefined; global.li = () => ''; @@ -100,36 +165,10 @@ global._attachCopyButton = () => {}; global._attachProgressBar = () => {}; global._wireTransparentHeaderToggle = () => {}; global._transparentToolDetailHtml = () => '
'; -// _toolDisclosureIdentity pulls in worklog hashing helpers irrelevant to the -// collapsed-preview assertion; stub it (only feeds a data- attribute). global._toolDisclosureIdentity = () => ''; -function extractFunc(name){ - const re=new RegExp('function\\s+'+name+'\\s*\\('); - const start=src.search(re); if(start<0) throw new Error(name+' not found'); - let i=src.indexOf('{',start); let depth=1; i++; - while(depth>0&&i 0 && i < src.length) { - if (src[i] === '{') depth++; - else if (src[i] === '}') depth--; - i++; - } - return src.slice(start, i); +const lines = fs.readFileSync(process.argv[2], 'utf8').split('\n'); +let startIdx = -1, endIdx = lines.length; +for (let i = 0; i < lines.length; i++) { + if (/^function _toolArgPreviewValue\(/.test(lines[i]) && startIdx < 0) startIdx = i; + if (/^function _toolCardAllowsDetail\(/.test(lines[i])) { endIdx = i; break; } } -eval(extractFunc('_toolArgPreviewValue')); -eval(extractFunc('_toolArgPreviewKeyIsHidden')); -eval(extractFunc('_formatToolArgPreview')); -eval(extractFunc('_toolResultOneLiner')); -eval(extractFunc('_toolCardPreviewText')); +if (startIdx < 0) throw new Error('_toolArgPreviewValue not found'); +eval(lines.slice(startIdx, endIdx).join('\n')); let buf = ''; process.stdin.on('data', c => { buf += c; }); process.stdin.on('end', () => { From 2ca65abefa1fde684871cf9ff54e9fa97d95543c Mon Sep 17 00:00:00 2001 From: Rod Boev Date: Wed, 24 Jun 2026 10:02:00 -0400 Subject: [PATCH 5/6] fix: use JSON.parse for bracket-prefixed lines instead of blanket suppression --- static/ui.js | 3 ++- tests/test_issue4752_collapsed_card_shows_result.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/static/ui.js b/static/ui.js index 8fd37a78..45252c98 100644 --- a/static/ui.js +++ b/static/ui.js @@ -12908,7 +12908,8 @@ function _toolResultOneLiner(preview){ const first=preview.split('\n').find(l=>l.trim())||''; const trimmed=first.trim(); if(!trimmed) return ''; - if(trimmed[0]==='{'||trimmed[0]==='[') return ''; + if(trimmed[0]==='{') return ''; + if(trimmed[0]==='['){try{JSON.parse(trimmed);return '';}catch(e){/* not JSON */}} return trimmed.length>180?trimmed.slice(0,177)+'…':trimmed; } function _toolCardPreviewText(tc, displaySnippet){ diff --git a/tests/test_issue4752_collapsed_card_shows_result.py b/tests/test_issue4752_collapsed_card_shows_result.py index 3cb2e6f5..c1ec8e9d 100644 --- a/tests/test_issue4752_collapsed_card_shows_result.py +++ b/tests/test_issue4752_collapsed_card_shows_result.py @@ -99,6 +99,9 @@ class TestToolResultOneLiner: def test_json_array_suppressed(self): assert _result_one_liner('[1,2,3]') == "" + def test_bracket_prefixed_non_json_shown(self): + assert _result_one_liner('[INFO] 3 files created') == "[INFO] 3 files created" + def test_multiline_collapses_to_first_nonempty_line(self): assert _result_one_liner("Line 1\nLine 2\nLine 3") == "Line 1" From 2de2599e8c57c0b623a7ae6eb836aa0dcdce3390 Mon Sep 17 00:00:00 2001 From: nesquena-hermes Date: Wed, 24 Jun 2026 20:45:30 +0000 Subject: [PATCH 6/6] Release WN (v0.51.633): collapsed tool cards preview result not call args (#4851, closes #4752) --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9b27059..95553115 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ ## [Unreleased] +## [v0.51.633] — 2026-06-24 — Release WN (collapsed tool cards preview the result, not the call args) + +### Fixed + +- **A completed tool card's collapsed header now previews the tool's result instead of its call arguments.** For a finished tool, the one-line header showed the call args (e.g. `path=src/`) rather than what actually happened (e.g. `Found 3 matches`). Completed tools now surface a one-line result preview (from the tool's preview/snippet), suppressing JSON object/array blobs and capping length, while running tools and the args-preview fallback are unchanged. The preview is escaped on render (no XSS). Thanks @rodboev. (#4851, closes #4752) + ## [v0.51.632] — 2026-06-24 — Release WM (new conversations use your configured default model) ### Fixed