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.
This commit is contained in:
Alessandro 2026-06-23 17:23:45 +02:00
parent 8bcf1d3165
commit 617adaf8a2
6 changed files with 25 additions and 6 deletions

View file

@ -830,7 +830,7 @@ def test_surface_buttons_keep_modal_and_canvas_entry_points_separate():
canvas_store.index("async undockActiveSurface")
]
should_render_block = canvas_store[
canvas_store.index("shouldRender()"):
canvas_store.index("\n shouldRender()"):
canvas_store.index("};\n\nexport const store")
]
surface_button_block = surfaces_js[
@ -845,6 +845,7 @@ def test_surface_buttons_keep_modal_and_canvas_entry_points_separate():
assert "markSurfaceMounted(targetId)" in canvas_store
assert "isSurfaceRendered(id)" in canvas_store
assert "isSurfaceVisible(id)" in canvas_store
assert 'import { store as chatsStore } from "/components/sidebar/chats/chats-store.js";' in canvas_store
assert "async openLatest(surfaceId" in canvas_store
assert "async openModalSurface(surfaceId" in canvas_store
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():
assert "return await this.openModalSurface(targetId, payload);" in canvas_store
assert "return await this.open(targetId, payload);" in canvas_store
assert 'return await this.open(this.activeSurfaceId || this.panelSurfaces[0]?.id || "", { source: "mobile-toggle" });' in canvas_store
assert "return true;" in should_render_block
assert "isWelcomeVisible()" in canvas_store
assert "return !this.isWelcomeVisible();" in should_render_block
assert "isMobileMode" not in should_render_block
assert 'document.body.classList.toggle("right-canvas-open", this.isOpen && !this.isMobileMode && this.shouldRender());' in canvas_store
assert "this.mountedSurfaces = {}" not in close_block
assert "surface?.close" not in close_block
assert "this.mountedSurfaces = {}" not in undock_block

View file

@ -144,6 +144,7 @@ def test_file_browser_is_registered_as_right_canvas_surface() -> None:
register = read("extensions", "webui", "right_canvas_register_surfaces", "register-files.js")
panel = read("extensions", "webui", "right-canvas-panels", "files-panel.html")
input_store = read("webui", "components", "chat", "input", "input-store.js")
welcome_store = read("webui", "components", "welcome", "welcome-store.js")
assert 'id: "files"' in surfaces
assert 'title: "Files"' in surfaces
@ -167,6 +168,9 @@ def test_file_browser_is_registered_as_right_canvas_surface() -> None:
assert 'data-surface-id="files"' in panel
assert 'path="modals/file-browser/file-browser.html" mode="canvas"' in panel
assert 'openLatestSurface("files"' in input_store
assert 'import { store as fileBrowserStore } from "/components/modals/file-browser/file-browser-store.js";' in welcome_store
assert "fileBrowserStore.open()" in welcome_store
assert "chatInputStore.browseFiles" not in welcome_store
def test_file_browser_reports_missing_directory(tmp_path: Path) -> None:

View file

@ -15,6 +15,7 @@
- Keep registered surfaces compatible with WebUI extension hooks.
- Preserve responsive layout and avoid overlapping the chat/sidebar shells.
- 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.
- 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.
- In mobile mode, keep the rail below blocking modal layers and compact it on very narrow screens instead of letting it cover modal content.
## Work Guidance

View file

@ -1,4 +1,5 @@
import { createStore } from "/js/AlpineStore.js";
import { store as chatsStore } from "/components/sidebar/chats/chats-store.js";
import { callJsExtensions } from "/js/extensions.js";
import {
SURFACE_MODE_DOCKED,
@ -135,6 +136,10 @@ const model = {
return true;
}
if (!this.shouldRender()) {
return await this.openModalSurface(targetId, payload);
}
if (this.isMobileMode) {
this.activeSurfaceId = targetId;
this.isOpen = false;
@ -342,6 +347,7 @@ const model = {
},
async toggleCanvas() {
if (!this.shouldRender()) return false;
if (this.isMobileMode) {
return await this.open(this.activeSurfaceId || this.panelSurfaces[0]?.id || "", { source: "mobile-toggle" });
}
@ -476,7 +482,7 @@ const model = {
applyLayoutState() {
this.updateLayoutMode();
document.documentElement.style.setProperty("--right-canvas-width", `${this.width}px`);
document.body.classList.toggle("right-canvas-open", this.isOpen && !this.isMobileMode);
document.body.classList.toggle("right-canvas-open", this.isOpen && !this.isMobileMode && this.shouldRender());
document.body.classList.toggle("right-canvas-overlay-mode", this.isOverlayMode);
document.body.classList.toggle("right-canvas-mobile-mode", this.isMobileMode);
},
@ -517,8 +523,12 @@ const model = {
return this.currentSurface()?.title || "Canvas";
},
isWelcomeVisible() {
return !chatsStore.selected;
},
shouldRender() {
return true;
return !this.isWelcomeVisible();
},
};

View file

@ -16,6 +16,7 @@
- Do not show setup prompts for already configured plugins when backend status can prevent it.
- The welcome screen mounts the shared chat composer to start a new chat; keep it mutually exclusive with the normal chat input DOM.
- Render `system-resources` as the dedicated System Resources panel, not as a generic alert banner.
- 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.
## Work Guidance

View file

@ -2,7 +2,7 @@ import { createStore } from "/js/AlpineStore.js";
import { store as chatsStore } from "/components/sidebar/chats/chats-store.js";
import { store as memoryStore } from "/plugins/_memory/webui/memory-dashboard-store.js";
import { store as projectsStore } from "/components/projects/projects-store.js";
import { store as chatInputStore } from "/components/chat/input/input-store.js";
import { store as fileBrowserStore } from "/components/modals/file-browser/file-browser-store.js";
import * as API from "/js/api.js";
import { getCurrentUserISOString } from "/js/time-utils.js";
@ -261,7 +261,7 @@ const model = {
memoryStore.openModal();
break;
case "files":
chatInputStore.browseFiles();
fileBrowserStore.open();
break;
case "website":
window.open("https://agent-zero.ai", "_blank");