Keep welcome screen free of docked canvas

Route non-action canvas surfaces to their floating/modal forms while the welcome screen is active, and keep the canvas rail/shell hidden until a chat is selected. Open the welcome Files quick action through the File Browser modal directly so the first screen remains composed. Document the welcome/canvas boundary in the local DOX contracts and add focused regression assertions for the canvas guard and Files quick action.

Alessandro committed Jun 23, 2026 at 17:23 UTC 617adaf8a21888fc117bb3cf601fb20009f7fea6
6 files changed +25 -6
tests/test_browser_agent_regressions.py
+5 -2
@@ -830,7 +830,7 @@ def test_surface_buttons_keep_modal_and_canvas_entry_points_separate():
830 canvas_store.index("async undockActiveSurface")
831 ]
832 should_render_block = canvas_store[
833 - canvas_store.index("shouldRender()"):
833 + canvas_store.index("\n shouldRender()"):
834 canvas_store.index("};\n\nexport const store")
835 ]
836 surface_button_block = surfaces_js[
@@ -845,6 +845,7 @@ def test_surface_buttons_keep_modal_and_canvas_entry_points_separate():
845 assert "markSurfaceMounted(targetId)" in canvas_store
846 assert "isSurfaceRendered(id)" in canvas_store
847 assert "isSurfaceVisible(id)" in canvas_store
848 + assert 'import { store as chatsStore } from "/components/sidebar/chats/chats-store.js";' in canvas_store
849 assert "async openLatest(surfaceId" in canvas_store
850 assert "async openModalSurface(surfaceId" in canvas_store
851 assert "this.recordSurfaceMode(targetId, SURFACE_MODE_DOCKED" in canvas_store
@@ -856,8 +857,10 @@ def test_surface_buttons_keep_modal_and_canvas_entry_points_separate():
857 assert "return await this.openModalSurface(targetId, payload);" in canvas_store
858 assert "return await this.open(targetId, payload);" in canvas_store
859 assert 'return await this.open(this.activeSurfaceId || this.panelSurfaces[0]?.id || "", { source: "mobile-toggle" });' in canvas_store
859 - assert "return true;" in should_render_block
860 + assert "isWelcomeVisible()" in canvas_store
861 + assert "return !this.isWelcomeVisible();" in should_render_block
862 assert "isMobileMode" not in should_render_block
863 + assert 'document.body.classList.toggle("right-canvas-open", this.isOpen && !this.isMobileMode && this.shouldRender());' in canvas_store
864 assert "this.mountedSurfaces = {}" not in close_block
865 assert "surface?.close" not in close_block
866 assert "this.mountedSurfaces = {}" not in undock_block
tests/test_file_browser_navigation.py
+4
@@ -144,6 +144,7 @@ def test_file_browser_is_registered_as_right_canvas_surface() -> None:
144 register = read("extensions", "webui", "right_canvas_register_surfaces", "register-files.js")
145 panel = read("extensions", "webui", "right-canvas-panels", "files-panel.html")
146 input_store = read("webui", "components", "chat", "input", "input-store.js")
147 + welcome_store = read("webui", "components", "welcome", "welcome-store.js")
148
149 assert 'id: "files"' in surfaces
150 assert 'title: "Files"' in surfaces
@@ -167,6 +168,9 @@ def test_file_browser_is_registered_as_right_canvas_surface() -> None:
168 assert 'data-surface-id="files"' in panel
169 assert 'path="modals/file-browser/file-browser.html" mode="canvas"' in panel
170 assert 'openLatestSurface("files"' in input_store
171 + assert 'import { store as fileBrowserStore } from "/components/modals/file-browser/file-browser-store.js";' in welcome_store
172 + assert "fileBrowserStore.open()" in welcome_store
173 + assert "chatInputStore.browseFiles" not in welcome_store
174
175
176 def test_file_browser_reports_missing_directory(tmp_path: Path) -> None:
webui/components/canvas/AGENTS.md
+1
@@ -15,6 +15,7 @@
15 - Keep registered surfaces compatible with WebUI extension hooks.
16 - Preserve responsive layout and avoid overlapping the chat/sidebar shells.
17 - Right-canvas rail and tab buttons are explicit canvas entry points above the mobile breakpoint; at mobile widths, keep the rail visible but route non-action surfaces into floating modals instead of the side canvas shell.
18 +- Keep the right-canvas rail and docked shell hidden while the welcome screen is active; non-action surface opens during welcome must route into floating/modal surfaces instead of docking beside the welcome screen.
19 - In mobile mode, keep the rail below blocking modal layers and compact it on very narrow screens instead of letting it cover modal content.
20
21 ## Work Guidance
webui/components/canvas/right-canvas-store.js
+12 -2
@@ -1,4 +1,5 @@
1 import { createStore } from "/js/AlpineStore.js";
2 +import { store as chatsStore } from "/components/sidebar/chats/chats-store.js";
3 import { callJsExtensions } from "/js/extensions.js";
4 import {
5 SURFACE_MODE_DOCKED,
@@ -135,6 +136,10 @@ const model = {
136 return true;
137 }
138
139 + if (!this.shouldRender()) {
140 + return await this.openModalSurface(targetId, payload);
141 + }
142 +
143 if (this.isMobileMode) {
144 this.activeSurfaceId = targetId;
145 this.isOpen = false;
@@ -342,6 +347,7 @@ const model = {
347 },
348
349 async toggleCanvas() {
350 + if (!this.shouldRender()) return false;
351 if (this.isMobileMode) {
352 return await this.open(this.activeSurfaceId || this.panelSurfaces[0]?.id || "", { source: "mobile-toggle" });
353 }
@@ -476,7 +482,7 @@ const model = {
482 applyLayoutState() {
483 this.updateLayoutMode();
484 document.documentElement.style.setProperty("--right-canvas-width", `${this.width}px`);
479 - document.body.classList.toggle("right-canvas-open", this.isOpen && !this.isMobileMode);
485 + document.body.classList.toggle("right-canvas-open", this.isOpen && !this.isMobileMode && this.shouldRender());
486 document.body.classList.toggle("right-canvas-overlay-mode", this.isOverlayMode);
487 document.body.classList.toggle("right-canvas-mobile-mode", this.isMobileMode);
488 },
@@ -517,8 +523,12 @@ const model = {
523 return this.currentSurface()?.title || "Canvas";
524 },
525
526 + isWelcomeVisible() {
527 + return !chatsStore.selected;
528 + },
529 +
530 shouldRender() {
521 - return true;
531 + return !this.isWelcomeVisible();
532 },
533 };
534
webui/components/welcome/AGENTS.md
+1
@@ -16,6 +16,7 @@
16 - Do not show setup prompts for already configured plugins when backend status can prevent it.
17 - The welcome screen mounts the shared chat composer to start a new chat; keep it mutually exclusive with the normal chat input DOM.
18 - Render `system-resources` as the dedicated System Resources panel, not as a generic alert banner.
19 +- Utility quick actions from welcome must keep the first screen intact; use modal/floating entry points instead of docking the right canvas beside welcome content.
20
21 ## Work Guidance
22
webui/components/welcome/welcome-store.js
+2 -2
@@ -2,7 +2,7 @@ import { createStore } from "/js/AlpineStore.js";
2 import { store as chatsStore } from "/components/sidebar/chats/chats-store.js";
3 import { store as memoryStore } from "/plugins/_memory/webui/memory-dashboard-store.js";
4 import { store as projectsStore } from "/components/projects/projects-store.js";
5 -import { store as chatInputStore } from "/components/chat/input/input-store.js";
5 +import { store as fileBrowserStore } from "/components/modals/file-browser/file-browser-store.js";
6 import * as API from "/js/api.js";
7 import { getCurrentUserISOString } from "/js/time-utils.js";
8
@@ -261,7 +261,7 @@ const model = {
261 memoryStore.openModal();
262 break;
263 case "files":
264 - chatInputStore.browseFiles();
264 + fileBrowserStore.open();
265 break;
266 case "website":
267 window.open("https://agent-zero.ai", "_blank");