Fix Office artifact creation and canvas closing
Add an explicit close button to the right canvas toolbar, next to the undock control, and cover its label, handler, and ordering in the canvas regression test. Treat document_artifact tool_args.method as an action alias so calls like method=create with format=xlsx create workbooks instead of falling back to LibreOffice status. Add regression coverage for the exact XLSX creation shape.
Alessandro committed
May 2, 2026 at 14:07 UTC
baac20f7a474233c79ede13cd73fd852416abe73
4 files changed
+73
-1
plugins/_office/tools/document_artifact.py
+2
-1
@@ -28,9 +28,10 @@ class DocumentArtifact(Tool):
28
chart: Any = None,
29
slides: Any = None,
30
max_chars: int | str = 12000,
31
+ method: str = "",
32
**kwargs: Any,
33
) -> Response:
33
- action = str(action or self.method or "status").strip().lower().replace("-", "_")
34
+ action = str(action or method or self.method or "status").strip().lower().replace("-", "_")
35
try:
36
if action == "create":
37
doc = document_store.create_document(
tests/test_office_canvas_setup.py
+5
@@ -300,6 +300,11 @@ def test_right_canvas_requires_explicit_open_and_is_absent_on_mobile():
300
assert "if (this.isMobileMode)" in canvas_store
301
assert "shouldRender()" in canvas_store
302
assert "$store.rightCanvas.shouldRender()" in canvas_html
303
+ assert 'title="Open as window"' in canvas_html
304
+ assert 'title="Close canvas"' in canvas_html
305
+ assert 'aria-label="Close canvas"' in canvas_html
306
+ assert "@click=\"$store.rightCanvas.close()\"" in canvas_html
307
+ assert canvas_html.index('title="Open as window"') < canvas_html.index('title="Close canvas"')
308
assert "body.right-canvas-mobile-mode .right-canvas" in canvas_css
309
assert "display: none !important" in canvas_css
310
assert "autoOpenOfficeCanvas" not in handler
tests/test_office_document_store.py
+57
@@ -1,5 +1,6 @@
1
from __future__ import annotations
2
3
+import asyncio
4
import importlib.util
5
import json
6
import os
@@ -127,6 +128,62 @@ def test_xlsx_and_pptx_creation_and_direct_edits_still_work(office_state):
128
assert deck_read["slides"][1]["title"] == "Next"
129
130
131
+def test_document_artifact_accepts_method_alias_for_xlsx_create(office_state, monkeypatch):
132
+ tool_module = types.ModuleType("helpers.tool")
133
+
134
+ class Response:
135
+ def __init__(self, message, break_loop, additional=None):
136
+ self.message = message
137
+ self.break_loop = break_loop
138
+ self.additional = additional
139
+
140
+ class Tool:
141
+ def __init__(self, agent, name, method, args, message, loop_data, **kwargs):
142
+ self.agent = agent
143
+ self.name = name
144
+ self.method = method
145
+ self.args = args
146
+ self.message = message
147
+ self.loop_data = loop_data
148
+
149
+ tool_module.Response = Response
150
+ tool_module.Tool = Tool
151
+ monkeypatch.setitem(sys.modules, "helpers.tool", tool_module)
152
+ spec = importlib.util.spec_from_file_location(
153
+ "test_document_artifact_tool",
154
+ PROJECT_ROOT / "plugins" / "_office" / "tools" / "document_artifact.py",
155
+ )
156
+ document_artifact_module = importlib.util.module_from_spec(spec)
157
+ assert spec and spec.loader
158
+ spec.loader.exec_module(document_artifact_module)
159
+ DocumentArtifact = document_artifact_module.DocumentArtifact
160
+
161
+ tool = DocumentArtifact(
162
+ agent=None,
163
+ name="document_artifact",
164
+ method=None,
165
+ args={},
166
+ message="",
167
+ loop_data=None,
168
+ )
169
+
170
+ response = asyncio.run(
171
+ tool.execute(
172
+ method="create",
173
+ kind="document",
174
+ title="New Excel Workbook",
175
+ format="xlsx",
176
+ content="Sheet1\n",
177
+ )
178
+ )
179
+ payload = json.loads(response.message)
180
+
181
+ assert payload["action"] == "create"
182
+ assert payload["document"]["extension"] == "xlsx"
183
+ assert Path(payload["document"]["path"]).name == "New Excel Workbook.xlsx"
184
+ assert Path(document_store._path_from_a0(payload["document"]["path"])).exists()
185
+
186
+
187
def test_odt_is_not_advertised_and_returns_clear_unsupported_response(office_state):
188
prompt = (PROJECT_ROOT / "plugins" / "_office" / "prompts" / "agent.system.tool.document_artifact.md").read_text(
189
encoding="utf-8",
webui/components/canvas/right-canvas.html
+9
@@ -98,6 +98,15 @@
98
>
99
<span class="material-symbols-outlined">open_in_new</span>
100
</button>
101
+ <button
102
+ type="button"
103
+ class="right-canvas-icon-button right-canvas-close-button"
104
+ title="Close canvas"
105
+ aria-label="Close canvas"
106
+ @click="$store.rightCanvas.close()"
107
+ >
108
+ <span class="material-symbols-outlined">close</span>
109
+ </button>
110
<x-extension id="right-canvas-toolbar-end"></x-extension>
111
</div>
112
</header>